use super::*;
use crate::model::{
Bone, MeshAsset, MeshInstance, Primitive, SceneAssets, SourceInverseBindAccessor,
SourceNodeAsset, SourceNodeLocalRest, SourceProjectionViolation, SourceSkeletonAssets,
SourceSkeletonCoverage, SourceSkinAsset, SourceSkinAttachment, Track,
};
use glam::{Quat, Vec3};
struct RigNode {
parent: Option<BoneId>,
source_node_index: usize,
translation: Vec3,
rotation: Quat,
scale: Vec3,
}
fn rig(parent: Option<BoneId>, source_node_index: usize, translation: Vec3) -> RigNode {
RigNode {
parent,
source_node_index,
translation,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
}
}
fn rig_document(
nodes: &[RigNode],
skin_bones: &[BoneId],
skin_source_index: usize,
ibm: Mat4,
) -> Document {
let bones: Vec<Bone> = nodes
.iter()
.enumerate()
.map(|(id, n)| Bone {
name: format!("bone{id}"),
parent: n.parent,
rest: Transform {
translation: n.translation,
rotation: n.rotation,
scale: n.scale,
},
inverse_bind: None,
})
.collect();
let source_nodes: Vec<SourceNodeAsset> = nodes
.iter()
.enumerate()
.map(|(id, n)| SourceNodeAsset {
source_node_index: n.source_node_index,
name: None,
parent_source_node_index: n.parent.map(|p| nodes[p].source_node_index),
scene_root_indices: if n.parent.is_none() { vec![0] } else { vec![] },
local_rest: SourceNodeLocalRest::Trs {
translation: n.translation,
rotation: n.rotation,
scale: n.scale,
},
bone: Some(id),
})
.collect();
let joint_source_node_indices: Vec<usize> = skin_bones
.iter()
.map(|&b| nodes[b].source_node_index)
.collect();
let mesh_owner_source_index =
nodes[*skin_bones.last().expect("at least one joint")].source_node_index;
Document {
skeleton: Skeleton { bones },
clips: Vec::new(),
assets: SceneAssets {
meshes: vec![MeshAsset {
name: "mesh".into(),
source_mesh_index: 0,
primitives: vec![Primitive {
positions: vec![Vec3::new(1.0, 0.0, 0.0)],
joints: vec![[0, 0, 0, 0]],
weights: vec![[1.0, 0.0, 0.0, 0.0]],
..Primitive::default()
}],
}],
instances: vec![MeshInstance {
source_node_index: mesh_owner_source_index,
node: skin_bones[0],
mesh: 0,
skin_joints: skin_bones.to_vec(),
skin_ibms: vec![ibm; skin_bones.len()],
}],
source_skeleton: SourceSkeletonAssets {
coverage: SourceSkeletonCoverage::Complete,
nodes: source_nodes,
skins: vec![SourceSkinAsset {
source_skin_index: skin_source_index,
name: None,
skeleton_root_source_node_index: None,
joint_source_node_indices,
inverse_bind_accessor: SourceInverseBindAccessor::default(),
attachments: Vec::new(),
}],
},
..SceneAssets::default()
},
source: Default::default(),
}
}
fn complete_capability() -> ScaleCapabilityFacts {
ScaleCapabilityFacts {
coverage: ScaleCapabilityCoverage::Complete,
..Default::default()
}
}
fn unit_rig() -> Vec<RigNode> {
vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
]
}
#[test]
fn assembly_basis_fingerprints_target_factors_and_rejects_orientation_or_helper_drift() {
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
},
rig(Some(0), 1, Vec3::new(0.0, 100.0, 0.0)),
];
let mut document = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
document.clips.push(Clip {
name: "cubic".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::CubicSpline,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![Vec3::ZERO; 6]),
}],
});
let capability = complete_capability();
let operation = ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
};
let plan = plan_scale(&ScaleRequest {
operation,
document: &document,
capability: &capability,
})
.unwrap();
let basis = assembly_scale_basis(&document, &plan).unwrap();
assert_eq!(basis.version, ASSEMBLY_SCALE_BASIS_VERSION);
assert_eq!(
basis.tolerance_policy_id,
ScaleTolerancePolicy::APPENDIX_D_V6.id
);
assert_eq!(basis.target_paths.len(), 1);
assert_eq!(basis.target_paths[0].factor_bits, 0.01f64.to_bits());
let mut orientation = document.clone();
orientation.skeleton.bones[1].rest.rotation = Quat::from_rotation_z(0.01);
if let SourceNodeLocalRest::Trs { rotation, .. } =
&mut orientation.assets.source_skeleton.nodes[1].local_rest
{
*rotation = Quat::from_rotation_z(0.01);
}
let orientation_plan = plan_scale(&ScaleRequest {
operation,
document: &orientation,
capability: &capability,
})
.unwrap();
let orientation_basis = assembly_scale_basis(&orientation, &orientation_plan).unwrap();
assert_eq!(
require_assembly_scale_compatibility(&basis, &orientation_basis)
.unwrap_err()
.reason,
"named-orientation"
);
let mut equivalent = basis.clone();
equivalent.named_nodes[1].rotation_bits = equivalent.named_nodes[1]
.rotation_bits
.map(|bits| (-f32::from_bits(bits)).to_bits());
equivalent.named_nodes[1].translation_bits[1] =
f32::from_bits(equivalent.named_nodes[1].translation_bits[1])
.next_up()
.to_bits();
let AssemblyScaleSourceRest::Trs {
translation_bits,
rotation_bits,
..
} = &mut equivalent.source_nodes[1].local_rest
else {
panic!("fixture source node uses TRS")
};
*rotation_bits = rotation_bits.map(|bits| (-f32::from_bits(bits)).to_bits());
translation_bits[1] = f32::from_bits(translation_bits[1]).next_up().to_bits();
assert_eq!(basis, basis.clone());
assert_ne!(basis, equivalent, "fingerprint material remains exact");
require_assembly_scale_compatibility(&basis, &equivalent)
.expect("q/-q and in-band numeric spelling are semantically equivalent");
for (changed, expected) in [
(
{
let mut changed = basis.clone();
changed.version += 1;
changed
},
"basis-version",
),
(
{
let mut changed = basis.clone();
changed.coordinate_convention = "left-handed-z-up-centimetres";
changed
},
"coordinate-convention",
),
(
{
let mut changed = basis.clone();
changed.tolerance_policy_id = "appendix-d-v999";
changed
},
"tolerance-policy",
),
(
{
let mut changed = basis.clone();
changed.source_skin_index += 1;
changed
},
"source-skin-selector",
),
(
{
let mut changed = basis.clone();
changed.source_root_node_index += 1;
changed
},
"source-root-selector",
),
(
{
let mut changed = basis.clone();
changed.expected_factor_bits = 0.02f64.to_bits();
changed
},
"expected-factor",
),
(
{
let mut changed = basis.clone();
changed.named_nodes[1].parent = None;
changed
},
"named-topology",
),
(
{
let mut changed = basis.clone();
changed.named_nodes[1].translation_bits[1] = 101.0f32.to_bits();
changed
},
"named-rest-basis",
),
] {
assert_eq!(
require_assembly_scale_compatibility(&basis, &changed)
.unwrap_err()
.reason,
expected
);
}
let mut distinct_take = basis.clone();
distinct_take.target_paths[0].bone = "another-valid-take-target".into();
distinct_take.target_paths[0].factor_bits = 0.5f64.to_bits();
assert_ne!(
basis, distinct_take,
"target paths remain fingerprint material"
);
require_assembly_scale_compatibility(&basis, &distinct_take)
.expect("each input validates its own target paths and plan factors");
let mut helper = document.clone();
helper.assets.source_skeleton.nodes[1].name = Some("changed-helper-name".into());
let helper_plan = plan_scale(&ScaleRequest {
operation,
document: &helper,
capability: &capability,
})
.unwrap();
let helper_basis = assembly_scale_basis(&helper, &helper_plan).unwrap();
assert_eq!(
require_assembly_scale_compatibility(&basis, &helper_basis)
.unwrap_err()
.reason,
"source-helper-layout"
);
let mut connector = document.clone();
connector.assets.source_skeleton.nodes[1].parent_source_node_index = Some(2);
let mut unnamed = SourceNodeAsset::new(2, SourceNodeLocalRest::Matrix(Mat4::IDENTITY));
unnamed.parent_source_node_index = Some(0);
connector.assets.source_skeleton.nodes.push(unnamed);
let connector_plan = plan_scale(&ScaleRequest {
operation,
document: &connector,
capability: &capability,
})
.unwrap();
let connector_basis = assembly_scale_basis(&connector, &connector_plan).unwrap();
assert_eq!(
require_assembly_scale_compatibility(&basis, &connector_basis)
.unwrap_err()
.reason,
"source-helper-layout"
);
let mut connector_matrix = connector.clone();
connector_matrix.assets.source_skeleton.nodes[2].local_rest =
SourceNodeLocalRest::Matrix(Mat4::from_translation(Vec3::new(0.1, 0.0, 0.0)));
let connector_matrix_plan = plan_scale(&ScaleRequest {
operation,
document: &connector_matrix,
capability: &capability,
})
.unwrap();
let connector_matrix_basis =
assembly_scale_basis(&connector_matrix, &connector_matrix_plan).unwrap();
assert_eq!(
require_assembly_scale_compatibility(&connector_basis, &connector_matrix_basis)
.unwrap_err()
.reason,
"source-helper-rest-basis"
);
}
#[test]
fn whole_document_factor_one_is_a_literal_no_op() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 1.0 },
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert_eq!(
candidate.document().skeleton.bones[1].rest.translation,
Vec3::new(0.0, 1.0, 0.0)
);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert!(proof.rest_translation.max() < 1e-9);
assert!(proof.bounds.max() < 1e-6);
}
#[test]
fn preserve_exact_rotation_rejects_signed_zero_and_quaternion_sign_changes() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let plan = whole_document_plan(&doc, &complete_capability());
let candidate = build_scale_candidate(&doc, &plan).unwrap();
for rotation in [
Quat::from_xyzw(-0.0, 0.0, 0.0, 1.0),
Quat::from_xyzw(-0.0, -0.0, -0.0, -1.0),
] {
let mut changed = candidate.document().clone();
let source_node = changed
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(0))
.unwrap();
let SourceNodeLocalRest::Trs {
rotation: candidate_rotation,
..
} = &mut source_node.local_rest
else {
panic!("fixture source root must use TRS")
};
*candidate_rotation = rotation;
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: changed }, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "field_disposition_mismatch"
}
);
}
}
#[test]
fn whole_document_conversion_scales_translation_mesh_and_ibm() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let child = &candidate.document().skeleton.bones[1];
assert!((child.rest.translation - Vec3::new(0.0, 0.01, 0.0)).length() < 1e-6);
assert_eq!(child.rest.scale, Vec3::ONE);
let mesh_position = candidate.document().assets.meshes[0].primitives[0].positions[0];
assert!((mesh_position - Vec3::new(0.01, 0.0, 0.0)).length() < 1e-6);
let ibm = candidate.document().assets.instances[0].skin_ibms[0];
assert!(ibm.w_axis.abs_diff_eq(Vec4::new(0.0, 0.0, 0.0, 1.0), 1e-6));
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert!(proof.bounds.max() < 1e-6);
}
#[test]
fn a_candidate_wrapped_from_an_external_document_is_proved_rather_than_trusted() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.unwrap();
let mut converted = doc.clone();
for bone in &mut converted.skeleton.bones {
bone.rest.translation *= 0.01;
}
for node in &mut converted.assets.source_skeleton.nodes {
node.local_rest = match &node.local_rest {
SourceNodeLocalRest::Trs {
translation,
rotation,
scale,
} => SourceNodeLocalRest::Trs {
translation: *translation * 0.01,
rotation: *rotation,
scale: *scale,
},
SourceNodeLocalRest::Matrix(matrix) => {
SourceNodeLocalRest::Matrix(scale_translation_only(*matrix, 0.01))
}
};
}
for mesh in &mut converted.assets.meshes {
for primitive in &mut mesh.primitives {
for position in &mut primitive.positions {
*position *= 0.01;
}
}
}
for instance in &mut converted.assets.instances {
for inverse_bind in &mut instance.skin_ibms {
*inverse_bind = scale_translation_only(*inverse_bind, 0.01);
}
}
let proof = prove_scale(&doc, &ScaleCandidate::from_document(converted), &plan).unwrap();
assert!(proof.rest_translation.max() < 1e-6);
assert!(proof.mesh_position.max() < 1e-6);
let error = prove_scale(&doc, &ScaleCandidate::from_document(doc.clone()), &plan)
.expect_err("an unconverted candidate must not prove");
assert!(
matches!(
error,
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::MeshPosition,
..
}
),
"expected a MeshPosition residual, got {error:?}"
);
}
#[test]
fn whole_document_conversion_scales_translation_track_values_and_cubic_tangents() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::CubicSpline,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::new(0.0, -1.0, 0.0), Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, -2.0, 0.0), Vec3::new(0.0, 2.0, 0.0), Vec3::new(0.0, 2.0, 0.0), ]),
}],
});
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let TrackValues::Vec3s(values) = &candidate.document().clips[0].tracks[0].values else {
panic!("expected vec3 track");
};
let expected: Vec<Vec3> = [-1.0, 1.0, 1.0, -2.0, 2.0, 2.0]
.into_iter()
.map(|y: f32| Vec3::new(0.0, y * 0.01, 0.0))
.collect();
for (value, expected) in values.iter().zip(expected) {
assert!((*value - expected).length() < 1e-6);
}
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert!(proof.cubic_interior.max() < 1e-4);
assert!(proof.trajectory.max() < 1e-4);
assert!(proof.sample_time_count > 0);
}
#[test]
fn rest_bind_resolves_shuffled_source_selectors_to_the_correct_bone_closure() {
let nodes = vec![
rig(None, 7, Vec3::ZERO), rig(Some(0), 2, Vec3::new(0.0, 1.0, 0.0)), ];
let doc = rig_document(&nodes, &[1], 42, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 42,
source_root_node_index: 7,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
assert_eq!(plan.affected_nodes(), &[0, 1]);
}
#[test]
fn rest_bind_factor_one_on_unit_rig_is_a_deterministic_no_op() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.clips.push(Clip {
name: "scale".into(),
duration_s: 0.0,
tracks: vec![Track {
bone: 0,
property: Property::Scale,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![Vec3::new(2.0, 3.0, 4.0)]),
}],
});
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert_eq!(
candidate.document().skeleton.bones[1].rest.translation,
Vec3::new(0.0, 1.0, 0.0)
);
let TrackValues::Vec3s(values) = &candidate.document().clips[0].tracks[0].values else {
panic!("expected vec3 scale track");
};
assert_eq!(values, &[Vec3::new(2.0, 3.0, 4.0)]);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert!(proof.rest_translation.max() < 1e-9);
}
#[test]
fn rest_bind_requesting_a_different_factor_on_unit_rig_rejects_as_factor_mismatch() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.5,
},
document: &doc,
capability: &capability,
};
let error = plan_scale(&request).unwrap_err();
assert!(matches!(error, ScaleError::FactorMismatch { .. }));
}
fn compensated_rig() -> Vec<RigNode> {
vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
},
RigNode {
parent: Some(0),
source_node_index: 1,
translation: Vec3::new(0.0, 100.0, 0.0),
rotation: Quat::from_rotation_y(0.2),
scale: Vec3::ONE,
},
rig(Some(1), 2, Vec3::new(1.0, 0.0, 0.0)),
]
}
fn compensated_document() -> Document {
let nodes = compensated_rig();
let child_world = Mat4::from_scale_rotation_translation(
nodes[0].scale,
nodes[1].rotation,
Vec3::new(0.0, 1.0, 0.0),
);
let ibm = child_world.inverse();
rig_document(&nodes, &[1], 0, ibm)
}
fn compensated_document_with_connectors(
connectors: &[Mat4],
raw_child: Transform,
normalized_child: Transform,
) -> Document {
assert!(!connectors.is_empty());
let mut doc = compensated_document();
doc.skeleton.bones[1].rest = normalized_child;
let child_source = doc
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.expect("the fixture projects bone 1");
child_source.local_rest = SourceNodeLocalRest::Trs {
translation: raw_child.translation,
rotation: raw_child.rotation,
scale: raw_child.scale,
};
child_source.parent_source_node_index = Some(10 + connectors.len() - 1);
for (offset, &matrix) in connectors.iter().enumerate() {
let mut connector = SourceNodeAsset::new(10 + offset, SourceNodeLocalRest::Matrix(matrix));
connector.parent_source_node_index = Some(if offset == 0 { 0 } else { 9 + offset });
doc.assets.source_skeleton.nodes.push(connector);
}
let root = &doc.skeleton.bones[0].rest;
let root_world =
Mat4::from_scale_rotation_translation(root.scale, root.rotation, root.translation);
let child_local = Mat4::from_scale_rotation_translation(
normalized_child.scale,
normalized_child.rotation,
normalized_child.translation,
);
doc.assets.instances[0].skin_ibms[0] = (root_world * child_local).inverse();
doc
}
fn matrix_with_columns(x: Vec4, y: Vec4, z: Vec4, translation: Vec3) -> Mat4 {
Mat4::from_cols(x, y, z, translation.extend(1.0))
}
fn assert_source_local_rest_exact(actual: &SourceNodeLocalRest, expected: &SourceNodeLocalRest) {
match (actual, expected) {
(SourceNodeLocalRest::Matrix(actual), SourceNodeLocalRest::Matrix(expected)) => {
assert_eq!(
actual.to_cols_array().map(f32::to_bits),
expected.to_cols_array().map(f32::to_bits)
);
}
(
SourceNodeLocalRest::Trs {
translation: actual_translation,
rotation: actual_rotation,
scale: actual_scale,
},
SourceNodeLocalRest::Trs {
translation: expected_translation,
rotation: expected_rotation,
scale: expected_scale,
},
) => {
assert_eq!(
actual_translation.to_array().map(f32::to_bits),
expected_translation.to_array().map(f32::to_bits)
);
assert_eq!(
actual_rotation.to_array().map(f32::to_bits),
expected_rotation.to_array().map(f32::to_bits)
);
assert_eq!(
actual_scale.to_array().map(f32::to_bits),
expected_scale.to_array().map(f32::to_bits)
);
}
_ => panic!("source local-rest representation changed"),
}
}
#[test]
fn rest_bind_rebases_a_projected_successor_through_one_unchanged_connector() {
let connector = matrix_with_columns(
Vec4::new(-2.0, -0.0, 0.0, 0.0),
Vec4::new(0.0, -2.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 2.0, 0.0),
Vec3::new(50.0, 0.0, 0.0),
);
let raw_child = Transform {
translation: Vec3::new(100.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.5),
};
let normalized_child = Transform {
translation: Vec3::new(-150.0, 0.0, 0.0),
rotation: Quat::from_rotation_z(std::f32::consts::PI),
scale: Vec3::ONE,
};
let doc = compensated_document_with_connectors(&[connector], raw_child, normalized_child);
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
assert!(!plan.affected_nodes().contains(&10));
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let connector_after = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.source_node_index == 10)
.unwrap();
assert_source_local_rest_exact(
&connector_after.local_rest,
&SourceNodeLocalRest::Matrix(connector),
);
let child_after = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(1))
.unwrap();
assert_source_local_rest_exact(
&child_after.local_rest,
&SourceNodeLocalRest::Trs {
translation: Vec3::new(25.75, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.5),
},
);
assert_eq!(
candidate.document().skeleton.bones[1].rest.translation,
Vec3::new(-1.5, 0.0, 0.0)
);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.rest_translation.comparisons(), 3);
assert_eq!(proof.rest_rotation.comparisons(), 3);
assert_eq!(proof.unit_scale.comparisons(), 3);
assert_eq!(proof.transform_only_affine.comparisons(), 1);
assert_eq!(proof.skin_matrix.comparisons(), 1);
assert_eq!(proof.bounds.comparisons(), 6);
assert!(proof.rest_translation.max() < 1e-4);
assert!(proof.rest_rotation.max() <= plan.tolerance_policy().rotation_residual_radians);
assert!(proof.unit_scale.max() <= plan.tolerance_policy().postcondition_unit_scale_residual);
assert!(proof.skin_matrix.max() < 1e-4);
assert!(proof.bounds.max() < 1e-4);
let mut changed_connector = candidate.document().clone();
let connector = changed_connector
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.source_node_index == 10)
.unwrap();
let SourceNodeLocalRest::Matrix(matrix) = &mut connector.local_rest else {
panic!("fixture connector changed representation");
};
matrix.w_axis.x += 1.0;
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate::from_document(changed_connector),
&plan,
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "connector_source_local_mismatch"
}
);
let mut changed_matrix_zero = candidate.document().clone();
let SourceNodeLocalRest::Matrix(matrix) = &mut changed_matrix_zero
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.source_node_index == 10)
.unwrap()
.local_rest
else {
panic!("fixture connector changed representation");
};
assert_eq!(matrix.x_axis.y.to_bits(), (-0.0f32).to_bits());
matrix.x_axis.y = 0.0;
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate::from_document(changed_matrix_zero),
&plan,
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "connector_source_local_mismatch"
}
);
let mut unrebased_successor = candidate.document().clone();
unrebased_successor
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest = doc
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest
.clone();
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate::from_document(unrebased_successor),
&plan,
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "bridged_source_local_mismatch"
}
);
}
#[test]
fn rest_bind_composes_and_preserves_a_trs_connector() {
let raw_child = Transform {
translation: Vec3::new(100.0, 0.0, 0.0),
..Transform::default()
};
let normalized_child = Transform {
translation: Vec3::new(150.0, 0.0, 0.0),
..Transform::default()
};
let mut doc = compensated_document_with_connectors(
&[Mat4::from_translation(Vec3::new(50.0, 0.0, 0.0))],
raw_child,
normalized_child,
);
let connector_before = SourceNodeLocalRest::Trs {
translation: Vec3::new(50.0, -0.0, 0.0),
rotation: Quat::from_xyzw(0.0, 0.0, 0.0, -1.0),
scale: Vec3::ONE,
};
doc.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.source_node_index == 10)
.unwrap()
.local_rest = connector_before.clone();
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let connector_after = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.source_node_index == 10)
.unwrap();
assert_source_local_rest_exact(&connector_after.local_rest, &connector_before);
let child_after = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(1))
.unwrap();
assert_source_local_rest_exact(
&child_after.local_rest,
&SourceNodeLocalRest::Trs {
translation: Vec3::new(-48.5, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
);
assert_eq!(
candidate.document().skeleton.bones[1].rest.translation,
Vec3::new(1.5, 0.0, 0.0)
);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.rest_translation.comparisons(), 3);
assert_eq!(proof.unit_scale.comparisons(), 3);
let mut changed_connector = candidate.document().clone();
let SourceNodeLocalRest::Trs { translation, .. } = &mut changed_connector
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.source_node_index == 10)
.unwrap()
.local_rest
else {
panic!("fixture connector changed representation");
};
assert_eq!(translation.y.to_bits(), (-0.0f32).to_bits());
translation.y = 0.0;
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate::from_document(changed_connector),
&plan,
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "connector_source_local_mismatch"
}
);
let mut changed_quaternion_sign = candidate.document().clone();
let SourceNodeLocalRest::Trs { rotation, .. } = &mut changed_quaternion_sign
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.source_node_index == 10)
.unwrap()
.local_rest
else {
panic!("fixture connector changed representation");
};
assert_eq!(rotation.w.to_bits(), (-1.0f32).to_bits());
*rotation = Quat::IDENTITY;
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate::from_document(changed_quaternion_sign),
&plan,
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "connector_source_local_mismatch"
}
);
let mut changed_successor = candidate.document().clone();
let SourceNodeLocalRest::Trs { translation, .. } = &mut changed_successor
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest
else {
panic!("fixture successor changed representation");
};
assert_eq!(translation.z, 0.0);
translation.z = f32::from_bits(translation.z.to_bits() ^ 0x8000_0000);
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate::from_document(changed_successor),
&plan,
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "bridged_source_local_mismatch"
}
);
let mut adjacent_successor = candidate.document().clone();
let SourceNodeLocalRest::Trs { translation, .. } = &mut adjacent_successor
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest
else {
panic!("fixture successor changed representation");
};
translation.x = f32::from_bits(translation.x.to_bits() + 1);
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate::from_document(adjacent_successor),
&plan,
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "bridged_source_local_mismatch"
}
);
let mut changed_successor_rotation = candidate.document().clone();
let SourceNodeLocalRest::Trs { rotation, .. } = &mut changed_successor_rotation
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest
else {
panic!("fixture successor changed representation");
};
*rotation = Quat::from_xyzw(0.0, 0.0, 0.0, -1.0);
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate::from_document(changed_successor_rotation),
&plan,
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "bridged_source_local_mismatch"
}
);
}
#[test]
fn rest_bind_composes_a_connector_between_two_non_root_projected_joints() {
let mut doc = compensated_document();
let connector_before = SourceNodeLocalRest::Trs {
translation: Vec3::new(50.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
let mut connector = SourceNodeAsset::new(10, connector_before.clone());
connector.parent_source_node_index = Some(1);
doc.assets.source_skeleton.nodes.push(connector);
let successor_before = SourceNodeLocalRest::Trs {
translation: Vec3::new(-49.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
let successor = doc
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(2))
.unwrap();
successor.parent_source_node_index = Some(10);
successor.local_rest = successor_before.clone();
doc.assets.source_skeleton.skins[0]
.joint_source_node_indices
.push(2);
doc.assets.instances[0].skin_joints.push(2);
let bone_two_world = doc.skeleton.bones[..=2]
.iter()
.fold(Mat4::IDENTITY, |world, bone| {
world
* Mat4::from_scale_rotation_translation(
bone.rest.scale,
bone.rest.rotation,
bone.rest.translation,
)
});
doc.assets.instances[0]
.skin_ibms
.push(bone_two_world.inverse());
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let source_nodes = &candidate.document().assets.source_skeleton.nodes;
assert_source_local_rest_exact(
&source_nodes
.iter()
.find(|node| node.source_node_index == 10)
.unwrap()
.local_rest,
&connector_before,
);
assert_source_local_rest_exact(
&source_nodes
.iter()
.find(|node| node.bone == Some(2))
.unwrap()
.local_rest,
&SourceNodeLocalRest::Trs {
translation: Vec3::new(-49.0, 0.0, 0.0) * 0.01 + Vec3::new(-49.5, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
);
assert_eq!(
candidate.document().skeleton.bones[2].rest.translation,
Vec3::new(0.01, 0.0, 0.0)
);
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn rest_bind_multiplies_three_unchanged_connectors_in_parent_to_child_order() {
let h1 = matrix_with_columns(
Vec4::new(0.0, 2.0, 0.0, 0.0),
Vec4::new(-2.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 2.0, 0.0),
Vec3::new(10.0, 0.0, 0.0),
);
let h2 = matrix_with_columns(
Vec4::new(0.0, -0.5, 0.0, 0.0),
Vec4::new(0.5, 0.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 0.5, 0.0),
Vec3::new(0.0, 20.0, 0.0),
);
let h3 = Mat4::from_translation(Vec3::new(5.0, 0.0, 0.0));
let raw_child = Transform {
translation: Vec3::new(25.0, 40.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
let normalized_child = Transform {
translation: Vec3::new(0.0, 40.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
let doc = compensated_document_with_connectors(&[h1, h2, h3], raw_child, normalized_child);
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
for (source, expected) in [(10, h1), (11, h2), (12, h3)] {
let connector_after = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.source_node_index == source)
.unwrap();
assert_source_local_rest_exact(
&connector_after.local_rest,
&SourceNodeLocalRest::Matrix(expected),
);
}
let child_after = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(1))
.unwrap();
assert_source_local_rest_exact(
&child_after.local_rest,
&SourceNodeLocalRest::Trs {
translation: Vec3::new(25.0, 0.39999998, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
);
assert_eq!(
candidate.document().skeleton.bones[1].rest.translation,
Vec3::new(0.0, 0.39999998, 0.0)
);
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn compensated_connector_product_is_widened_before_the_f32_candidate_boundary() {
let huge = Mat4::from_scale(Vec3::splat(1e20));
let raw_child = Transform {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(1e-40),
};
let doc = compensated_document_with_connectors(&[huge, huge], raw_child, Transform::default());
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let successor = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(1))
.unwrap();
assert_source_local_rest_exact(
&successor.local_rest,
&SourceNodeLocalRest::Trs {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(1e-40),
},
);
prove_scale(&doc, &candidate, &plan).unwrap();
let normalized_plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: candidate.document(),
capability: &capability,
})
.unwrap();
let normalized_candidate =
build_scale_candidate(candidate.document(), &normalized_plan).unwrap();
prove_scale(
candidate.document(),
&normalized_candidate,
&normalized_plan,
)
.unwrap();
}
#[test]
fn compensated_connector_translation_is_widened_before_the_f32_candidate_boundary() {
let connector_scale = Mat4::from_scale(Vec3::splat(1e20));
let connector_translation = Mat4::from_translation(Vec3::new(1e20, -2e20, 3e20));
let raw_child = Transform {
translation: Vec3::new(-1e20, 2e20, -3e20),
rotation: Quat::IDENTITY,
scale: Vec3::splat(1e-20),
};
let doc = compensated_document_with_connectors(
&[connector_scale, connector_translation],
raw_child,
Transform::default(),
);
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let successor = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(1))
.unwrap();
let SourceNodeLocalRest::Trs { translation, .. } = &successor.local_rest else {
panic!("fixture successor changed representation");
};
assert_eq!(translation.x.to_bits(), (-1e20f32).to_bits());
assert_eq!(translation.y.to_bits(), 2e20f32.to_bits());
assert_eq!(translation.z.to_bits(), (-3e20f32).to_bits());
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn bridged_translation_terms_are_combined_before_the_f32_candidate_boundary() {
let factor = 128.0;
let connector_scale = f32::from_bits(0x3c00_0000); let magnitude = f32::from_bits(0x7b00_0000); let connector_translation = Vec3::new(-magnitude, magnitude, -magnitude);
let authored_successor_translation =
Vec3::new(magnitude * 129.0, -magnitude * 129.0, magnitude * 130.0);
let normalized_successor_translation = Vec3::new(
f32::from_bits(0x7780_0000), f32::from_bits(0xf780_0000), f32::from_bits(0x7800_0000), );
let expected_successor_translation = Vec3::new(
f32::from_bits(0x7f00_0000), f32::from_bits(0xff00_0000), f32::from_bits(0x7f40_0000), );
let connector = Mat4::from_scale_rotation_translation(
Vec3::splat(connector_scale),
Quat::IDENTITY,
connector_translation,
);
let authored_successor = Transform {
translation: authored_successor_translation,
rotation: Quat::IDENTITY,
scale: Vec3::splat(factor),
};
let normalized_successor = Transform {
translation: normalized_successor_translation,
..Transform::default()
};
let expected_successor = Transform {
translation: expected_successor_translation,
..authored_successor
};
for matrix_successor in [false, true] {
let mut doc = compensated_document_with_connectors(
&[connector],
authored_successor,
normalized_successor,
);
doc.skeleton.bones[0].rest.scale = Vec3::splat(factor);
let SourceNodeLocalRest::Trs { scale, .. } = &mut doc
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(0))
.unwrap()
.local_rest
else {
panic!("fixture root changed representation");
};
*scale = Vec3::splat(factor);
doc.assets.instances[0].skin_ibms[0] = Mat4::from_scale_rotation_translation(
Vec3::splat(connector_scale),
Quat::IDENTITY,
-normalized_successor_translation,
);
let expected_successor = if matrix_successor {
SourceNodeLocalRest::Matrix(Mat4::from_scale_rotation_translation(
expected_successor.scale,
expected_successor.rotation,
expected_successor.translation,
))
} else {
SourceNodeLocalRest::Trs {
translation: expected_successor.translation,
rotation: expected_successor.rotation,
scale: expected_successor.scale,
}
};
if matrix_successor {
doc.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest = SourceNodeLocalRest::Matrix(authored_successor.to_mat4());
}
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: f64::from(factor),
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let successor = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(1))
.unwrap();
assert_source_local_rest_exact(&successor.local_rest, &expected_successor);
prove_scale(&doc, &candidate, &plan).unwrap();
}
}
#[test]
fn bridged_matrix_linear_ratio_is_combined_before_the_f32_candidate_boundary() {
let factor = 128.0;
let connector_scale = f32::from_bits(0x0100_0000); let successor_scale = f32::from_bits(0x7e00_0000); let authored_successor = Transform {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(successor_scale),
};
let authored_successor_matrix = authored_successor.to_mat4();
let mut doc = compensated_document_with_connectors(
&[Mat4::from_scale(Vec3::splat(connector_scale))],
authored_successor,
Transform::default(),
);
doc.skeleton.bones[0].rest.scale = Vec3::splat(factor);
let SourceNodeLocalRest::Trs { scale, .. } = &mut doc
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(0))
.unwrap()
.local_rest
else {
panic!("fixture root changed representation");
};
*scale = Vec3::splat(factor);
doc.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest = SourceNodeLocalRest::Matrix(authored_successor_matrix);
doc.assets.instances[0].skin_ibms[0] = Mat4::from_scale(Vec3::splat(1.0 / factor));
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: f64::from(factor),
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let successor = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(1))
.unwrap();
assert_source_local_rest_exact(
&successor.local_rest,
&SourceNodeLocalRest::Matrix(authored_successor_matrix),
);
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn planning_composes_connector_translation_into_the_raw_source_world() {
let connector_scale = Mat4::from_scale(Vec3::splat(1e20));
let connector_translation = Mat4::from_translation(Vec3::splat(f32::MAX));
let doc = compensated_document_with_connectors(
&[connector_scale, connector_translation],
Transform {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(1e-20),
},
Transform::default(),
);
let capability = complete_capability();
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap_err(),
ScaleError::NonFiniteSourceTransform {
source_node_index: 1
}
);
}
#[test]
fn rest_bind_classifies_projected_endpoints_not_nonuniform_connector_rows() {
let connector = Mat4::from_scale(Vec3::new(2.0, 3.0, 4.0));
let raw_child = Transform {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::new(0.5, 1.0 / 3.0, 0.25),
};
let normalized_child = Transform::default();
let mut doc = compensated_document_with_connectors(&[connector], raw_child, normalized_child);
doc.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.source_node_index == 10)
.unwrap()
.local_rest = SourceNodeLocalRest::Trs {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::new(2.0, 3.0, 4.0),
};
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn rest_bind_refuses_a_projective_unprojected_connector() {
let projective =
matrix_with_columns(Vec4::new(1.0, 0.0, 0.0, 0.25), Vec4::Y, Vec4::Z, Vec3::ZERO);
let rest = Transform::default();
let doc = compensated_document_with_connectors(&[projective], rest, rest);
let capability = complete_capability();
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap_err(),
ScaleError::IncompleteClosure {
reason: "non_affine_connector_source_transform"
}
);
}
#[test]
fn bridged_matrix_successor_preserves_linear_and_homogeneous_bits() {
let connector = Mat4::from_translation(Vec3::new(50.0, 0.0, 0.0));
let raw_child_matrix = Mat4::from_cols(
Vec4::X,
Vec4::new(0.0, 1.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0, 0.0),
Vec4::new(100.0, 0.0, 0.0, 1.0),
);
let mut doc = compensated_document_with_connectors(
&[connector],
Transform {
translation: Vec3::new(100.0, 0.0, 0.0),
..Transform::default()
},
Transform {
translation: Vec3::new(150.0, 0.0, 0.0),
..Transform::default()
},
);
doc.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest = SourceNodeLocalRest::Matrix(raw_child_matrix);
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let SourceNodeLocalRest::Matrix(rebased) = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest
else {
panic!("matrix successor changed representation");
};
assert_eq!(rebased.x_axis, raw_child_matrix.x_axis);
assert_eq!(rebased.y_axis, raw_child_matrix.y_axis);
assert_eq!(rebased.z_axis, raw_child_matrix.z_axis);
assert_eq!(
Vec4::new(
rebased.x_axis.w,
rebased.y_axis.w,
rebased.z_axis.w,
rebased.w_axis.w,
),
Vec4::new(0.0, 0.0, 0.0, 1.0)
);
assert_eq!(rebased.w_axis.truncate(), Vec3::new(-48.5, 0.0, 0.0));
prove_scale(&doc, &candidate, &plan).unwrap();
let mut changed_successor_linear = candidate.document().clone();
let SourceNodeLocalRest::Matrix(matrix) = &mut changed_successor_linear
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest
else {
panic!("matrix successor changed representation");
};
assert_eq!(matrix.x_axis.y.to_bits(), 0.0f32.to_bits());
matrix.x_axis.y = -0.0;
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate::from_document(changed_successor_linear),
&plan,
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "bridged_source_local_mismatch"
}
);
}
#[test]
fn connector_bridge_factor_one_is_a_public_bit_exact_no_op() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let connector_before = SourceNodeLocalRest::Trs {
translation: Vec3::new(50.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
let mut connector = SourceNodeAsset::new(10, connector_before.clone());
connector.parent_source_node_index = Some(0);
doc.assets.source_skeleton.nodes.push(connector);
let child_before = SourceNodeLocalRest::Trs {
translation: Vec3::new(-50.0, 1.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
let child = doc
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap();
child.parent_source_node_index = Some(10);
child.local_rest = child_before.clone();
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let source_nodes = &candidate.document().assets.source_skeleton.nodes;
assert_source_local_rest_exact(
&source_nodes
.iter()
.find(|node| node.source_node_index == 10)
.unwrap()
.local_rest,
&connector_before,
);
assert_source_local_rest_exact(
&source_nodes
.iter()
.find(|node| node.bone == Some(1))
.unwrap()
.local_rest,
&child_before,
);
assert_eq!(
candidate.document().skeleton.bones[1].rest.translation,
Vec3::new(0.0, 1.0, 0.0)
);
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn a_finite_but_unrepresentable_connector_rebase_is_atomic() {
let connector = Mat4::from_scale_rotation_translation(
Vec3::splat(1e-20),
Quat::IDENTITY,
Vec3::splat(1e20),
);
let doc = compensated_document_with_connectors(
&[connector],
Transform {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(1e20),
},
Transform {
translation: Vec3::splat(1e20),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
);
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
assert_eq!(
build_scale_candidate(&doc, &plan).unwrap_err(),
ScaleError::NonFiniteTransform { node: 1 }
);
assert_source_local_rest_exact(
&doc.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.source_node_index == 10)
.unwrap()
.local_rest,
&SourceNodeLocalRest::Matrix(connector),
);
assert_eq!(doc.skeleton.bones[1].rest.translation, Vec3::splat(1e20));
}
#[test]
fn a_shared_connector_chain_plans_builds_and_proves_through_public_apis() {
const CONNECTORS: usize = 64;
const PROJECTED_CHILDREN: usize = 64;
let normalized_child_translation = Vec3::new(150.0, 0.0, 0.0);
let raw_child_translation = Vec3::new(100.0, 0.0, 0.0);
let expected_child_translation = Vec3::new(-48.5, 0.0, 0.0);
let mut nodes = vec![RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
}];
nodes.extend(
(1..=PROJECTED_CHILDREN).map(|source| rig(Some(0), source, normalized_child_translation)),
);
let skin_bones: Vec<BoneId> = (1..=PROJECTED_CHILDREN).collect();
let child_world =
Mat4::from_scale(Vec3::splat(0.01)) * Mat4::from_translation(normalized_child_translation);
let mut doc = rig_document(&nodes, &skin_bones, 0, child_world.inverse());
for offset in 0..CONNECTORS {
let source = 100 + offset;
let matrix = if offset == 0 {
Mat4::from_translation(Vec3::new(50.0, 0.0, 0.0))
} else {
Mat4::IDENTITY
};
let mut connector = SourceNodeAsset::new(source, SourceNodeLocalRest::Matrix(matrix));
connector.parent_source_node_index = Some(if offset == 0 { 0 } else { source - 1 });
doc.assets.source_skeleton.nodes.push(connector);
}
let tail = 100 + CONNECTORS - 1;
for child in doc
.assets
.source_skeleton
.nodes
.iter_mut()
.filter(|node| node.bone.is_some_and(|bone| bone != 0))
{
child.parent_source_node_index = Some(tail);
child.local_rest = SourceNodeLocalRest::Trs {
translation: raw_child_translation,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
}
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap();
assert_eq!(plan.affected_nodes().len(), 1 + PROJECTED_CHILDREN);
let domain = derive_rest_bind_plan_domain(&doc, 0, 0).unwrap();
assert!(
domain.ancestry_steps <= CONNECTORS + 1,
"shared connector ancestry was walked {} times",
domain.ancestry_steps
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
for source in 100..100 + CONNECTORS {
let before = doc
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.source_node_index == source)
.unwrap();
let after = candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.source_node_index == source)
.unwrap();
assert_source_local_rest_exact(&after.local_rest, &before.local_rest);
}
for child in candidate
.document()
.assets
.source_skeleton
.nodes
.iter()
.filter(|node| node.bone.is_some_and(|bone| bone != 0))
{
assert_source_local_rest_exact(
&child.local_rest,
&SourceNodeLocalRest::Trs {
translation: expected_child_translation,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
);
}
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn a_stale_plan_cannot_replay_across_connector_topology_changes() {
let identity = Mat4::IDENTITY;
let rest = Transform::default();
let doc = compensated_document_with_connectors(&[identity, identity], rest, rest);
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let mut reordered = doc.clone();
reordered
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.source_node_index == 10)
.unwrap()
.parent_source_node_index = Some(11);
reordered
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.source_node_index == 11)
.unwrap()
.parent_source_node_index = Some(0);
reordered
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.parent_source_node_index = Some(10);
assert_eq!(
build_scale_candidate(&reordered, &plan).unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "affected_source_topology_mismatch"
}
);
assert_eq!(
prove_scale(
&reordered,
&ScaleCandidate::from_document(reordered.clone()),
&plan,
)
.unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "affected_source_topology_mismatch"
}
);
let mut changed = doc.clone();
changed.assets.source_skeleton.nodes.push(SourceNodeAsset {
source_node_index: 12,
name: None,
parent_source_node_index: Some(11),
scene_root_indices: Vec::new(),
local_rest: SourceNodeLocalRest::Matrix(Mat4::IDENTITY),
bone: None,
});
changed
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.parent_source_node_index = Some(12);
assert_eq!(
build_scale_candidate(&changed, &plan).unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "affected_source_topology_mismatch"
}
);
assert_eq!(
prove_scale(
&changed,
&ScaleCandidate::from_document(changed.clone()),
&plan,
)
.unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "affected_source_topology_mismatch"
}
);
}
#[test]
fn a_replayed_plan_allows_connector_numeric_changes_when_topology_is_identical() {
let rest = Transform::default();
let doc = compensated_document_with_connectors(&[Mat4::IDENTITY], rest, rest);
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let mut numerically_changed = doc.clone();
numerically_changed
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.source_node_index == 10)
.unwrap()
.local_rest = SourceNodeLocalRest::Matrix(Mat4::from_translation(Vec3::new(5.0, 0.0, 0.0)));
let candidate = build_scale_candidate(&numerically_changed, &plan).unwrap();
prove_scale(&numerically_changed, &candidate, &plan).unwrap();
}
#[test]
fn compensated_inherited_scale_reparameterizes_and_preserves_world_geometry() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
assert_eq!(plan.transform_only_attachments(), &[2]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let bones = &candidate.document().skeleton.bones;
assert!((bones[0].rest.scale - Vec3::ONE).length() < 1e-6);
assert!((bones[1].rest.translation - Vec3::new(0.0, 1.0, 0.0)).length() < 1e-6);
assert!((bones[2].rest.translation - Vec3::new(0.01, 0.0, 0.0)).length() < 1e-6);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert!(proof.rest_translation.max() < 1e-4);
assert!(proof.unit_scale.max() < 1e-4);
assert!(proof.transform_only_affine.max() < 1e-4);
assert!(proof.skin_matrix.max() < 1e-3);
}
#[test]
fn a_stale_no_op_candidate_for_the_transform_only_attachment_fails_proof() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.skeleton.bones[2].rest.translation = Vec3::new(1.0, 0.0, 0.0);
let broken = ScaleCandidate { document: broken };
assert!(prove_scale(&doc, &broken, &plan).is_err());
}
fn branching_rig() -> Vec<RigNode> {
vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
},
rig(Some(0), 1, Vec3::new(0.0, 100.0, 0.0)),
rig(Some(0), 2, Vec3::new(100.0, 0.0, 0.0)),
rig(Some(1), 3, Vec3::new(0.0, 0.0, 100.0)),
rig(Some(1), 4, Vec3::new(0.0, 50.0, 0.0)),
rig(Some(2), 5, Vec3::new(0.0, 0.0, 50.0)),
]
}
fn branching_document() -> Document {
let ibm = Mat4::from_cols(
Vec4::new(100.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, 100.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 100.0, 0.0),
Vec4::new(0.0, -100.0, 0.0, 1.0),
);
let mut doc = rig_document(&branching_rig(), &[1], 0, ibm);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 4,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![Vec3::new(0.0, 50.0, 0.0), Vec3::new(0.0, 60.0, 0.0)]),
}],
});
doc
}
#[test]
fn a_branching_affected_closure_pulls_in_every_child_at_every_depth() {
let doc = branching_document();
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap();
assert_eq!(plan.affected_nodes(), &[0, 1, 2, 3, 4, 5]);
assert_eq!(plan.transform_only_attachments(), &[2, 3, 4, 5]);
assert_eq!(plan.common_factor(), 0.01);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let bones = &candidate.document().skeleton.bones;
let expected_local = [
Vec3::ZERO,
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
Vec3::new(0.0, 0.5, 0.0),
Vec3::new(0.0, 0.0, 0.5),
];
for (node, expected) in expected_local.iter().enumerate() {
assert!(
(bones[node].rest.translation - *expected).length() < 1e-6,
"bone {node} translation {:?}",
bones[node].rest.translation
);
assert!(
(bones[node].rest.scale - Vec3::ONE).length() < 1e-6,
"bone {node} scale {:?}",
bones[node].rest.scale
);
}
let TrackValues::Vec3s(values) = &candidate.document().clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
assert!((values[0] - Vec3::new(0.0, 0.5, 0.0)).length() < 1e-6);
assert!((values[1] - Vec3::new(0.0, 0.6, 0.0)).length() < 1e-6);
let rebased_ibm = candidate.document().assets.instances[0].skin_ibms[0];
assert!(
rebased_ibm.abs_diff_eq(
Mat4::from_cols(
Vec4::new(1.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, 1.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0, 0.0),
Vec4::new(0.0, -1.0, 0.0, 1.0),
),
1e-6
),
"rebased inverse bind {rebased_ibm:?}"
);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 2);
assert!(proof.rest_translation.max() < 1e-6);
assert!(proof.unit_scale.max() < 1e-6);
assert!(proof.transform_only_affine.max() < 1e-6);
assert!(proof.trajectory.max() < 1e-6);
assert!(proof.key_translation.max() < 1e-6);
assert!(proof.skin_matrix.max() < 1e-4);
assert!(proof.bounds.max() < 1e-6);
}
fn parent_boundary_rig() -> Vec<RigNode> {
vec![
rig(None, 0, Vec3::new(5.0, 0.0, 0.0)),
RigNode {
parent: Some(0),
source_node_index: 1,
translation: Vec3::new(0.0, 2.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
},
rig(Some(1), 2, Vec3::new(0.0, 100.0, 0.0)),
rig(Some(2), 3, Vec3::new(100.0, 0.0, 0.0)),
rig(Some(3), 4, Vec3::new(0.0, 0.0, 200.0)),
]
}
fn parent_boundary_document() -> Document {
let ibm = Mat4::from_scale_rotation_translation(
Vec3::splat(100.0),
Quat::IDENTITY,
Vec3::new(-500.0, -300.0, 0.0),
);
let mut doc = rig_document(&parent_boundary_rig(), &[2], 0, ibm);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![Vec3::new(0.0, 2.0, 0.0), Vec3::new(0.0, 4.0, 0.0)]),
}],
});
doc
}
#[test]
fn a_scaled_root_whose_parent_is_outside_the_closure_keeps_its_own_translation_basis() {
let doc = parent_boundary_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 1,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
assert_eq!(plan.affected_nodes(), &[1, 2, 3, 4]);
assert_eq!(plan.transform_only_attachments(), &[3, 4]);
assert_eq!(plan.common_factor(), 0.01);
assert_eq!(plan.observed_factor(), NEAR_UNIT_OBSERVED_FACTOR);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let bones = &candidate.document().skeleton.bones;
assert_eq!(bones[0].rest.translation, Vec3::new(5.0, 0.0, 0.0));
assert_eq!(bones[0].rest.scale, Vec3::ONE);
assert!((bones[1].rest.translation - Vec3::new(0.0, 2.0, 0.0)).length() < 1e-9);
assert!((bones[2].rest.translation - Vec3::new(0.0, 1.0, 0.0)).length() < 1e-6);
assert!((bones[3].rest.translation - Vec3::new(1.0, 0.0, 0.0)).length() < 1e-6);
assert!((bones[4].rest.translation - Vec3::new(0.0, 0.0, 2.0)).length() < 1e-6);
for (id, bone) in bones.iter().enumerate().skip(1) {
assert!(
(bone.rest.scale - Vec3::ONE).length() < 1e-6,
"bone {id} local scale {:?}",
bone.rest.scale
);
}
let source_nodes = &candidate.document().assets.source_skeleton.nodes;
let expected_projection = [
(0, Vec3::new(5.0, 0.0, 0.0), Vec3::ONE),
(1, Vec3::new(0.0, 2.0, 0.0), Vec3::ONE),
(2, Vec3::new(0.0, 1.0, 0.0), Vec3::ONE),
(3, Vec3::new(1.0, 0.0, 0.0), Vec3::ONE),
(4, Vec3::new(0.0, 0.0, 2.0), Vec3::ONE),
];
for (index, expected_translation, expected_scale) in expected_projection {
let SourceNodeLocalRest::Trs {
translation, scale, ..
} = &source_nodes[index].local_rest
else {
panic!("expected a trs source rest");
};
assert!(
(*translation - expected_translation).length() < 1e-6,
"source node {index} translation {translation:?}"
);
assert!(
(*scale - expected_scale).length() < 1e-6,
"source node {index} scale {scale:?}"
);
}
let TrackValues::Vec3s(values) = &candidate.document().clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
let expected_values = [Vec3::new(0.0, 2.0, 0.0), Vec3::new(0.0, 4.0, 0.0)];
for (value, expected) in values.iter().zip(expected_values) {
assert!((*value - expected).length() < 1e-9, "track value {value:?}");
}
let binds = &candidate.document().assets.instances[0].skin_ibms;
assert_eq!(binds.len(), 1);
assert!(
binds[0].abs_diff_eq(Mat4::from_translation(Vec3::new(-5.0, -3.0, 0.0)), 1e-5),
"rebased bind {:?}",
binds[0]
);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.observed_factor, NEAR_UNIT_OBSERVED_FACTOR);
assert_eq!(proof.sample_time_count, 2);
assert!(proof.rest_translation.max() < 1e-4);
assert!(proof.unit_scale.max() < 1e-4);
assert!(proof.transform_only_affine.max() < 1e-4);
assert!(proof.track_value.max() < 1e-9);
assert!(proof.trajectory.max() < 1e-4);
assert!(proof.skin_matrix.max() < 1e-4);
assert!(proof.bounds.max() < 1e-4);
}
fn scaled_ancestor_rig() -> Vec<RigNode> {
vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::new(5.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::splat(2.0),
},
RigNode {
parent: Some(0),
source_node_index: 1,
translation: Vec3::new(0.0, 2.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.005),
},
rig(Some(1), 2, Vec3::new(0.0, 100.0, 0.0)),
]
}
fn scaled_ancestor_document() -> Document {
let ibm = Mat4::from_scale_rotation_translation(
Vec3::splat(100.0),
Quat::IDENTITY,
Vec3::new(-500.0, -500.0, 0.0),
);
rig_document(&scaled_ancestor_rig(), &[2], 0, ibm)
}
#[test]
fn the_observed_factor_is_the_scaled_roots_composed_scale_not_its_local_one() {
let doc = scaled_ancestor_document();
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 1,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap();
assert_eq!(plan.affected_nodes(), &[1, 2]);
assert_eq!(plan.common_factor(), 0.01);
assert_eq!(plan.observed_factor(), NEAR_UNIT_OBSERVED_FACTOR);
assert_eq!(
f64::from(doc.skeleton.bones[1].rest.scale.x),
NEAR_UNIT_OBSERVED_FACTOR / 2.0
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert_eq!(
candidate.document().skeleton.bones[1].rest.scale,
Vec3::splat(0.5)
);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.unit_scale.max(), 0.0);
assert_eq!(proof.observed_factor, NEAR_UNIT_OBSERVED_FACTOR);
}
fn reject_case(mutate: impl FnOnce(&mut SourceNodeLocalRest)) -> ScaleError {
let nodes = vec![rig(None, 0, Vec3::ZERO), rig(Some(0), 1, Vec3::ZERO)];
let mut doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
let root = &mut doc.assets.source_skeleton.nodes[0].local_rest;
mutate(root);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
plan_scale(&request).unwrap_err()
}
fn trs_scale(scale: Vec3) -> SourceNodeLocalRest {
SourceNodeLocalRest::Trs {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale,
}
}
#[test]
fn the_equal_axis_band_is_relative_to_the_longer_axis_and_admits_its_own_edge() {
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::new(99_998.5, 99_998.5, 100_000.0),
},
rig(Some(0), 1, Vec3::ZERO),
];
let doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = plan_scale(&declared_factor_request(&doc, &capability, 99_999.0))
.expect("an axis exactly on the equal-axis edge is uniform");
assert_eq!(plan.common_factor(), 99_999.0);
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::new(99_998.5, 99_998.5, 100_000.0 + 0.007_812_5),
},
rig(Some(0), 1, Vec3::ZERO),
];
let doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
assert!(matches!(
plan_scale(&declared_factor_request(&doc, &capability, 99_999.0)).unwrap_err(),
ScaleError::InvalidAffineDomain {
reason: AffineDomainViolation::NonUniformScale,
..
}
));
}
#[test]
fn an_orthogonality_dot_exactly_on_its_tolerance_is_accepted_and_the_next_one_up_is_not() {
let c = 8_388_610.0_f32 * 2.0_f32.powi(-23);
let x1 = 10_995_118.0_f32 * 2.0_f32.powi(-40);
let y0 = 13_826_165.0_f32 * 2.0_f32.powi(-69);
let y0_up = 13_826_166.0_f32 * 2.0_f32.powi(-69);
let y0_down = 13_826_164.0_f32 * 2.0_f32.powi(-69);
let dot = f64::from(x1) + f64::from(y0);
assert_eq!(
(f64::from(x1) + f64::from(y0_up)).to_bits(),
dot.to_bits() + 1
);
assert_eq!(
(f64::from(x1) + f64::from(y0_down)).to_bits(),
dot.to_bits() - 1
);
let basis = |y: f32| {
Mat3::from_cols(
Vec3::new(1.0, y, 0.0),
Vec3::new(x1, 1.0, 0.0),
Vec3::new(0.0, 0.0, c),
)
};
let tol = ScaleTolerancePolicy::APPENDIX_D_V6;
assert!(classify_affine(basis(y0_down), &tol).is_ok());
assert!(classify_affine(basis(y0), &tol).is_ok());
assert_eq!(
classify_affine(basis(y0_up), &tol),
Err(AffineDomainViolation::Sheared)
);
}
#[test]
fn appendix_d_v6_rejects_the_shared_policy_divergence_fixture() {
assert_eq!(
classify_affine(
crate::model::affine_test_fixtures::tolerance_divergence_basis(),
&ScaleTolerancePolicy::APPENDIX_D_V6,
),
Err(AffineDomainViolation::NonUniformScale)
);
}
#[test]
fn affine_adapter_maps_each_named_policy_field_to_its_own_classifier_band() {
let strict_shape_policy = ScaleTolerancePolicy {
equal_axis: 1.0e-4,
relative_orthogonality: 1.0e-5,
singular_determinant_relative: 1.0e-7,
..ScaleTolerancePolicy::APPENDIX_D_V6
};
assert!(
classify_affine(
crate::model::affine_test_fixtures::tolerance_divergence_basis(),
&strict_shape_policy,
)
.is_ok(),
"the loose equal-axis band must reach the equal-axis field"
);
assert_eq!(
classify_affine(
crate::model::affine_test_fixtures::orthogonality_tolerance_divergence_basis(),
&strict_shape_policy,
),
Err(AffineDomainViolation::Sheared),
"the strict orthogonality band must reach the orthogonality field"
);
let loose_orthogonality_policy = ScaleTolerancePolicy {
relative_orthogonality: 1.0e-4,
..strict_shape_policy
};
assert!(
classify_affine(
crate::model::affine_test_fixtures::orthogonality_tolerance_divergence_basis(),
&loose_orthogonality_policy,
)
.is_ok(),
"the loose orthogonality band must reach the orthogonality field"
);
let determinant_boundary_basis =
|determinant: f32| Mat3::from_cols(Vec3::X, Vec3::new(1.0, determinant, 0.0), Vec3::Z);
assert_eq!(
classify_affine(determinant_boundary_basis(5.0e-8), &strict_shape_policy),
Err(AffineDomainViolation::Singular),
"the strict singularity band must reach the singularity field"
);
assert_eq!(
classify_affine(determinant_boundary_basis(5.0e-7), &strict_shape_policy),
Err(AffineDomainViolation::Sheared),
"a determinant beyond the singularity band must reach the later shape check"
);
assert_eq!(
classify_affine(
determinant_boundary_basis(5.0e-7),
&ScaleTolerancePolicy::APPENDIX_D_V6,
),
Err(AffineDomainViolation::Singular),
"the production singularity band must differ from the strict test policy"
);
}
#[test]
fn nonuniform_scale_domain_rejects() {
let error = reject_case(|rest| *rest = trs_scale(Vec3::new(0.01, 0.02, 0.01)));
assert!(matches!(
error,
ScaleError::InvalidAffineDomain {
reason: AffineDomainViolation::NonUniformScale,
..
}
));
}
#[test]
fn rest_bind_planning_refuses_every_appendix_d_v6_mean_permutation() {
for (permutation, linear) in
crate::model::affine_test_fixtures::appendix_d_v6_mean_permutations()
.into_iter()
.enumerate()
{
let error = reject_case(|rest| {
*rest = SourceNodeLocalRest::Matrix(Mat4::from_cols(
linear.x_axis.extend(0.0),
linear.y_axis.extend(0.0),
linear.z_axis.extend(0.0),
Vec4::W,
));
});
assert_eq!(
error,
ScaleError::InvalidAffineDomain {
node: 0,
reason: AffineDomainViolation::NonUniformScale,
},
"orientation-preserving permutation {permutation}"
);
}
}
#[test]
fn literal_shear_via_a_raw_matrix_fixture_rejects() {
let angle = 80f32.to_radians();
let error = reject_case(|rest| {
*rest = SourceNodeLocalRest::Matrix(Mat4::from_cols_array(&[
1.0,
0.0,
0.0,
0.0,
angle.cos(),
angle.sin(),
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
]));
});
assert!(matches!(
error,
ScaleError::InvalidAffineDomain {
reason: AffineDomainViolation::Sheared,
..
}
));
}
#[test]
fn reflected_domain_rejects() {
let error = reject_case(|rest| *rest = trs_scale(Vec3::new(-0.01, 0.01, 0.01)));
assert!(matches!(
error,
ScaleError::InvalidAffineDomain {
reason: AffineDomainViolation::Reflected,
..
}
));
}
#[test]
fn singular_domain_rejects() {
let error = reject_case(|rest| *rest = trs_scale(Vec3::new(0.0, 0.01, 0.01)));
assert!(matches!(
error,
ScaleError::InvalidAffineDomain {
reason: AffineDomainViolation::Singular,
..
}
));
}
#[test]
fn near_singular_domain_rejects() {
let eps = 1e-8f32;
let error = reject_case(|rest| {
*rest = SourceNodeLocalRest::Matrix(Mat4::from_cols_array(&[
1.0,
0.0,
0.0,
0.0,
eps.cos(),
eps.sin(),
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
]));
});
assert!(matches!(
error,
ScaleError::InvalidAffineDomain {
reason: AffineDomainViolation::Singular,
..
}
));
}
#[test]
fn nonfinite_domain_rejects() {
let error = reject_case(|rest| *rest = trs_scale(Vec3::new(f32::NAN, 0.01, 0.01)));
assert!(matches!(
error,
ScaleError::NonFiniteSourceTransform {
source_node_index: 0
}
));
}
#[test]
fn mixed_factor_within_domain_rejects() {
let nodes = vec![rig(None, 0, Vec3::ZERO), rig(Some(0), 1, Vec3::ZERO)];
let mut doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
doc.assets.source_skeleton.nodes[0].local_rest = trs_scale(Vec3::splat(0.01));
doc.assets.source_skeleton.nodes[1].local_rest = trs_scale(Vec3::splat(0.02));
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&request).unwrap_err(),
ScaleError::MixedFactor { .. }
));
}
fn noisy_factor_document(scale: f32) -> Document {
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(scale),
},
rig(Some(0), 1, Vec3::ZERO),
];
rig_document(&nodes, &[1], 0, Mat4::IDENTITY)
}
fn noisy_factor_request<'a>(
document: &'a Document,
capability: &'a ScaleCapabilityFacts,
) -> ScaleRequest<'a> {
ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document,
capability,
}
}
#[test]
fn noisy_but_within_tolerance_factor_is_accepted_and_just_outside_is_not() {
for (scale, should_accept) in [(0.010_000_099_f32, true), (0.010_000_1_f32, false)] {
let doc = noisy_factor_document(scale);
let capability = complete_capability();
let request = noisy_factor_request(&doc, &capability);
let planned = plan_scale(&request);
assert_eq!(
planned.is_ok(),
should_accept,
"scale {scale} accepted={should_accept}"
);
if !should_accept {
assert!(matches!(
planned.unwrap_err(),
ScaleError::FactorMismatch { .. }
));
}
}
}
fn declared_factor_request<'a>(
document: &'a Document,
capability: &'a ScaleCapabilityFacts,
expected_factor: f64,
) -> ScaleRequest<'a> {
ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor,
},
document,
capability,
}
}
#[test]
fn the_common_factor_band_is_relative_to_the_larger_operand_whichever_it_is() {
let capability = complete_capability();
for (observed, declared) in [(99_999.0f32, 100_000.0f64), (100_000.0f32, 99_999.0f64)] {
let doc = noisy_factor_document(observed);
let request = declared_factor_request(&doc, &capability, declared);
let plan = plan_scale(&request).unwrap_or_else(|error| {
panic!("observed {observed} declared {declared} must plan, got {error:?}")
});
assert_eq!(plan.common_factor(), declared);
assert_eq!((f64::from(observed) - declared).abs(), 1.0);
}
let doc = noisy_factor_document(99_999.0);
let outside =
declared_factor_request(&doc, &capability, f64::from_bits(100_000f64.to_bits() + 1));
assert!(matches!(
plan_scale(&outside).unwrap_err(),
ScaleError::FactorMismatch { .. }
));
}
#[test]
fn a_noisy_factor_plan_scale_accepts_still_satisfies_its_own_proof_postcondition() {
let doc = noisy_factor_document(0.010_000_02);
let capability = complete_capability();
let request = noisy_factor_request(&doc, &capability);
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(
proof.unit_scale.max(),
17.0 * 2f64.powi(-23),
"unit scale residual {}",
proof.unit_scale.max()
);
}
const NOISY_OBSERVED_FACTOR: f64 = 0.010_000_020_265_579_224;
const NEAR_UNIT_OBSERVED_FACTOR: f64 = 0.009_999_999_776_482_582;
#[test]
fn a_rest_bind_plan_and_its_proof_both_report_the_observed_factor_and_the_declared_one() {
let doc = noisy_factor_document(0.010_000_02);
let capability = complete_capability();
let plan = plan_scale(&noisy_factor_request(&doc, &capability)).unwrap();
assert_eq!(plan.common_factor(), 0.01);
assert_eq!(plan.observed_factor(), NOISY_OBSERVED_FACTOR);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.observed_factor, NOISY_OBSERVED_FACTOR);
}
#[test]
fn the_proof_re_derives_the_observed_factor_from_its_own_source_not_from_the_plan() {
let planned = noisy_factor_document(0.010_000_02);
let proved = noisy_factor_document(0.01);
let capability = complete_capability();
let plan = plan_scale(&noisy_factor_request(&planned, &capability)).unwrap();
assert_eq!(plan.observed_factor(), NOISY_OBSERVED_FACTOR);
let candidate = build_scale_candidate(&proved, &plan).unwrap();
let proof = prove_scale(&proved, &candidate, &plan).unwrap();
assert_eq!(proof.observed_factor, NEAR_UNIT_OBSERVED_FACTOR);
assert_eq!(proof.unit_scale.max(), 0.0);
}
#[test]
fn the_evidence_record_carries_both_observed_factors_and_the_divergence_between_them() {
let capability = complete_capability();
let consistent = noisy_factor_document(0.010_000_02);
let plan = plan_scale(&noisy_factor_request(&consistent, &capability)).unwrap();
let candidate = build_scale_candidate(&consistent, &plan).unwrap();
let proof = prove_scale(&consistent, &candidate, &plan).unwrap();
assert_eq!(proof.planned_observed_factor, NOISY_OBSERVED_FACTOR);
assert_eq!(proof.observed_factor, NOISY_OBSERVED_FACTOR);
assert_eq!(proof.observed_factor_divergence, 0.0);
let proved = noisy_factor_document(0.01);
let candidate = build_scale_candidate(&proved, &plan).unwrap();
let proof = prove_scale(&proved, &candidate, &plan).unwrap();
assert_eq!(proof.planned_observed_factor, NOISY_OBSERVED_FACTOR);
assert_eq!(proof.observed_factor, NEAR_UNIT_OBSERVED_FACTOR);
assert_eq!(proof.observed_factor_divergence, 11.0 / 5_368_720.0);
assert!(
proof.observed_factor_divergence
< plan.tolerance_policy().observed_factor_divergence_ceiling()
);
let swapped_plan = plan_scale(&noisy_factor_request(&proved, &capability)).unwrap();
let swapped_candidate = build_scale_candidate(&consistent, &swapped_plan).unwrap();
let swapped = prove_scale(&consistent, &swapped_candidate, &swapped_plan).unwrap();
assert_eq!(swapped.planned_observed_factor, NEAR_UNIT_OBSERVED_FACTOR);
assert_eq!(swapped.observed_factor, NOISY_OBSERVED_FACTOR);
assert_eq!(swapped.observed_factor_divergence, 11.0 / 5_368_720.0);
}
#[test]
fn the_divergence_ceiling_is_the_sum_of_the_two_bands_that_produce_it() {
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
assert_eq!(policy.common_factor, 1e-5);
assert_eq!(policy.postcondition_unit_scale_residual, 2f64.powi(-14));
assert_eq!(
policy.observed_factor_divergence_ceiling(),
1e-5 + 2f64.powi(-14)
);
assert_eq!(
policy.observed_factor_divergence_ceiling(),
7.103_515_625e-5
);
}
#[test]
fn a_pair_that_nearly_spends_both_bands_leaves_barely_any_ceiling_headroom() {
let planned = noisy_factor_document(0.010_000_099);
let proved = noisy_factor_document(0.009_999_4);
let capability = complete_capability();
let plan = plan_scale(&noisy_factor_request(&planned, &capability)).unwrap();
let candidate = build_scale_candidate(&proved, &plan).unwrap();
let proof = prove_scale(&proved, &candidate, &plan).unwrap();
assert_eq!(proof.unit_scale.max(), 1007.0 * 2f64.powi(-24));
assert_eq!(proof.observed_factor_divergence, 751.0 / 10_737_525.0);
let ceiling = plan.tolerance_policy().observed_factor_divergence_ceiling();
assert!(
proof.observed_factor_divergence < ceiling,
"divergence {} exceeds ceiling {ceiling}",
proof.observed_factor_divergence
);
assert!(ceiling - proof.observed_factor_divergence < 1.1e-6);
}
#[test]
fn a_document_whose_skeleton_and_projection_disagree_is_proved_not_refused() {
let mut doc = noisy_factor_document(0.009_999_9);
doc.skeleton.bones[0].rest.scale = Vec3::splat(0.010_000_611);
let capability = complete_capability();
let plan = plan_scale(&noisy_factor_request(&doc, &capability)).unwrap();
assert_eq!(plan.observed_factor(), 0.009_999_900_124_967_098);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.observed_factor, 0.010_000_610_724_091_53);
assert_eq!(proof.unit_scale.max(), 2f64.powi(-14));
assert_eq!(proof.observed_factor_divergence, 763.0 / 10_738_074.0);
let ceiling = plan.tolerance_policy().observed_factor_divergence_ceiling();
assert!(
proof.observed_factor_divergence > ceiling,
"divergence {} did not exceed ceiling {ceiling}",
proof.observed_factor_divergence
);
}
#[test]
fn a_whole_document_conversion_reports_one_factor_under_both_names() {
let doc = payload_document();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(plan.common_factor(), 0.01);
assert_eq!(proof.planned_observed_factor, 0.01);
assert_eq!(proof.observed_factor, 0.01);
assert_eq!(proof.observed_factor_divergence, 0.0);
}
fn contradictory_parent_chain_document() -> Document {
let mut doc = rig_document(
&[
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
},
rig(Some(0), 1, Vec3::ZERO),
],
&[0],
0,
Mat4::IDENTITY,
);
let nodes = &mut doc.assets.source_skeleton.nodes;
nodes[0].parent_source_node_index = Some(1);
nodes[0].scene_root_indices = Vec::new();
nodes[0].local_rest = SourceNodeLocalRest::Trs {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(1.0 + 2f32.powi(-18)),
};
nodes[1].parent_source_node_index = None;
nodes[1].scene_root_indices = vec![0];
nodes[1].local_rest = SourceNodeLocalRest::Trs {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
};
doc
}
#[test]
fn the_contradictory_parent_chain_documents_false_proof_is_refused() {
let doc = contradictory_parent_chain_document();
let capability = complete_capability();
for source_root_node_index in [0, 1] {
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap_err(),
ScaleError::InvalidDocumentShape(DocumentShapeError::SourceProjection {
source_node_index: 0,
violation: SourceProjectionViolation::NearestProjectedParentMismatch,
})
);
}
}
#[test]
fn a_candidate_whose_parent_chains_disagree_is_refused_by_proof() {
let doc = compensated_document();
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut doctored = candidate.document().clone();
doctored.assets.source_skeleton.nodes[2].parent_source_node_index = Some(0);
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: doctored }, &plan).unwrap_err(),
ScaleError::InvalidDocumentShape(DocumentShapeError::SourceProjection {
source_node_index: 2,
violation: SourceProjectionViolation::NearestProjectedParentMismatch,
})
);
}
#[test]
fn a_projection_that_is_not_complete_coverage_is_not_identity_evidence() {
let capability = complete_capability();
let mut doc = compensated_document();
doc.assets.source_skeleton = SourceSkeletonAssets::default();
assert_eq!(
doc.assets.source_skeleton.coverage,
SourceSkeletonCoverage::Unavailable
);
assert_eq!(doc.skeleton.bones.len(), 3);
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.expect("an unavailable projection beside a populated skeleton still plans");
let mut contradictory = contradictory_parent_chain_document();
contradictory.assets.source_skeleton.coverage = SourceSkeletonCoverage::Unavailable;
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &contradictory,
capability: &capability,
})
.expect("an unavailable projection is not checked against the skeleton");
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &contradictory,
capability: &capability,
})
.unwrap_err(),
ScaleError::IncompleteSourceSkeleton
);
}
fn unprojected_skeleton_child_document() -> Document {
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
},
rig(Some(0), 1, Vec3::new(0.0, 100.0, 0.0)),
rig(Some(0), 2, Vec3::new(5.0, 0.0, 0.0)),
];
let joint_world = Mat4::from_scale_rotation_translation(
Vec3::splat(0.01),
Quat::IDENTITY,
Vec3::new(0.0, 1.0, 0.0),
);
let mut doc = rig_document(&nodes, &[1], 0, joint_world.inverse());
let outside_world = Mat4::from_scale_rotation_translation(
Vec3::splat(0.01),
Quat::IDENTITY,
Vec3::new(0.05, 0.0, 0.0),
);
doc.assets.instances.push(MeshInstance {
source_node_index: 2,
node: 2,
mesh: 0,
skin_joints: vec![2],
skin_ibms: vec![outside_world.inverse()],
});
doc.assets
.source_skeleton
.nodes
.retain(|node| node.source_node_index != 2);
doc
}
#[test]
fn a_bone_the_projection_omits_below_one_it_describes_is_refused() {
let capability = complete_capability();
let doc = unprojected_skeleton_child_document();
let expected = ScaleError::InvalidDocumentShape(DocumentShapeError::SourceProjection {
source_node_index: 0,
violation: SourceProjectionViolation::ProjectedBoneHasUnprojectedSkeletonChild,
});
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.unwrap_err(),
expected
);
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap_err(),
expected
);
}
#[test]
fn a_bone_with_no_projected_ancestor_is_not_a_disagreement() {
let capability = complete_capability();
let mut doc = unprojected_skeleton_child_document();
doc.skeleton.bones[2].parent = None;
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.expect("an unprojected bone with no projected ancestor is not a disagreement");
}
fn projection_root_of_a_skeleton_child_document() -> Document {
let mut doc = unprojected_skeleton_child_document();
let mut evidence = SourceNodeAsset::new(
2,
SourceNodeLocalRest::Trs {
translation: Vec3::new(5.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
);
evidence.bone = Some(2);
evidence.scene_root_indices = vec![0];
doc.assets.source_skeleton.nodes.push(evidence);
doc
}
#[test]
fn a_projection_root_of_a_skeleton_child_is_refused() {
let capability = complete_capability();
let doc = projection_root_of_a_skeleton_child_document();
let expected = ScaleError::InvalidDocumentShape(DocumentShapeError::SourceProjection {
source_node_index: 2,
violation: SourceProjectionViolation::NearestProjectedParentMismatch,
});
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.unwrap_err(),
expected
);
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap_err(),
expected
);
}
#[test]
fn a_container_node_above_the_first_joint_is_not_a_disagreement() {
let capability = complete_capability();
let mut doc = compensated_document();
let mut container = SourceNodeAsset::new(7, SourceNodeLocalRest::Matrix(Mat4::IDENTITY));
container.name = Some("Armature".into());
container.scene_root_indices = vec![0];
doc.assets.source_skeleton.nodes.push(container);
doc.assets.source_skeleton.nodes[0].parent_source_node_index = Some(7);
doc.assets.source_skeleton.nodes[0].scene_root_indices = Vec::new();
assert_eq!(doc.skeleton.bones[0].parent, None);
for operation in [
ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
] {
let plan = plan_scale(&ScaleRequest {
operation,
document: &doc,
capability: &capability,
})
.expect("a container node above the first joint is not a disagreement");
let candidate = build_scale_candidate(&doc, &plan).expect("the rewrite is buildable");
prove_scale(&doc, &candidate, &plan).expect("and the rewrite proves");
}
let mut container = SourceNodeAsset::new(8, SourceNodeLocalRest::Matrix(Mat4::IDENTITY));
container.scene_root_indices = vec![0];
doc.assets.source_skeleton.nodes.push(container);
let count = doc.assets.source_skeleton.nodes.len();
doc.assets.source_skeleton.nodes[count - 2].parent_source_node_index = Some(8);
doc.assets.source_skeleton.nodes[count - 2].scene_root_indices = Vec::new();
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.expect("a chain of container nodes above the first joint is not a disagreement either");
}
#[test]
fn an_unprojected_node_between_two_joints_is_not_a_disagreement() {
let capability = complete_capability();
let mut doc = compensated_document();
let mut middle = SourceNodeAsset::new(5, SourceNodeLocalRest::Matrix(Mat4::IDENTITY));
middle.parent_source_node_index = Some(1);
doc.assets.source_skeleton.nodes.push(middle);
doc.assets.source_skeleton.nodes[2].parent_source_node_index = Some(5);
assert_eq!(doc.skeleton.bones[2].parent, Some(1));
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.expect("an unprojected node between two joints is not a disagreement");
}
#[test]
fn a_source_node_that_never_became_a_bone_is_not_a_disagreement() {
let capability = complete_capability();
let mut doc = compensated_document();
let mut evidence = SourceNodeAsset::new(
3,
SourceNodeLocalRest::Trs {
translation: Vec3::new(7.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
);
evidence.scene_root_indices = vec![0];
assert_eq!(evidence.bone, None);
doc.assets.source_skeleton.nodes.push(evidence);
for operation in [
ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
] {
plan_scale(&ScaleRequest {
operation,
document: &doc,
capability: &capability,
})
.expect("bare source-node evidence is not a chain disagreement");
}
doc.assets.source_skeleton.nodes[3].parent_source_node_index = Some(2);
doc.assets.source_skeleton.nodes[3].scene_root_indices = Vec::new();
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.expect("a whole-document conversion never reads `bone` at all");
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap_err(),
ScaleError::SourceNodeNotNormalized {
source_node_index: 3
}
);
let mut all_unprojected = compensated_document();
for node in &mut all_unprojected.assets.source_skeleton.nodes {
node.bone = None;
}
assert_eq!(all_unprojected.skeleton.bones.len(), 3);
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &all_unprojected,
capability: &capability,
})
.expect("a wholly unprojected complete table beside a populated skeleton still plans");
}
#[test]
fn an_empty_projection_under_complete_coverage_is_accepted() {
let capability = complete_capability();
let mut doc = compensated_document();
doc.assets.source_skeleton.nodes.clear();
assert_eq!(
doc.assets.source_skeleton.coverage,
SourceSkeletonCoverage::Complete
);
assert_eq!(doc.skeleton.bones.len(), 3);
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.expect("an empty projection under complete coverage is not a disagreement");
}
#[test]
fn a_complete_projection_beside_an_empty_skeleton_is_accepted() {
let capability = complete_capability();
let mut doc = Document::default();
let mut root = SourceNodeAsset::new(0, SourceNodeLocalRest::Matrix(Mat4::IDENTITY));
root.scene_root_indices = vec![0];
let mut child = SourceNodeAsset::new(1, SourceNodeLocalRest::Matrix(Mat4::IDENTITY));
child.parent_source_node_index = Some(0);
doc.assets.source_skeleton = SourceSkeletonAssets {
coverage: SourceSkeletonCoverage::Complete,
nodes: vec![root, child],
skins: Vec::new(),
};
assert!(doc.skeleton.bones.is_empty());
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.expect("unprojected source-node evidence beside an empty skeleton still plans");
doc.assets.source_skeleton.nodes[1].bone = Some(0);
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.unwrap_err(),
ScaleError::InvalidDocumentShape(DocumentShapeError::SourceProjection {
source_node_index: 1,
violation: SourceProjectionViolation::ProjectedBoneOutOfRange,
})
);
}
fn per_axis_factor_document(scale: Vec3) -> Document {
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale,
},
rig(Some(0), 1, Vec3::ZERO),
];
rig_document(&nodes, &[1], 0, Mat4::IDENTITY)
}
#[test]
fn the_observed_factor_is_the_mean_of_the_scaled_roots_axes_not_its_first_one() {
let step = 328.0 * 2f32.powi(-30);
let proved = per_axis_factor_document(Vec3::new(0.01 + step, 0.01 - step, 0.01));
let root = proved.skeleton.bones[0].rest.scale;
let ulps = 328.0 * 2f64.powi(-30);
assert_eq!(f64::from(root.x), NEAR_UNIT_OBSERVED_FACTOR + ulps);
assert_eq!(f64::from(root.y), NEAR_UNIT_OBSERVED_FACTOR - ulps);
assert_eq!(f64::from(root.z), NEAR_UNIT_OBSERVED_FACTOR);
let capability = complete_capability();
assert_eq!(
plan_scale(&noisy_factor_request(&proved, &capability)).unwrap_err(),
ScaleError::InvalidAffineDomain {
node: 0,
reason: AffineDomainViolation::NonUniformScale,
}
);
let planned = noisy_factor_document(0.01);
let plan = plan_scale(&noisy_factor_request(&planned, &capability)).unwrap();
let candidate = build_scale_candidate(&proved, &plan).unwrap();
let proof = prove_scale(&proved, &candidate, &plan).unwrap();
assert_eq!(proof.observed_factor, NEAR_UNIT_OBSERVED_FACTOR);
assert_eq!(proof.unit_scale.max(), 513.0 * 2f64.powi(-24));
assert!(proof.unit_scale.max() <= plan.tolerance_policy().postcondition_unit_scale_residual);
}
#[test]
fn observed_factor_from_source_uses_the_canonical_appendix_d_v6_mean() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
})
.expect("the unit document supplies a valid rest/bind plan");
let expected = 0x3ff1_09ef_b555_6f3f;
for (permutation, linear) in
crate::model::affine_test_fixtures::appendix_d_v6_mean_permutations()
.into_iter()
.enumerate()
{
let source_worlds = WorldPose {
bones: vec![WorldBonePose {
matrix: Mat4::from_cols(
linear.x_axis.extend(0.0),
linear.y_axis.extend(0.0),
linear.z_axis.extend(0.0),
Vec4::W,
),
translation_rounding_magnitude: 0.0,
}],
};
assert_eq!(
observed_factor_from_source(&doc, &source_worlds, &plan)
.unwrap()
.to_bits(),
expected,
"orientation-preserving permutation {permutation}"
);
}
}
#[test]
fn a_whole_document_plan_reports_its_declared_factor_as_the_observed_one() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.unwrap();
assert_eq!(plan.common_factor(), 0.01);
assert_eq!(plan.observed_factor(), 0.01);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.observed_factor, 0.01);
}
#[test]
fn a_rest_bind_plan_cannot_be_replayed_against_a_renumbered_selector_domain() {
let doc = compensated_document();
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut unprojected = doc.clone();
unprojected.assets.source_skeleton.nodes[0].source_node_index = 7;
unprojected.assets.source_skeleton.nodes[1].parent_source_node_index = Some(7);
let mut unprojected_candidate = candidate.into_document();
unprojected_candidate.assets.source_skeleton.nodes[0].source_node_index = 7;
unprojected_candidate.assets.source_skeleton.nodes[1].parent_source_node_index = Some(7);
assert_eq!(
prove_scale(
&unprojected,
&ScaleCandidate {
document: unprojected_candidate
},
&plan
)
.unwrap_err(),
ScaleError::InvalidRootSelector {
source_root_node_index: 0,
}
);
}
#[test]
fn plan_scale_accepting_a_common_factor_implies_its_candidate_proves() {
let capability = complete_capability();
for (scale, expected_residual) in [
(0.010_000_02_f32, 17.0 * 2f64.powi(-23)),
(0.010_000_061_f32, 51.0 * 2f64.powi(-23)),
(0.010_000_08_f32, 67.0 * 2f64.powi(-23)),
(0.010_000_099_f32, 83.0 * 2f64.powi(-23)),
] {
let doc = noisy_factor_document(scale);
let request = noisy_factor_request(&doc, &capability);
let plan = plan_scale(&request)
.unwrap_or_else(|error| panic!("scale {scale} must plan, got {error:?}"));
let candidate = build_scale_candidate(&doc, &plan)
.unwrap_or_else(|error| panic!("scale {scale} must build, got {error:?}"));
let proof = prove_scale(&doc, &candidate, &plan)
.unwrap_or_else(|error| panic!("scale {scale} must prove, got {error:?}"));
assert_eq!(
proof.unit_scale.max(),
expected_residual,
"scale {scale} unit-scale residual"
);
assert!(
proof.unit_scale.max() <= plan.tolerance_policy().postcondition_unit_scale_residual,
"scale {scale} residual {} exceeds the declared bound",
proof.unit_scale.max()
);
}
}
#[test]
fn the_common_factor_band_stays_relative_below_the_scalar_absolute_floor() {
let declared = 2f64.powi(-23);
let unit = 2f32.powi(-23);
let capability = complete_capability();
for n in [83u32, 84, 600] {
let doc = noisy_factor_document(unit * (1.0 + n as f32 * unit));
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: declared,
},
document: &doc,
capability: &capability,
};
if n > 83 {
assert!(
matches!(plan_scale(&request), Err(ScaleError::FactorMismatch { .. })),
"n = {n} must not be accepted"
);
continue;
}
let plan =
plan_scale(&request).unwrap_or_else(|error| panic!("n = {n} must plan, got {error:?}"));
let candidate = build_scale_candidate(&doc, &plan)
.unwrap_or_else(|error| panic!("n = {n} must build, got {error:?}"));
let proof = prove_scale(&doc, &candidate, &plan)
.unwrap_or_else(|error| panic!("n = {n} must prove, got {error:?}"));
assert_eq!(proof.unit_scale.max(), f64::from(n) * 2f64.powi(-23));
}
}
#[test]
fn a_plan_loading_all_three_analytic_bands_still_proves() {
let u = 2f32.powi(-17);
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.5 * (1.0 + u)),
},
RigNode {
parent: Some(0),
source_node_index: 1,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::new(1.0 + u * 0.5, 1.0 + u * 0.5, 1.0 + u * 2.0),
},
];
let doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.5,
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.unit_scale.max(), 192.0 * 2f64.powi(-23));
let policy = plan.tolerance_policy();
assert!(proof.unit_scale.max() <= policy.postcondition_unit_scale_residual);
let two_bands = (1.0 - policy.common_factor).powi(-2) - 1.0;
assert!(
proof.unit_scale.max() > two_bands,
"residual {} does not exceed the two-band figure {two_bands}",
proof.unit_scale.max()
);
}
#[test]
fn equal_axis_uniformity_is_relative_to_the_authored_magnitude() {
let error = reject_case(|rest| *rest = trs_scale(Vec3::new(0.01, 0.01, 0.010005)));
assert!(
matches!(
error,
ScaleError::InvalidAffineDomain {
reason: AffineDomainViolation::NonUniformScale,
..
}
),
"unexpected error {error:?}"
);
}
#[test]
fn a_tiny_expected_factor_does_not_pass_the_common_factor_check_by_absolute_luck() {
let doc = noisy_factor_document(1e-6);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1e-30,
},
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&request).unwrap_err(),
ScaleError::FactorMismatch { .. }
));
}
#[test]
fn incomplete_capability_rejects_before_geometry_is_inspected() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = ScaleCapabilityFacts::default();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 1.0 },
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&request).unwrap_err(),
ScaleError::IncompleteCapability
));
}
#[test]
fn incomplete_source_skeleton_coverage_rejects() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.assets.source_skeleton.coverage = SourceSkeletonCoverage::Unavailable;
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&request).unwrap_err(),
ScaleError::IncompleteSourceSkeleton
));
}
#[test]
fn invalid_factor_rejects() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
for factor in [0.0, -1.0, f64::NAN, f64::INFINITY] {
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor },
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&request).unwrap_err(),
ScaleError::InvalidFactor { .. }
));
}
}
#[test]
fn invalid_source_selectors_reject_without_panicking() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let bad_root = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 99,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&bad_root).unwrap_err(),
ScaleError::InvalidRootSelector {
source_root_node_index: 99
}
));
let bad_skin = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 99,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&bad_skin).unwrap_err(),
ScaleError::InvalidSkinSelector {
source_skin_index: 99
}
));
}
#[test]
fn incomplete_closure_when_a_skin_joint_is_outside_the_scaled_roots_descendants() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(None, 1, Vec3::ZERO),
rig(Some(1), 2, Vec3::new(0.0, 1.0, 0.0)),
];
let doc = rig_document(&nodes, &[2], 0, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
};
assert_eq!(
plan_scale(&request).unwrap_err(),
ScaleError::IncompleteClosure {
reason: "joint_not_descendant_of_scaled_root"
}
);
}
#[test]
fn descendant_unskinned_geometry_inside_the_closure_rejects() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(1), 2, Vec3::new(1.0, 0.0, 0.0)),
];
let mut doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
doc.assets.meshes.push(MeshAsset {
name: "prop".into(),
source_mesh_index: 1,
primitives: vec![Primitive {
positions: vec![Vec3::ZERO],
joints: vec![[0, 0, 0, 0]],
weights: vec![[1.0, 0.0, 0.0, 0.0]],
..Primitive::default()
}],
});
doc.assets.instances.push(MeshInstance {
source_node_index: 2,
node: 2,
mesh: 1,
skin_joints: Vec::new(),
skin_ibms: Vec::new(),
});
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&request).unwrap_err(),
ScaleError::UnsupportedUnskinnedGeometry { node: 2 }
));
}
#[test]
fn root_attached_unskinned_geometry_rejects() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.assets.meshes.push(MeshAsset {
name: "prop".into(),
source_mesh_index: 1,
primitives: vec![Primitive {
positions: vec![Vec3::ZERO],
joints: vec![[0, 0, 0, 0]],
weights: vec![[1.0, 0.0, 0.0, 0.0]],
..Primitive::default()
}],
});
doc.assets.instances.push(MeshInstance {
source_node_index: 0,
node: 0,
mesh: 1,
skin_joints: Vec::new(),
skin_ibms: Vec::new(),
});
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&request).unwrap_err(),
ScaleError::UnsupportedUnskinnedGeometry { node: 0 }
));
}
#[test]
fn ancestor_path_attached_unskinned_geometry_rejects() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(1), 2, Vec3::new(0.0, 1.0, 0.0)),
];
let mut doc = rig_document(&nodes, &[2], 0, Mat4::IDENTITY);
doc.assets.meshes.push(MeshAsset {
name: "prop".into(),
source_mesh_index: 1,
primitives: vec![Primitive {
positions: vec![Vec3::ZERO],
joints: vec![[0, 0, 0, 0]],
weights: vec![[1.0, 0.0, 0.0, 0.0]],
..Primitive::default()
}],
});
doc.assets.instances.push(MeshInstance {
source_node_index: 1,
node: 1,
mesh: 1,
skin_joints: Vec::new(),
skin_ibms: Vec::new(),
});
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&request).unwrap_err(),
ScaleError::UnsupportedUnskinnedGeometry { node: 1 }
));
}
#[test]
fn dangling_ancestor_source_parent_index_rejects_without_panicking() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(1), 2, Vec3::new(0.0, 1.0, 0.0)),
];
let mut doc = rig_document(&nodes, &[2], 0, Mat4::IDENTITY);
doc.assets
.source_skeleton
.nodes
.retain(|node| node.source_node_index != 1);
assert_eq!(
closure_reject_reason(&doc, 0),
ScaleError::IncompleteClosure {
reason: "dangling_source_parent_node_index"
}
);
assert_eq!(
rest_bind_reject_reason(&doc),
ScaleError::InvalidDocumentShape(DocumentShapeError::SourceProjection {
source_node_index: 2,
violation: SourceProjectionViolation::ParentSourceNodeMissing,
})
);
}
#[test]
fn rest_bind_rebases_root_scale_animation_values_and_cubic_tangents() {
let mut doc = compensated_document();
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 0,
property: Property::Scale,
interpolation: Interpolation::CubicSpline,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::new(-2.0, 3.0, -4.0), Vec3::ONE, Vec3::new(5.0, -6.0, 7.0), Vec3::new(-8.0, 9.0, -10.0), Vec3::new(11.0, -12.0, 13.0), Vec3::new(-14.0, 15.0, -16.0), ]),
}],
});
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
assert!(plan.field_rows().iter().any(|row| matches!(
(row.target, row.disposition),
(
ScaleFieldTarget::AnimationValues {
property: Property::Scale,
..
},
ScaleFieldDisposition::Rewrite(ScaleRewriteRule::RestBindLocalScale)
)
)));
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let TrackValues::Vec3s(values) = &candidate.document().clips[0].tracks[0].values else {
panic!("expected vec3 scale track");
};
let expected = [
Vec3::new(-200.0, 300.0, -400.0),
Vec3::splat(100.0),
Vec3::new(500.0, -600.0, 700.0),
Vec3::new(-800.0, 900.0, -1000.0),
Vec3::new(1100.0, -1200.0, 1300.0),
Vec3::new(-1400.0, 1500.0, -1600.0),
];
assert_eq!(values, &expected);
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn rest_bind_rebases_every_constant_identity_root_scale_key() {
let mut doc = compensated_document();
doc.clips.push(Clip {
name: "constant identity".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 0,
property: Property::Scale,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![Vec3::ONE, Vec3::ONE]),
}],
});
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let TrackValues::Vec3s(values) = &candidate.document().clips[0].tracks[0].values else {
panic!("expected vec3 scale track");
};
assert_eq!(values, &[Vec3::splat(100.0), Vec3::splat(100.0)]);
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn rest_bind_preserves_strict_descendant_and_unaffected_scale_tracks() {
let mut doc = parent_boundary_document();
doc.clips.push(Clip {
name: "scale".into(),
duration_s: 1.0,
tracks: vec![
Track {
bone: 1,
property: Property::Scale,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![Vec3::ONE]),
},
Track {
bone: 2,
property: Property::Scale,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![Vec3::new(2.0, 3.0, 4.0)]),
},
Track {
bone: 0,
property: Property::Scale,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![Vec3::new(5.0, 6.0, 7.0)]),
},
],
});
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 1,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let tracks = &candidate.document().clips[1].tracks;
let TrackValues::Vec3s(root) = &tracks[0].values else {
panic!("expected vec3 root scale track");
};
let TrackValues::Vec3s(descendant) = &tracks[1].values else {
panic!("expected vec3 descendant scale track");
};
let TrackValues::Vec3s(unaffected) = &tracks[2].values else {
panic!("expected vec3 unaffected scale track");
};
assert_eq!(root, &[Vec3::splat(100.0)]);
assert_eq!(descendant, &[Vec3::new(2.0, 3.0, 4.0)]);
assert_eq!(unaffected, &[Vec3::new(5.0, 6.0, 7.0)]);
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn rest_bind_scale_animation_uses_f64_reciprocal_then_one_f32_narrowing() {
let mut nodes = compensated_rig();
nodes[0].scale = Vec3::splat(0.03);
let child_world = Mat4::from_scale_rotation_translation(
nodes[0].scale,
nodes[1].rotation,
Vec3::new(0.0, 3.0, 0.0),
);
let mut doc = rig_document(&nodes, &[1], 0, child_world.inverse());
let original = Vec3::new(0.7, -1.3, 2.9);
doc.clips.push(Clip {
name: "scale".into(),
duration_s: 0.0,
tracks: vec![Track {
bone: 0,
property: Property::Scale,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![original]),
}],
});
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.03,
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let expected_multiplier = 1.0 / plan.common_factor();
assert_eq!(expected_multiplier, 1.0f64 / 0.03f64);
assert_ne!(
expected_multiplier,
f64::from(1.0f32 / 0.03f32),
"the stored rewrite must not round the reciprocal through f32"
);
let TrackValues::Vec3s(values) = &candidate.document().clips[0].tracks[0].values else {
panic!("expected vec3 scale track");
};
assert_eq!(
values[0],
(original.as_dvec3() * expected_multiplier).as_vec3()
);
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn proof_names_a_root_scale_track_left_at_its_source_value() {
let mut doc = compensated_document();
doc.clips.push(Clip {
name: "scale".into(),
duration_s: 0.0,
tracks: vec![Track {
bone: 0,
property: Property::Scale,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![Vec3::ONE]),
}],
});
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut no_root_rewrite = candidate.into_document();
no_root_rewrite.clips[0].tracks[0].values = TrackValues::Vec3s(vec![Vec3::ONE]);
assert!(matches!(
prove_scale(&doc, &ScaleCandidate::from_document(no_root_rewrite), &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::TrackValue,
..
}
));
}
#[test]
fn build_scale_candidate_rejects_a_scale_track_added_after_planning_without_mutating_the_document()
{
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let mut mutated = doc.clone();
mutated.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 0,
property: Property::Scale,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![Vec3::ONE]),
}],
});
let original_translation = mutated.skeleton.bones[0].rest.translation;
let original_clip_count = mutated.clips.len();
let error = build_scale_candidate(&mutated, &plan).unwrap_err();
assert_eq!(
error,
ScaleError::PlanDocumentMismatch {
reason: "proof_obligations_mismatch"
}
);
assert_eq!(
mutated.skeleton.bones[0].rest.translation,
original_translation
);
assert_eq!(mutated.clips.len(), original_clip_count);
assert_eq!(doc.skeleton.bones[0].rest.translation, Vec3::ZERO);
}
#[test]
fn every_rejection_path_leaves_the_source_document_unchanged() {
let cases: Vec<Box<dyn Fn() -> (Document, ScaleOperation)>> = vec![
Box::new(|| {
(
rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY),
ScaleOperation::WholeDocumentLinearUnits { factor: -1.0 },
)
}),
Box::new(|| {
(
rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY),
ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.5,
},
)
}),
Box::new(|| {
(
rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY),
ScaleOperation::RestBindUniformScale {
source_skin_index: 99,
source_root_node_index: 0,
expected_factor: 1.0,
},
)
}),
];
for case in cases {
let (doc, operation) = case();
let before = doc.skeleton.bones[0].rest.translation;
let capability = complete_capability();
let request = ScaleRequest {
operation,
document: &doc,
capability: &capability,
};
assert!(plan_scale(&request).is_err());
assert_eq!(doc.skeleton.bones[0].rest.translation, before);
}
}
#[test]
fn duplicate_source_node_index_rejects_instead_of_last_write_wins() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let mut duplicate = doc.assets.source_skeleton.nodes[1].clone();
duplicate.source_node_index = 1;
doc.assets.source_skeleton.nodes.push(duplicate);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 2.0 },
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&request).unwrap_err(),
ScaleError::InvalidDocumentShape(DocumentShapeError::DuplicateSourceNodeIndex {
source_node_index: 1
})
));
}
#[test]
fn out_of_range_track_bone_added_after_planning_rejects_without_panicking() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 2.0 },
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let mut mutated = doc.clone();
mutated.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 99,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![Vec3::ZERO]),
}],
});
assert!(matches!(
build_scale_candidate(&mutated, &plan).unwrap_err(),
ScaleError::InvalidDocumentShape(DocumentShapeError::TrackShape { .. })
));
}
#[test]
fn joint_influence_slot_outside_skin_joints_rejects_without_panicking() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut malformed = doc.clone();
malformed.assets.meshes[0].primitives[0].joints[0] = [5, 0, 0, 0];
assert!(matches!(
prove_scale(&malformed, &candidate, &plan).unwrap_err(),
ScaleError::InvalidSkinnedPrimitive {
reason: "joint_influence_slot_out_of_range",
..
}
));
}
#[test]
fn missing_per_vertex_joints_or_weights_in_a_skinned_primitive_rejects() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut malformed = doc.clone();
malformed.assets.meshes[0].primitives[0].weights.clear();
assert_eq!(
prove_scale(&malformed, &candidate, &plan).unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "payload_shape_inventory_mismatch"
}
);
}
#[test]
fn non_finite_vertex_position_in_a_skinned_primitive_rejects() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut malformed = doc.clone();
malformed.assets.meshes[0].primitives[0].positions[0] = Vec3::new(f32::NAN, 0.0, 0.0);
assert!(matches!(
prove_scale(&malformed, &candidate, &plan).unwrap_err(),
ScaleError::InvalidMeshPrimitive {
reason: "non_finite_position",
..
}
));
}
#[test]
fn build_validates_the_candidate_it_generated_before_returning_it() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.assets.meshes[0].primitives[0].positions[0] = Vec3::splat(f32::MAX);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 2.0 },
document: &doc,
capability: &capability,
})
.expect("the finite source and representable factor plan");
assert!(matches!(
build_scale_candidate(&doc, &plan).unwrap_err(),
ScaleError::InvalidMeshPrimitive {
reason: "non_finite_position",
..
}
));
}
#[test]
fn build_runs_the_shared_shape_checker_on_the_candidate_it_generated() {
let document = Document {
skeleton: Skeleton {
bones: vec![Bone {
name: "root".into(),
parent: None,
rest: Transform {
translation: Vec3::splat(f32::MAX),
..Transform::IDENTITY
},
inverse_bind: None,
}],
},
..Document::default()
};
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 2.0 },
document: &document,
capability: &capability,
})
.expect("the source shape and narrowed factor are individually finite");
assert_eq!(
build_scale_candidate(&document, &plan).unwrap_err(),
ScaleError::InvalidDocumentShape(DocumentShapeError::NonFiniteSkeletonRest { node: 0 })
);
}
#[test]
fn missing_inverse_bind_evidence_rejects_instead_of_defaulting_to_identity() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut malformed = doc.clone();
malformed.assets.instances[0].skin_ibms.clear();
assert_eq!(
prove_scale(&malformed, &candidate, &plan).unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "payload_shape_inventory_mismatch"
}
);
}
fn absent_inverse_bind_document(
status: SourceInverseBindAccessorStatus,
coverage: SourceSkeletonCoverage,
) -> Document {
Document {
skeleton: Skeleton {
bones: vec![Bone {
name: "bone0".into(),
parent: None,
rest: Transform::IDENTITY,
inverse_bind: None,
}],
},
clips: Vec::new(),
assets: SceneAssets {
meshes: vec![MeshAsset {
name: "mesh".into(),
source_mesh_index: 0,
primitives: vec![Primitive {
positions: vec![Vec3::new(1.0, 0.0, 0.0)],
joints: vec![[0, 0, 0, 0]],
weights: vec![[1.0, 0.0, 0.0, 0.0]],
..Primitive::default()
}],
}],
instances: vec![MeshInstance {
source_node_index: 0,
node: 0,
mesh: 0,
skin_joints: vec![0],
skin_ibms: Vec::new(),
}],
source_skeleton: SourceSkeletonAssets {
coverage,
nodes: vec![SourceNodeAsset {
source_node_index: 0,
name: None,
parent_source_node_index: None,
scene_root_indices: vec![0],
local_rest: SourceNodeLocalRest::Trs {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
bone: Some(0),
}],
skins: vec![SourceSkinAsset {
source_skin_index: 0,
name: None,
skeleton_root_source_node_index: None,
joint_source_node_indices: vec![0],
inverse_bind_accessor: SourceInverseBindAccessor {
status,
declared_count: None,
matrices: Vec::new(),
},
attachments: vec![SourceSkinAttachment {
source_node_index: 0,
source_mesh_index: Some(0),
}],
}],
},
..SceneAssets::default()
},
source: Default::default(),
}
}
#[test]
fn absent_inverse_bind_accessor_with_complete_coverage_resolves_to_identity() {
let doc = absent_inverse_bind_document(
SourceInverseBindAccessorStatus::Absent,
SourceSkeletonCoverage::Complete,
);
let instance = &doc.assets.instances[0];
assert_eq!(instance_bind(&doc, instance, 0, 0), Ok(Mat4::IDENTITY));
}
#[test]
fn malformed_inverse_bind_accessor_status_still_rejects_rather_than_defaulting() {
for status in [
SourceInverseBindAccessorStatus::EmptyAccessor,
SourceInverseBindAccessorStatus::CountMismatch,
SourceInverseBindAccessorStatus::Unreadable,
] {
let doc = absent_inverse_bind_document(status, SourceSkeletonCoverage::Complete);
let instance = &doc.assets.instances[0];
assert!(matches!(
instance_bind(&doc, instance, 0, 0),
Err(ScaleError::MissingInverseBind { node: 0 })
));
}
}
#[test]
fn absent_inverse_bind_accessor_with_incomplete_coverage_still_rejects() {
let doc = absent_inverse_bind_document(
SourceInverseBindAccessorStatus::Absent,
SourceSkeletonCoverage::Unavailable,
);
let instance = &doc.assets.instances[0];
assert!(matches!(
instance_bind(&doc, instance, 0, 0),
Err(ScaleError::MissingInverseBind { node: 0 })
));
}
#[test]
fn build_scale_candidate_rejects_a_duplicate_clip_track_added_after_planning() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 2.0 },
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let mut mutated = doc.clone();
let track = Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![Vec3::ZERO]),
};
mutated.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![track.clone(), track],
});
assert!(matches!(
build_scale_candidate(&mutated, &plan).unwrap_err(),
ScaleError::InvalidDocumentShape(DocumentShapeError::DuplicateClipTrack { .. })
));
}
#[test]
fn prove_scale_rejects_a_malformed_source_document_replayed_against_a_valid_candidate() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 2.0 },
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut malformed_source = doc.clone();
malformed_source.assets.instances[0].mesh = 99;
assert!(matches!(
prove_scale(&malformed_source, &candidate, &plan).unwrap_err(),
ScaleError::InvalidDocumentShape(DocumentShapeError::MeshInstanceShape {
violation: MeshInstanceShapeViolation::MeshIndexOutOfRange,
..
})
));
}
#[test]
fn prove_scale_rejects_a_candidate_missing_a_source_clip() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![Vec3::ZERO]),
}],
});
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 2.0 },
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut dropped = candidate.document().clone();
dropped.clips.clear();
let dropped = ScaleCandidate { document: dropped };
assert!(matches!(
prove_scale(&doc, &dropped, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "clip_count_mismatch"
}
));
}
#[test]
fn prove_scale_rejects_a_candidate_with_an_extra_track_not_present_in_source() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut extended = candidate.document().clone();
extended.clips.push(Clip {
name: "extra".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 0,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Vec3s(vec![Vec3::ZERO]),
}],
});
let extended = ScaleCandidate { document: extended };
assert!(matches!(
prove_scale(&doc, &extended, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "clip_count_mismatch"
}
));
}
#[test]
fn prove_scale_rejects_a_candidate_whose_track_times_differ_from_the_source() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, 3.0, 0.0)]),
}],
});
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let TrackValues::Vec3s(built) = &candidate.document().clips[0].tracks[0].values else {
panic!("translation track must hold Vec3 values");
};
assert!((built[0] - Vec3::new(0.0, 0.01, 0.0)).length() < 1e-9);
assert!((built[1] - Vec3::new(0.0, 0.03, 0.0)).length() < 1e-9);
prove_scale(&doc, &candidate, &plan).unwrap();
let mut retimed = candidate.document().clone();
retimed.clips[0].tracks[0].times = vec![0.0, 2.0];
assert_eq!(
retimed.clips[0].tracks[0].values.len(),
doc.clips[0].tracks[0].values.len(),
"only the sampling grid may differ"
);
let retimed = ScaleCandidate { document: retimed };
assert_eq!(
prove_scale(&doc, &retimed, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "track_shape_mismatch"
}
);
}
#[test]
fn an_authored_rest_rotation_with_magnitude_below_one_is_not_a_rotation_residual() {
let unnormalized = Quat::from_xyzw(0.0, 0.3826834, 0.0, 0.9238795);
assert!(
unnormalized.as_dquat().length() < 1.0,
"fixture quaternion must be shorter than unit length"
);
let nodes = vec![
rig(None, 0, Vec3::ZERO),
RigNode {
parent: Some(0),
source_node_index: 1,
translation: Vec3::new(0.0, 1.0, 0.0),
rotation: unnormalized,
scale: Vec3::ONE,
},
];
let doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.rest_rotation.max(), 0.0);
}
#[test]
fn a_genuinely_rewritten_rest_rotation_still_fails_proof() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.skeleton.bones[1].rest.rotation = Quat::from_rotation_y(0.5);
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::RestRotation,
..
}
));
}
#[test]
fn a_rest_rotation_error_is_bounded_by_the_declared_angle_not_twice_it() {
let doc = rig_document(&rest_only_leaf_rig(), &[1], 0, Mat4::IDENTITY);
assert_eq!(doc.skeleton.bones[2].rest.rotation, Quat::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert_eq!(plan.tolerance_policy().rotation_residual_radians, 1e-5);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let rotate_leaf = |half_angle: f32| {
let mut broken = candidate.document().clone();
broken.skeleton.bones[2].rest.rotation = Quat::from_xyzw(0.0, half_angle, 0.0, 1.0);
ScaleCandidate { document: broken }
};
let inside = rotate_leaf(4.5e-6);
let proof = prove_scale(&doc, &inside, &plan).unwrap();
assert!(
(proof.rest_rotation.max() - 9.0e-6).abs() < 1e-9,
"residual {} is not the 9e-6 radian angle it measures",
proof.rest_rotation.max()
);
let outside = rotate_leaf(5.5e-6);
let error = prove_scale(&doc, &outside, &plan).unwrap_err();
let ScaleError::ProofResidualExceeded {
kind,
observed,
tolerance,
} = error
else {
panic!("expected a residual rejection, got {error:?}");
};
assert_eq!(kind, ProofResidualKind::RestRotation);
assert_eq!(tolerance, 1e-5);
assert!(
(observed - 1.1e-5).abs() < 1e-9,
"observed {observed} is not the 1.1e-5 radian angle it measures"
);
}
#[test]
fn a_rest_rotation_chord_above_two_saturates_at_two_pi_instead_of_reporting_nan() {
let doc = rig_document(&rest_only_leaf_rig(), &[1], 0, Mat4::IDENTITY);
assert_eq!(doc.skeleton.bones[2].rest.rotation, Quat::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert_eq!(plan.tolerance_policy().rotation_residual_radians, 1e-5);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.skeleton.bones[2].rest.rotation = Quat::from_xyzw(0.0, 0.0, 0.0, -4.0);
let broken = ScaleCandidate { document: broken };
let error = prove_scale(&doc, &broken, &plan).unwrap_err();
let ScaleError::ProofResidualExceeded {
kind,
observed,
tolerance,
} = error
else {
panic!("expected a residual rejection, got {error:?}");
};
assert_eq!(kind, ProofResidualKind::RestRotation);
assert_eq!(tolerance, 1e-5);
assert!(
observed.is_finite(),
"saturated residual {observed} must never be NaN"
);
assert_eq!(observed, std::f64::consts::TAU);
}
#[test]
fn the_reported_rest_rotation_residual_is_the_maximum_not_the_last_node_seen() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(0), 2, Vec3::new(3.0, 0.0, 0.0)),
rig(Some(0), 3, Vec3::new(0.0, 0.0, 3.0)),
];
let doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2, 3]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.skeleton.bones[2].rest.rotation = Quat::from_xyzw(2f32.powi(-18), 0.0, 0.0, 1.0);
assert_eq!(broken.skeleton.bones[3].rest.rotation, Quat::IDENTITY);
let broken = ScaleCandidate { document: broken };
let proof = prove_scale(&doc, &broken, &plan).unwrap();
assert!(
(proof.rest_rotation.max() - 2f64.powi(-17)).abs() < 1e-15,
"residual {} is not the 2^-17 radian angle bone 2 carries",
proof.rest_rotation.max()
);
assert!(proof.rest_rotation.max() > 0.0);
}
#[test]
fn a_factor_that_annihilates_or_overflows_at_the_f32_boundary_rejects_at_plan_time() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
for factor in [1e-50, 1e40] {
let request = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor },
document: &doc,
capability: &capability,
};
assert!(
matches!(
plan_scale(&request).unwrap_err(),
ScaleError::FactorNotRepresentable { .. }
),
"factor {factor} was not rejected"
);
}
}
#[test]
fn a_rest_bind_factor_whose_reciprocal_overflows_f32_rejects_at_plan_time() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1e-40,
},
document: &doc,
capability: &capability,
};
assert!(matches!(
plan_scale(&request).unwrap_err(),
ScaleError::FactorNotRepresentable {
declared: 1e-40,
..
}
));
}
#[test]
fn a_non_finite_residual_fails_closed_instead_of_comparing_false() {
for (observed, tolerance) in [
(f64::NAN, 1.0),
(f64::INFINITY, 1.0),
(f64::NEG_INFINITY, 1.0),
(0.0, f64::NAN),
(0.0, f64::INFINITY),
] {
assert!(
check_residual(ProofResidualKind::Bounds, observed, tolerance).is_err(),
"observed {observed} tolerance {tolerance} passed"
);
}
}
#[test]
fn a_shear_only_f64_can_see_is_still_classified_as_sheared() {
let c0 = Vec3::new(0.12792248, -0.99066633, -0.047073245);
let c1 = Vec3::new(-0.34637994, -0.00016034879, -0.93809813);
let c2 = Vec3::new(0.92933476, 0.13630849, -0.3431568);
assert!((c1.dot(c2) as f64).abs() < 1e-5, "f32 dot is inside band");
assert!(
(c1.as_dvec3().dot(c2.as_dvec3())).abs() > 1e-5,
"f64 dot is outside band"
);
let error = reject_case(|rest| {
*rest = SourceNodeLocalRest::Matrix(Mat4::from_cols(
c0.extend(0.0),
c1.extend(0.0),
c2.extend(0.0),
Vec4::W,
));
});
assert!(
matches!(
error,
ScaleError::InvalidAffineDomain {
reason: AffineDomainViolation::Sheared,
..
}
),
"unexpected error {error:?}"
);
}
#[derive(Clone, Copy, Debug)]
enum ShearPair {
XY,
XZ,
YZ,
}
fn single_shear_basis(pair: ShearPair, s: f32) -> Mat3 {
match pair {
ShearPair::XY => Mat3::from_cols(
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(s, 1.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
),
ShearPair::XZ => Mat3::from_cols(
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(s, 0.0, 1.0),
),
ShearPair::YZ => Mat3::from_cols(
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(0.0, s, 1.0),
),
}
}
fn assert_only_this_column_pair_decides_shear(pair: ShearPair) {
let tol = ScaleTolerancePolicy::APPENDIX_D_V6;
let out_of_band_magnitude = 2.0_f32.powi(-15);
let in_band_magnitude = 2.0_f32.powi(-17);
for sign in [1.0_f32, -1.0] {
let out_of_band = sign * out_of_band_magnitude;
assert_eq!(
classify_affine(single_shear_basis(pair, out_of_band), &tol),
Err(AffineDomainViolation::Sheared),
"{pair:?} shear {out_of_band} was not rejected as sheared"
);
let in_band = sign * in_band_magnitude;
assert!(
classify_affine(single_shear_basis(pair, in_band), &tol).is_ok(),
"{pair:?} shear {in_band} was rejected, so the shape and not \
the magnitude is what the out-of-band case rejects on"
);
}
}
#[test]
fn shear_isolated_to_the_x_y_column_pair_is_sheared_at_either_sign() {
assert_only_this_column_pair_decides_shear(ShearPair::XY);
}
#[test]
fn shear_isolated_to_the_x_z_column_pair_is_sheared_at_either_sign() {
assert_only_this_column_pair_decides_shear(ShearPair::XZ);
}
#[test]
fn shear_isolated_to_the_y_z_column_pair_is_sheared_at_either_sign() {
assert_only_this_column_pair_decides_shear(ShearPair::YZ);
}
fn payload_document() -> Document {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.assets.meshes[0].primitives[0] = Primitive {
positions: vec![
Vec3::new(1.0, 1.0, 1.0),
Vec3::ZERO,
Vec3::new(-1.0, -1.0, -1.0),
],
joints: vec![[0, 0, 0, 0]; 3],
weights: vec![[1.0, 0.0, 0.0, 0.0]; 3],
..Primitive::default()
};
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Rotation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Quats(vec![
Quat::from_rotation_y(0.1),
Quat::from_rotation_y(0.1),
]),
}],
});
doc
}
fn whole_document_plan(document: &Document, capability: &ScaleCapabilityFacts) -> ScalePlan {
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document,
capability,
})
.unwrap()
}
#[test]
fn a_rotation_key_rewritten_in_the_candidate_fails_proof() {
let doc = payload_document();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let mut doctored = doc.clone();
doctored.clips[0].tracks[0].values =
TrackValues::Quats(vec![Quat::from_rotation_y(2.5), Quat::from_rotation_y(2.5)]);
let candidate = build_scale_candidate(&doctored, &plan).unwrap();
assert!(matches!(
prove_scale(&doc, &candidate, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::TrackValue,
..
}
));
}
#[test]
fn an_interior_mesh_vertex_moved_in_the_candidate_fails_proof() {
let doc = payload_document();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let mut doctored = doc.clone();
doctored.assets.meshes[0].primitives[0].positions[1] = Vec3::new(0.5, 0.5, 0.5);
let candidate = build_scale_candidate(&doctored, &plan).unwrap();
assert!(matches!(
prove_scale(&doc, &candidate, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::MeshPosition,
..
}
));
}
#[test]
fn an_unsampled_translation_tangent_is_named_by_the_track_value_obligation() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::CubicSpline,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::new(0.0, 500.0, 0.0), Vec3::new(0.0, 1.0, 0.0), Vec3::ZERO, Vec3::ZERO, Vec3::new(0.0, 1.0, 0.0), Vec3::ZERO, ]),
}],
});
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
let TrackValues::Vec3s(values) = &mut broken.clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
values[0] = Vec3::new(0.0, 500.0, 0.0);
let broken = ScaleCandidate { document: broken };
let error = prove_scale(&doc, &broken, &plan).unwrap_err();
let ScaleError::ProofResidualExceeded {
kind,
observed,
tolerance,
} = error
else {
panic!("expected a proof residual, got {error:?}");
};
assert_eq!(kind, ProofResidualKind::TrackValue);
assert!((observed - 495.0).abs() < 1e-9, "observed {observed}");
assert!((tolerance - 5.001e-3).abs() < 1e-9, "tolerance {tolerance}");
}
#[test]
fn an_honest_candidate_proves_every_retained_payload_with_a_zero_residual() {
let doc = payload_document();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.track_value.max(), 0.0);
assert!(proof.mesh_position.max() < 1e-9);
}
#[test]
fn sample_times_are_harvested_from_every_animated_track_not_only_translation() {
let doc = payload_document();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 2);
}
fn unskinned_document() -> Document {
Document {
skeleton: Skeleton {
bones: vec![Bone {
name: "bone0".into(),
parent: None,
rest: Transform::IDENTITY,
inverse_bind: None,
}],
},
clips: Vec::new(),
assets: SceneAssets {
meshes: vec![MeshAsset {
name: "mesh".into(),
source_mesh_index: 0,
primitives: vec![Primitive {
positions: vec![Vec3::new(1.0, 2.0, 3.0)],
..Primitive::default()
}],
}],
instances: vec![MeshInstance {
source_node_index: 0,
node: 0,
mesh: 0,
skin_joints: Vec::new(),
skin_ibms: Vec::new(),
}],
..SceneAssets::default()
},
source: Default::default(),
}
}
#[test]
fn an_unskinned_document_does_not_declare_a_bounds_obligation_it_cannot_check() {
let doc = unskinned_document();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert!(
!plan
.obligations()
.contains(&ScaleProofObligation::SkinAndBounds)
);
assert!(plan.field_rows().iter().any(|row| matches!(
(row.target, row.disposition),
(
ScaleFieldTarget::MeshPositions { .. },
ScaleFieldDisposition::Rewrite(ScaleRewriteRule::WholeDocumentLength)
)
)));
}
#[test]
fn an_unskinned_documents_base_positions_are_proved_directly() {
let doc = unskinned_document();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert!(
(candidate.document().assets.meshes[0].primitives[0].positions[0]
- Vec3::new(0.01, 0.02, 0.03))
.length()
< 1e-8
);
prove_scale(&doc, &candidate, &plan).unwrap();
let mut unrewritten = candidate.document().clone();
unrewritten.assets.meshes[0].primitives[0].positions[0] = Vec3::new(1.0, 2.0, 3.0);
let unrewritten = ScaleCandidate {
document: unrewritten,
};
assert!(matches!(
prove_scale(&doc, &unrewritten, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::MeshPosition,
..
}
));
}
#[test]
fn a_replayed_plan_cannot_lose_its_bounds_evidence() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert!(
plan.obligations()
.contains(&ScaleProofObligation::SkinAndBounds)
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut unskinned = doc.clone();
unskinned.assets.instances[0].skin_joints.clear();
unskinned.assets.instances[0].skin_ibms.clear();
let mut unskinned_candidate = candidate.into_document();
unskinned_candidate.assets.instances[0].skin_joints.clear();
unskinned_candidate.assets.instances[0].skin_ibms.clear();
let candidate = ScaleCandidate {
document: unskinned_candidate,
};
assert_eq!(
prove_scale(&unskinned, &candidate, &plan).unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "proof_obligations_mismatch"
}
);
}
fn rotation_only_clip_document() -> Document {
let mut doc = multi_joint_document();
doc.clips[0].tracks = vec![Track {
bone: 1,
property: Property::Rotation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Quats(vec![Quat::IDENTITY, Quat::IDENTITY]),
}];
doc
}
fn without_clips(source: &Document, candidate: ScaleCandidate) -> (Document, ScaleCandidate) {
let mut source = source.clone();
source.clips.clear();
let mut document = candidate.into_document();
document.clips.clear();
(source, ScaleCandidate { document })
}
fn assert_replayed_inventory_mismatch(source: &Document, plan: &ScalePlan) {
let expected = ScaleError::PlanDocumentMismatch {
reason: "proof_obligations_mismatch",
};
assert_eq!(build_scale_candidate(source, plan).unwrap_err(), expected);
assert_eq!(
prove_scale(
source,
&ScaleCandidate {
document: source.clone(),
},
plan,
)
.unwrap_err(),
expected
);
}
#[test]
fn the_clip_driven_obligations_are_declared_only_by_the_tracks_that_evidence_them() {
let capability = complete_capability();
let unanimated = compensated_document();
let unanimated_plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &unanimated,
capability: &capability,
})
.unwrap();
assert!(unanimated.clips.is_empty());
let obligations = unanimated_plan.obligations().to_vec();
assert!(!obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(!obligations.contains(&ScaleProofObligation::CubicInteriors));
assert!(!obligations.contains(&ScaleProofObligation::Trajectories));
let rotation_only = rotation_only_clip_document();
let obligations = multi_joint_plan(&rotation_only, &capability)
.obligations()
.to_vec();
assert!(!obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(!obligations.contains(&ScaleProofObligation::CubicInteriors));
assert!(obligations.contains(&ScaleProofObligation::Trajectories));
let mut linear_only = multi_joint_document();
linear_only.clips[0].tracks.truncate(1);
assert_eq!(
linear_only.clips[0].tracks[0].interpolation,
Interpolation::Linear
);
let obligations = multi_joint_plan(&linear_only, &capability)
.obligations()
.to_vec();
assert!(obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(!obligations.contains(&ScaleProofObligation::CubicInteriors));
assert!(obligations.contains(&ScaleProofObligation::Trajectories));
let animated = multi_joint_document();
let obligations = multi_joint_plan(&animated, &capability)
.obligations()
.to_vec();
assert!(obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(obligations.contains(&ScaleProofObligation::CubicInteriors));
assert!(obligations.contains(&ScaleProofObligation::Trajectories));
}
#[test]
fn a_replayed_plan_cannot_lose_its_key_evidence() {
let doc = multi_joint_document();
let capability = complete_capability();
let plan = multi_joint_plan(&doc, &capability);
assert!(
plan.obligations()
.contains(&ScaleProofObligation::KeyTranslations)
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).unwrap();
let (clipless, clipless_candidate) = without_clips(&doc, candidate);
assert_eq!(
prove_scale(&clipless, &clipless_candidate, &plan).unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "proof_obligations_mismatch"
}
);
}
#[test]
fn a_replayed_plan_cannot_lose_its_cubic_evidence() {
let doc = multi_joint_document();
let capability = complete_capability();
let plan = multi_joint_plan(&doc, &capability);
assert!(
plan.obligations()
.contains(&ScaleProofObligation::CubicInteriors)
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut source = doc.clone();
source.clips[0].tracks.truncate(1);
let mut document = candidate.into_document();
document.clips[0].tracks.truncate(1);
assert_eq!(
prove_scale(&source, &ScaleCandidate { document }, &plan).unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "proof_obligations_mismatch"
}
);
}
#[test]
fn a_replayed_plan_cannot_lose_its_trajectory_evidence() {
let doc = rotation_only_clip_document();
let capability = complete_capability();
let plan = multi_joint_plan(&doc, &capability);
let obligations = plan.obligations().to_vec();
assert!(obligations.contains(&ScaleProofObligation::Trajectories));
assert!(!obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(!obligations.contains(&ScaleProofObligation::CubicInteriors));
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).unwrap();
let (clipless, clipless_candidate) = without_clips(&doc, candidate);
assert_eq!(
prove_scale(&clipless, &clipless_candidate, &plan).unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "proof_obligations_mismatch"
}
);
}
#[test]
fn a_replayed_plan_cannot_gain_trajectory_evidence() {
let doc = compensated_document();
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let before = plan.obligations().to_vec();
assert!(!before.contains(&ScaleProofObligation::KeyTranslations));
assert!(!before.contains(&ScaleProofObligation::CubicInteriors));
assert!(!before.contains(&ScaleProofObligation::Trajectories));
let mut gained = doc.clone();
gained.clips.push(Clip {
name: "gained_rotation".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Rotation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Quats(vec![Quat::IDENTITY, Quat::IDENTITY]),
}],
});
let after = compensated_rest_bind_plan(&gained, &capability)
.obligations()
.to_vec();
assert!(!after.contains(&ScaleProofObligation::KeyTranslations));
assert!(!after.contains(&ScaleProofObligation::CubicInteriors));
assert!(after.contains(&ScaleProofObligation::Trajectories));
assert_replayed_inventory_mismatch(&gained, &plan);
}
#[test]
fn a_replayed_plan_cannot_gain_key_evidence() {
let doc = rotation_only_clip_document();
let capability = complete_capability();
let plan = multi_joint_plan(&doc, &capability);
let before = plan.obligations().to_vec();
assert!(!before.contains(&ScaleProofObligation::KeyTranslations));
assert!(!before.contains(&ScaleProofObligation::CubicInteriors));
assert!(before.contains(&ScaleProofObligation::Trajectories));
let mut gained = doc.clone();
gained.clips[0].tracks.push(linear_translation_track());
let after = multi_joint_plan(&gained, &capability)
.obligations()
.to_vec();
assert!(after.contains(&ScaleProofObligation::KeyTranslations));
assert!(!after.contains(&ScaleProofObligation::CubicInteriors));
assert!(after.contains(&ScaleProofObligation::Trajectories));
assert_replayed_inventory_mismatch(&gained, &plan);
}
#[test]
fn a_replayed_plan_cannot_gain_cubic_evidence() {
let mut doc = multi_joint_document();
doc.clips[0].tracks.truncate(1);
let capability = complete_capability();
let plan = multi_joint_plan(&doc, &capability);
let before = plan.obligations().to_vec();
assert!(before.contains(&ScaleProofObligation::KeyTranslations));
assert!(!before.contains(&ScaleProofObligation::CubicInteriors));
assert!(before.contains(&ScaleProofObligation::Trajectories));
let mut gained = doc.clone();
gained.clips[0].tracks.push(cubic_rotation_track());
let after = multi_joint_plan(&gained, &capability)
.obligations()
.to_vec();
assert!(after.contains(&ScaleProofObligation::KeyTranslations));
assert!(after.contains(&ScaleProofObligation::CubicInteriors));
assert!(after.contains(&ScaleProofObligation::Trajectories));
assert_replayed_inventory_mismatch(&gained, &plan);
}
fn cubic_rotation_track() -> Track {
let zero = Quat::from_xyzw(0.0, 0.0, 0.0, 0.0);
Track {
bone: 2,
property: Property::Rotation,
interpolation: Interpolation::CubicSpline,
times: vec![0.0, 1.0],
values: TrackValues::Quats(vec![
zero, Quat::IDENTITY, zero, zero, Quat::from_rotation_z(0.5), zero, ]),
}
}
fn linear_translation_track() -> Track {
Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![Vec3::new(0.0, 100.0, 0.0), Vec3::new(0.0, 200.0, 0.0)]),
}
}
#[test]
fn the_cubic_obligations_two_halves_must_meet_inside_one_clip_and_neither_need_be_the_other() {
let capability = complete_capability();
let mut split = multi_joint_document();
split.clips[0].tracks = vec![linear_translation_track()];
split.clips.push(Clip {
name: "cubic_rotation".into(),
duration_s: 1.0,
tracks: vec![cubic_rotation_track()],
});
let split_plan = multi_joint_plan(&split, &capability);
let obligations = split_plan.obligations().to_vec();
assert!(obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(!obligations.contains(&ScaleProofObligation::CubicInteriors));
assert!(obligations.contains(&ScaleProofObligation::Trajectories));
let split_candidate = build_scale_candidate(&split, &split_plan).unwrap();
let proof = prove_scale(&split, &split_candidate, &split_plan).unwrap();
assert_eq!(proof.cubic_interior.max(), 0.0);
let mut shared = multi_joint_document();
shared.clips[0].tracks = vec![linear_translation_track(), cubic_rotation_track()];
let shared_plan = multi_joint_plan(&shared, &capability);
let obligations = shared_plan.obligations().to_vec();
assert!(obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(obligations.contains(&ScaleProofObligation::CubicInteriors));
assert!(obligations.contains(&ScaleProofObligation::Trajectories));
let shared_candidate = build_scale_candidate(&shared, &shared_plan).unwrap();
prove_scale(&shared, &shared_candidate, &shared_plan).unwrap();
}
#[test]
fn a_single_key_cubic_track_is_not_a_cubic_segment() {
let mut doc = multi_joint_document();
doc.clips[0].tracks = vec![
linear_translation_track(),
Track {
bone: 2,
property: Property::Translation,
interpolation: Interpolation::CubicSpline,
times: vec![0.0],
values: TrackValues::Vec3s(vec![
Vec3::ZERO, Vec3::new(0.0, 100.0, 0.0), Vec3::ZERO, ]),
},
];
let capability = complete_capability();
let plan = multi_joint_plan(&doc, &capability);
let obligations = plan.obligations().to_vec();
assert!(obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(!obligations.contains(&ScaleProofObligation::CubicInteriors));
assert!(obligations.contains(&ScaleProofObligation::Trajectories));
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.cubic_interior.max(), 0.0);
}
fn mid_chain_closure_document() -> Document {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
RigNode {
parent: Some(1),
source_node_index: 2,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
},
rig(Some(2), 3, Vec3::new(0.0, 100.0, 0.0)),
];
let joint_world = Mat4::from_translation(Vec3::new(0.0, 1.0, 0.0))
* Mat4::from_scale(Vec3::splat(0.01))
* Mat4::from_translation(Vec3::new(0.0, 100.0, 0.0));
rig_document(&nodes, &[3], 0, joint_world.inverse())
}
fn mid_chain_closure_plan(document: &Document) -> ScalePlan {
let capability = complete_capability();
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 2,
expected_factor: 0.01,
},
document,
capability: &capability,
})
.expect("the mid-chain rig plans")
}
#[test]
fn a_track_on_an_unaffected_bone_is_evidence_for_nothing() {
let mut doc = mid_chain_closure_document();
doc.clips.push(Clip {
name: "unaffected".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 0,
property: Property::Translation,
interpolation: Interpolation::CubicSpline,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::ZERO,
Vec3::new(0.0, 1.0, 0.0),
Vec3::ZERO,
Vec3::ZERO,
Vec3::new(0.0, 2.0, 0.0),
Vec3::ZERO,
]),
}],
});
let plan = mid_chain_closure_plan(&doc);
assert_eq!(plan.affected_nodes(), &[2, 3]);
assert_eq!(doc.skeleton.bones.len(), 4);
let obligations = plan.obligations().to_vec();
assert!(!obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(!obligations.contains(&ScaleProofObligation::CubicInteriors));
assert!(!obligations.contains(&ScaleProofObligation::Trajectories));
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn the_replayed_plan_inventory_reads_key_evidence_not_a_neighbouring_field() {
let mut linear_only = multi_joint_document();
linear_only.clips[0].tracks = vec![linear_translation_track()];
let capability = complete_capability();
let plan = multi_joint_plan(&linear_only, &capability);
let obligations = plan.obligations().to_vec();
assert!(obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(!obligations.contains(&ScaleProofObligation::CubicInteriors));
assert!(obligations.contains(&ScaleProofObligation::Trajectories));
let rotation_only = rotation_only_clip_document();
assert_eq!(
build_scale_candidate(&rotation_only, &plan).unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "proof_obligations_mismatch"
}
);
}
#[test]
fn an_unskinned_rest_bind_document_declares_neither_skin_nor_bounds() {
let mut doc = multi_joint_document();
doc.assets.instances.clear();
let capability = complete_capability();
let plan = multi_joint_plan(&doc, &capability);
let obligations = plan.obligations().to_vec();
assert!(!obligations.contains(&ScaleProofObligation::SkinAndBounds));
assert!(obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(obligations.contains(&ScaleProofObligation::Trajectories));
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert!(proof.sample_time_count > 0);
let skinned = multi_joint_document();
let skinned_domain = derive_rest_bind_plan_domain(&skinned, 0, 0).unwrap();
assert_eq!(skinned_domain.affected_nodes(), plan.affected_nodes());
let expected = ScaleError::PlanDocumentMismatch {
reason: "proof_obligations_mismatch",
};
assert_eq!(
build_scale_candidate(&skinned, &plan).unwrap_err(),
expected
);
let mut corrupted = build_rest_bind(&skinned, &plan).unwrap();
corrupted.assets.instances[0].skin_ibms[0] = Mat4::IDENTITY;
assert_eq!(
prove_scale(
&skinned,
&ScaleCandidate {
document: corrupted
},
&plan
)
.unwrap_err(),
expected
);
}
#[test]
fn a_closure_with_no_transform_only_attachment_does_not_declare_the_affine_obligation() {
let capability = complete_capability();
let bare = multi_joint_document();
let bare_plan = multi_joint_plan(&bare, &capability);
assert!(bare_plan.transform_only_attachments().is_empty());
assert!(
!bare_plan
.obligations()
.contains(&ScaleProofObligation::TransformOnlyAffine)
);
let attached = compensated_document();
let attached_plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &attached,
capability: &capability,
})
.unwrap();
assert_eq!(attached_plan.transform_only_attachments(), &[2]);
assert!(
attached_plan
.obligations()
.contains(&ScaleProofObligation::TransformOnlyAffine)
);
}
#[test]
fn a_replayed_plan_cannot_reclassify_a_transform_only_attachment_as_a_joint() {
let doc = compensated_document();
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap();
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
assert_eq!(plan.transform_only_attachments(), &[2]);
let mut reclassified = doc.clone();
let child_source = reclassified
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(2))
.unwrap()
.source_node_index;
reclassified.assets.source_skeleton.skins[0]
.joint_source_node_indices
.push(child_source);
assert_eq!(
build_scale_candidate(&reclassified, &plan).unwrap_err(),
ScaleError::PlanDocumentMismatch {
reason: "affected_source_topology_mismatch"
}
);
}
#[test]
fn a_source_skin_whose_vertices_are_all_unweighted_names_the_missing_source_bounds() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert!(
plan.obligations()
.contains(&ScaleProofObligation::SkinAndBounds)
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).unwrap();
let mut unweighted = doc.clone();
unweighted.assets.meshes[0].primitives[0].weights[0] = [0.0; 4];
assert_eq!(unweighted.assets.instances[0].skin_joints, vec![1]);
let error = prove_scale(&unweighted, &candidate, &plan).unwrap_err();
let ScaleError::MissingProofEvidence { kind, detail } = error else {
panic!("expected missing bounds evidence, got {error:?}");
};
assert_eq!(kind, ProofResidualKind::Bounds);
assert_eq!(detail, "source_bounds_missing");
}
#[test]
fn a_candidate_skin_whose_vertices_are_all_unweighted_names_the_missing_candidate_bounds() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert!(
plan.obligations()
.contains(&ScaleProofObligation::SkinAndBounds)
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut unweighted = candidate.document().clone();
unweighted.assets.meshes[0].primitives[0].weights[0] = [0.0; 4];
let unweighted = ScaleCandidate {
document: unweighted,
};
let error = prove_scale(&doc, &unweighted, &plan).unwrap_err();
let ScaleError::MissingProofEvidence { kind, detail } = error else {
panic!("expected missing bounds evidence, got {error:?}");
};
assert_eq!(kind, ProofResidualKind::Bounds);
assert_eq!(detail, "candidate_bounds_missing");
}
#[test]
fn rest_bind_materializes_the_format_defined_identity_bind_it_must_conjugate() {
let mut doc = compensated_document();
doc.assets.instances[0].skin_ibms.clear();
doc.assets.source_skeleton.skins[0].attachments = vec![SourceSkinAttachment {
source_node_index: doc.assets.instances[0].source_node_index,
source_mesh_index: Some(0),
}];
assert_eq!(
doc.assets.source_skeleton.skins[0]
.inverse_bind_accessor
.status,
SourceInverseBindAccessorStatus::Absent
);
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let ibms = &candidate.document().assets.instances[0].skin_ibms;
assert_eq!(ibms.len(), 1);
assert!(ibms[0].abs_diff_eq(Mat4::from_scale(Vec3::splat(0.01)), 1e-8));
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert!(proof.skin_matrix.max() < 1e-6);
}
#[test]
fn rest_bind_rebases_the_raw_source_projection_alongside_the_skeleton() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let SourceNodeLocalRest::Trs { scale, .. } =
&candidate.document().assets.source_skeleton.nodes[0].local_rest
else {
panic!("expected a trs source rest");
};
assert!((*scale - Vec3::ONE).length() < 1e-6);
let replanned = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: candidate.document(),
capability: &capability,
};
assert!(matches!(
plan_scale(&replanned).unwrap_err(),
ScaleError::FactorMismatch { .. }
));
}
#[test]
fn proof_rejects_a_corrupt_direct_trs_source_rewrite_with_correct_normalized_bones() {
let doc = compensated_document();
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).unwrap();
let mut adjacent = candidate.document().clone();
let root = adjacent
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(0))
.unwrap();
let SourceNodeLocalRest::Trs { scale, .. } = &mut root.local_rest else {
panic!("fixture root changed representation");
};
scale.x = f32::from_bits(scale.x.to_bits() + 1);
prove_scale(&doc, &ScaleCandidate::from_document(adjacent), &plan)
.expect("an adjacent-float TRS scale rewrite remains within tolerance");
let mut corrupted = candidate.into_document();
let root = corrupted
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(0))
.unwrap();
let SourceNodeLocalRest::Trs { scale, .. } = &mut root.local_rest else {
panic!("fixture root changed representation");
};
scale.x += 0.01;
assert_eq!(
prove_scale(&doc, &ScaleCandidate::from_document(corrupted), &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "field_disposition_mismatch"
}
);
}
fn matrix_projection_document() -> Document {
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
},
RigNode {
parent: Some(0),
source_node_index: 1,
translation: Vec3::new(0.0, 100.0, 0.0),
rotation: Quat::from_rotation_z(std::f32::consts::PI),
scale: Vec3::ONE,
},
rig(Some(1), 2, Vec3::new(0.0, 0.0, 50.0)),
];
let ibm = Mat4::from_cols(
Vec4::new(-100.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, -100.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 100.0, 0.0),
Vec4::new(0.0, 100.0, 0.0, 1.0),
);
let mut doc = rig_document(&nodes, &[1], 0, ibm);
let authored = [
Mat4::from_cols(
Vec4::new(0.01, 0.0, 0.0, 0.0),
Vec4::new(0.0, 0.01, 0.0, 0.0),
Vec4::new(0.0, 0.0, 0.01, 0.0),
Vec4::new(0.0, 0.0, 0.0, 1.0),
),
Mat4::from_cols(
Vec4::new(-1.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, -1.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0, 0.0),
Vec4::new(0.0, 100.0, 0.0, 1.0),
),
Mat4::from_cols(
Vec4::new(1.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, 1.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0, 0.0),
Vec4::new(0.0, 0.0, 50.0, 1.0),
),
];
for (node, matrix) in doc.assets.source_skeleton.nodes.iter_mut().zip(authored) {
node.local_rest = SourceNodeLocalRest::Matrix(matrix);
}
doc
}
#[test]
fn rest_bind_rebases_a_matrix_declared_source_projection_to_agree_with_the_skeleton() {
let doc = matrix_projection_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
assert_eq!(plan.transform_only_attachments(), &[2]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let expected = [
Mat4::IDENTITY,
Mat4::from_cols(
Vec4::new(-1.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, -1.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0, 0.0),
Vec4::new(0.0, 1.0, 0.0, 1.0),
),
Mat4::from_cols(
Vec4::new(1.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, 1.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0, 0.0),
Vec4::new(0.0, 0.0, 0.5, 1.0),
),
];
for (index, expected) in expected.into_iter().enumerate() {
let SourceNodeLocalRest::Matrix(matrix) =
&candidate.document().assets.source_skeleton.nodes[index].local_rest
else {
panic!("an authored matrix source rest must stay a matrix");
};
assert!(
matrix.abs_diff_eq(expected, 1e-6),
"source node {index} rebased to {matrix:?}"
);
let rest = candidate.document().skeleton.bones[index].rest;
let bone_matrix =
Mat4::from_scale_rotation_translation(rest.scale, rest.rotation, rest.translation);
assert!(
matrix.abs_diff_eq(bone_matrix, 1e-6),
"source node {index} projection {matrix:?} disagrees with bone rest {bone_matrix:?}"
);
}
let binds = &candidate.document().assets.instances[0].skin_ibms;
assert!(
binds[0].abs_diff_eq(
Mat4::from_cols(
Vec4::new(-1.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, -1.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0, 0.0),
Vec4::new(0.0, 1.0, 0.0, 1.0),
),
1e-5
),
"rebased bind {:?}",
binds[0]
);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert!(proof.rest_translation.max() < 1e-4);
assert!(proof.rest_rotation.max() < 1e-9);
assert!(proof.unit_scale.max() < 1e-4);
assert!(proof.transform_only_affine.max() < 1e-4);
assert!(proof.skin_matrix.max() < 1e-4);
}
#[test]
fn direct_matrix_rows_preserve_siblings_and_proof_checks_the_rewritten_translation() {
let mut doc = matrix_projection_document();
let translation = Vec3::new(3.25, -7.5, 11.0);
let rotation = Quat::from_rotation_z(0.001_f32.to_radians());
doc.skeleton.bones[1].rest.translation = translation;
doc.skeleton.bones[1].rest.rotation = rotation;
doc.assets.source_skeleton.nodes[1].local_rest = SourceNodeLocalRest::Matrix(
Mat4::from_scale_rotation_translation(Vec3::ONE, rotation, translation),
);
let root = doc.skeleton.bones[0].rest;
let joint = doc.skeleton.bones[1].rest;
let joint_world =
Mat4::from_scale_rotation_translation(root.scale, root.rotation, root.translation)
* Mat4::from_scale_rotation_translation(joint.scale, joint.rotation, joint.translation);
doc.assets.instances[0].skin_ibms[0] = joint_world.inverse();
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let SourceNodeLocalRest::Matrix(before) = &doc.assets.source_skeleton.nodes[1].local_rest
else {
panic!("fixture descendant changed representation");
};
let SourceNodeLocalRest::Matrix(after) =
&candidate.document().assets.source_skeleton.nodes[1].local_rest
else {
panic!("candidate descendant changed representation");
};
let old_two_step = |column: Vec4| {
let scaled = Vec4::new(column.x * 0.01, column.y * 0.01, column.z * 0.01, column.w);
scaled * (1.0 / 0.01)
};
assert_ne!(
[
old_two_step(before.x_axis),
old_two_step(before.y_axis),
old_two_step(before.z_axis),
]
.map(|value| value.to_array().map(f32::to_bits)),
[before.x_axis, before.y_axis, before.z_axis]
.map(|value| value.to_array().map(f32::to_bits)),
"fixture must expose the old two-step f32 linear roundtrip"
);
assert_eq!(
[after.x_axis, after.y_axis, after.z_axis].map(|value| value.to_array().map(f32::to_bits)),
[before.x_axis, before.y_axis, before.z_axis]
.map(|value| value.to_array().map(f32::to_bits)),
"a translation-only row must not perturb preserved linear columns"
);
assert_eq!(
after.w_axis.truncate().to_array().map(f32::to_bits),
(before.w_axis.truncate() * 0.01)
.to_array()
.map(f32::to_bits),
"the direct row must rewrite exactly the translation component"
);
assert_eq!(
[
after.x_axis.w,
after.y_axis.w,
after.z_axis.w,
after.w_axis.w,
]
.map(f32::to_bits),
[
before.x_axis.w,
before.y_axis.w,
before.z_axis.w,
before.w_axis.w,
]
.map(f32::to_bits),
"a translation-only row must preserve homogeneous components bit-exactly"
);
let mut adjacent_translation = candidate.document().clone();
let SourceNodeLocalRest::Matrix(local) =
&mut adjacent_translation.assets.source_skeleton.nodes[1].local_rest
else {
panic!("candidate descendant changed representation");
};
local.w_axis.y = f32::from_bits(local.w_axis.y.to_bits() + 1);
prove_scale(
&doc,
&ScaleCandidate::from_document(adjacent_translation),
&plan,
)
.expect("an adjacent-float matrix translation rewrite remains within tolerance");
let mut adjacent_linear = candidate.document().clone();
let SourceNodeLocalRest::Matrix(local) =
&mut adjacent_linear.assets.source_skeleton.nodes[0].local_rest
else {
panic!("candidate root changed representation");
};
local.x_axis.x = f32::from_bits(local.x_axis.x.to_bits() + 1);
prove_scale(&doc, &ScaleCandidate::from_document(adjacent_linear), &plan)
.expect("an adjacent-float matrix linear rewrite remains within tolerance");
let mut corrupt_linear = candidate.document().clone();
let SourceNodeLocalRest::Matrix(local) =
&mut corrupt_linear.assets.source_skeleton.nodes[0].local_rest
else {
panic!("candidate root changed representation");
};
local.x_axis.x += 0.1;
assert_eq!(
prove_scale(&doc, &ScaleCandidate::from_document(corrupt_linear), &plan,).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "field_disposition_mismatch"
}
);
let mut corrupted = candidate.into_document();
let SourceNodeLocalRest::Matrix(local) =
&mut corrupted.assets.source_skeleton.nodes[1].local_rest
else {
panic!("candidate descendant changed representation");
};
local.w_axis.y += 1.0;
assert_eq!(
prove_scale(&doc, &ScaleCandidate::from_document(corrupted), &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "field_disposition_mismatch"
}
);
}
#[test]
fn whole_document_conversion_rebases_the_raw_source_projection_too() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let SourceNodeLocalRest::Trs {
translation, scale, ..
} = &candidate.document().assets.source_skeleton.nodes[1].local_rest
else {
panic!("expected a trs source rest");
};
assert!((*translation - Vec3::new(0.0, 0.01, 0.0)).length() < 1e-8);
assert_eq!(*scale, Vec3::ONE);
}
#[test]
fn whole_document_proof_checks_raw_rewrites_without_using_normalized_bones_as_a_proxy() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut adjacent = candidate.document().clone();
let SourceNodeLocalRest::Trs { translation, .. } =
&mut adjacent.assets.source_skeleton.nodes[1].local_rest
else {
panic!("fixture source rest changed representation");
};
translation.y = f32::from_bits(translation.y.to_bits() + 1);
prove_scale(&doc, &ScaleCandidate::from_document(adjacent), &plan)
.expect("an adjacent-float raw rewrite remains within the published tolerance");
let mut corrupted = candidate.into_document();
let SourceNodeLocalRest::Trs { translation, .. } =
&mut corrupted.assets.source_skeleton.nodes[1].local_rest
else {
panic!("fixture source rest changed representation");
};
translation.y += 1.0;
assert_eq!(
prove_scale(&doc, &ScaleCandidate::from_document(corrupted), &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "field_disposition_mismatch"
}
);
}
#[test]
fn unavailable_source_rows_are_converted_but_never_become_replay_identity() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let mut matrix = Mat4::from_rotation_z(0.37);
matrix.w_axis = Vec4::new(3.25, -7.5, 11.0, 1.0);
doc.assets.source_skeleton.nodes.push(SourceNodeAsset::new(
77,
SourceNodeLocalRest::Matrix(matrix),
));
doc.assets.source_skeleton.coverage = SourceSkeletonCoverage::Unavailable;
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert_eq!(plan.ledger().source_topology().count(), 0);
assert!(
!plan
.ledger()
.field_rows()
.any(|row| matches!(row.target(), ScaleFieldTarget::SourceNodeRest { .. }))
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let SourceNodeLocalRest::Trs { translation, .. } =
&candidate.document().assets.source_skeleton.nodes[1].local_rest
else {
panic!("fixture source rest changed representation");
};
assert_eq!(*translation, Vec3::new(0.0, 0.01, 0.0));
let SourceNodeLocalRest::Matrix(converted_matrix) =
&candidate.document().assets.source_skeleton.nodes[2].local_rest
else {
panic!("best-effort matrix source rest changed representation");
};
assert_eq!(
converted_matrix
.w_axis
.truncate()
.to_array()
.map(f32::to_bits),
(matrix.w_axis.truncate() * 0.01)
.to_array()
.map(f32::to_bits),
"Unavailable matrix translation must still receive unit conversion"
);
assert_eq!(
[
converted_matrix.x_axis,
converted_matrix.y_axis,
converted_matrix.z_axis,
]
.map(|value| value.to_array().map(f32::to_bits)),
[matrix.x_axis, matrix.y_axis, matrix.z_axis]
.map(|value| value.to_array().map(f32::to_bits)),
"Unavailable matrix linear columns remain dimensionless"
);
assert_eq!(
[
converted_matrix.x_axis.w,
converted_matrix.y_axis.w,
converted_matrix.z_axis.w,
converted_matrix.w_axis.w,
]
.map(f32::to_bits),
[
matrix.x_axis.w,
matrix.y_axis.w,
matrix.z_axis.w,
matrix.w_axis.w,
]
.map(f32::to_bits),
"Unavailable matrix homogeneous components remain bit-exact"
);
let mut replay = doc.clone();
replay.assets.source_skeleton.nodes[1].source_node_index = 123_456;
replay.assets.source_skeleton.nodes[2].source_node_index = 654_321;
let replayed = build_scale_candidate(&replay, &plan).unwrap();
let SourceNodeLocalRest::Trs { translation, .. } =
&replayed.document().assets.source_skeleton.nodes[1].local_rest
else {
panic!("fixture source rest changed representation");
};
assert_eq!(*translation, Vec3::new(0.0, 0.01, 0.0));
let SourceNodeLocalRest::Matrix(replayed_matrix) =
&replayed.document().assets.source_skeleton.nodes[2].local_rest
else {
panic!("replayed best-effort matrix source rest changed representation");
};
assert_eq!(
replayed_matrix.to_cols_array().map(f32::to_bits),
converted_matrix.to_cols_array().map(f32::to_bits),
"Unavailable matrix conversion must ignore non-authoritative raw identity"
);
}
fn rest_only_leaf_rig() -> Vec<RigNode> {
vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(0), 2, Vec3::new(3.0, 0.0, 0.0)),
]
}
#[test]
fn an_un_rewritten_rest_translation_is_named_by_the_rest_translation_obligation() {
let doc = rig_document(&rest_only_leaf_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert!(
(candidate.document().skeleton.bones[2].rest.translation - Vec3::new(0.03, 0.0, 0.0))
.length()
< 1e-8
);
let mut broken = candidate.document().clone();
broken.skeleton.bones[2].rest.translation = Vec3::new(3.0, 0.0, 0.0);
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::RestTranslation,
..
}
));
}
#[test]
fn a_rest_translation_error_confined_to_z_is_still_named_by_the_rest_translation_obligation() {
let doc = rig_document(&rest_only_leaf_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.skeleton.bones[2].rest.translation = Vec3::new(0.03, 0.0, 1.0);
let broken = ScaleCandidate { document: broken };
let error = prove_scale(&doc, &broken, &plan).unwrap_err();
assert!(
matches!(
error,
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::RestTranslation,
..
}
),
"{error:?}"
);
}
#[test]
fn a_non_unit_composed_scale_on_an_affected_node_is_named_by_the_unit_scale_obligation() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.skeleton.bones[2].rest.scale = Vec3::splat(2.0);
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::UnitScale,
..
}
));
}
#[test]
fn a_composed_scale_anomaly_confined_to_z_is_still_named_by_the_unit_scale_obligation() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.skeleton.bones[2].rest.scale = Vec3::new(1.0, 1.0, 2.0);
let broken = ScaleCandidate { document: broken };
let error = prove_scale(&doc, &broken, &plan).unwrap_err();
assert!(
matches!(
error,
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::UnitScale,
..
}
),
"{error:?}"
);
}
#[test]
fn a_transform_only_attachment_with_a_correct_origin_but_a_wrong_linear_part_still_fails() {
let doc = compensated_document();
let capability = complete_capability();
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
};
let plan = plan_scale(&request).unwrap();
assert_eq!(plan.transform_only_attachments(), &[2]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.skeleton.bones[2].rest.scale = Vec3::new(-1.0, -1.0, 1.0);
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::TransformOnlyAffine,
..
}
));
}
#[test]
fn an_inverse_bind_whose_linear_part_was_not_conjugated_is_named_by_the_skin_obligation() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.assets.meshes[0].primitives[0].positions[0] = Vec3::ZERO;
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.assets.instances[0].skin_ibms[0].x_axis = Vec4::new(2.0, 0.0, 0.0, 0.0);
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::SkinMatrix,
..
}
));
}
#[test]
fn a_rest_scale_that_only_shows_up_under_animation_is_named_by_the_trajectory_obligation() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::ZERO),
rig(Some(1), 2, Vec3::ZERO),
];
let mut doc = rig_document(&nodes, &[0], 0, Mat4::IDENTITY);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 2,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![Vec3::new(1.0, 0.0, 0.0), Vec3::new(2.0, 0.0, 0.0)]),
}],
});
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.skeleton.bones[1].rest.scale = Vec3::splat(2.0);
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Trajectory,
..
}
));
}
fn flat_cubic_translation_track() -> Track {
Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::CubicSpline,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::ZERO, Vec3::ZERO, Vec3::new(0.0, 1000.0, 0.0), Vec3::new(0.0, 1000.0, 0.0), Vec3::ZERO, Vec3::ZERO, ]),
}
}
fn identity_conversion_plan(document: &Document, capability: &ScaleCapabilityFacts) -> ScalePlan {
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 1.0 },
document,
capability,
})
.unwrap()
}
#[test]
fn a_cubic_tangent_error_inside_element_tolerance_is_named_by_the_cubic_interior_obligation() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![flat_cubic_translation_track()],
});
let capability = complete_capability();
let plan = identity_conversion_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
let TrackValues::Vec3s(values) = &mut broken.clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
values[2].y = 1000.001;
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::CubicInterior,
..
}
));
}
#[test]
fn the_same_tangent_error_at_a_harvested_key_time_is_named_by_the_key_obligation() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![
flat_cubic_translation_track(),
Track {
bone: 1,
property: Property::Rotation,
interpolation: Interpolation::Linear,
times: vec![0.5],
values: TrackValues::Quats(vec![Quat::from_rotation_y(0.3)]),
},
],
});
let capability = complete_capability();
let plan = identity_conversion_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
let TrackValues::Vec3s(values) = &mut broken.clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
values[2].y = 1000.001;
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::KeyTranslation,
..
}
));
}
fn multi_joint_document() -> Document {
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
},
rig(Some(0), 1, Vec3::new(0.0, 100.0, 0.0)),
rig(Some(1), 2, Vec3::new(0.0, 100.0, 0.0)),
];
let mut doc = rig_document(&nodes, &[1, 2], 0, Mat4::IDENTITY);
doc.assets.instances[0].skin_ibms = vec![
Mat4::from_scale_rotation_translation(
Vec3::splat(100.0),
Quat::IDENTITY,
Vec3::new(0.0, -100.0, 0.0),
),
Mat4::from_scale_rotation_translation(
Vec3::splat(100.0),
Quat::IDENTITY,
Vec3::new(0.0, -200.0, 0.0),
),
];
doc.assets.meshes[0].primitives[0] = Primitive {
positions: vec![
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(-1.0, 0.0, 2.0),
Vec3::new(0.0, 2.0, 0.0),
Vec3::new(0.0, -3.0, 0.0),
],
joints: vec![[0, 0, 0, 0], [1, 0, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]],
weights: vec![
[1.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0],
[0.25, 0.75, 0.0, 0.0],
[0.4, 0.4, 0.0, 0.0],
],
..Primitive::default()
};
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![
Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::new(0.0, 100.0, 0.0),
Vec3::new(0.0, 200.0, 0.0),
]),
},
Track {
bone: 2,
property: Property::Translation,
interpolation: Interpolation::CubicSpline,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::ZERO, Vec3::new(0.0, 100.0, 0.0), Vec3::new(0.0, 60.0, 0.0), Vec3::new(0.0, 60.0, 0.0), Vec3::new(0.0, 300.0, 0.0), Vec3::ZERO, ]),
},
],
});
doc
}
fn multi_joint_plan(document: &Document, capability: &ScaleCapabilityFacts) -> ScalePlan {
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document,
capability,
})
.unwrap()
}
#[test]
fn skinned_bounds_blends_and_normalises_multi_joint_weights() {
let doc = multi_joint_document();
let slots = [
unrounded_slot(Mat4::from_translation(Vec3::new(0.0, 1.0, 0.0))),
unrounded_slot(Mat4::from_translation(Vec3::new(0.0, 3.0, 0.0))),
];
let mut accumulator = BoundsAccumulator::default();
accumulate_skinned_bounds(
0,
0,
&doc.assets.meshes[0].primitives[0],
&slots,
&mut accumulator,
)
.unwrap();
let (min, max) = accumulator
.finish()
.expect("multi-joint fixture has weighted vertices");
assert!(
(min - Vec3::new(-1.0, -1.0, 0.0)).length() < 1e-5,
"min {min:?}"
);
assert!(
(max - Vec3::new(1.0, 4.5, 2.0)).length() < 1e-5,
"max {max:?}"
);
}
fn unrounded_slot(matrix: Mat4) -> SkinSlot {
SkinSlot::compose(matrix, Mat4::IDENTITY, 0.0)
}
fn one_influence_primitive(weight: f32) -> Primitive {
Primitive {
positions: vec![Vec3::new(1.0, 0.0, 0.0)],
joints: vec![[0, 0, 0, 0]],
weights: vec![[weight, 0.0, 0.0, 0.0]],
..Primitive::default()
}
}
#[test]
fn an_infinite_skin_weight_is_rejected_as_a_weight_not_as_a_skinned_result() {
let slots = [unrounded_slot(Mat4::IDENTITY)];
let mut accumulator = BoundsAccumulator::default();
assert_eq!(
accumulate_skinned_bounds(
7,
3,
&one_influence_primitive(f32::INFINITY),
&slots,
&mut accumulator,
),
Err(ScaleError::InvalidSkinnedPrimitive {
instance_index: 7,
primitive_index: 3,
reason: "non_finite_weight",
})
);
assert_eq!(
accumulate_skinned_bounds(
7,
3,
&one_influence_primitive(f32::NAN),
&slots,
&mut accumulator,
),
Err(ScaleError::InvalidSkinnedPrimitive {
instance_index: 7,
primitive_index: 3,
reason: "non_finite_weight",
})
);
}
#[test]
fn bounds_provenance_is_invariant_when_weights_are_rescaled_to_subnormals() {
let slots = [
SkinSlot {
matrix: Mat4::IDENTITY,
absolute: Mat4::IDENTITY,
rounding_magnitude: 6.0,
},
SkinSlot {
matrix: Mat4::IDENTITY,
absolute: Mat4::IDENTITY,
rounding_magnitude: 3.0,
},
];
let measure = |weights: [f32; 4]| {
let primitive = Primitive {
positions: vec![Vec3::splat(0.5)],
joints: vec![[0, 1, 0, 0]],
weights: vec![weights],
..Primitive::default()
};
let mut accumulator = BoundsAccumulator::default();
accumulate_skinned_bounds(0, 0, &primitive, &slots, &mut accumulator).unwrap();
let magnitude = accumulator.rounding_magnitude();
(accumulator.finish(), magnitude)
};
let tiny = f32::from_bits(1);
let two_tiny = f32::from_bits(2);
let scale = f32::from_bits(4);
assert!(tiny.is_subnormal() && two_tiny.is_subnormal());
assert_eq!(0.25 * scale, tiny);
assert_eq!(0.5 * scale, two_tiny);
let ordinary = measure([0.25, 0.5, 0.0, 0.0]);
let subnormal = measure([tiny, two_tiny, 0.0, 0.0]);
assert_eq!(ordinary, (Some((Vec3::splat(0.5), Vec3::splat(0.5))), 4.0));
assert_eq!(subnormal, ordinary);
}
#[test]
fn weight_normalization_does_not_overflow_at_f32_max() {
assert!((f32::MAX + f32::MAX).is_infinite());
let primitive = Primitive {
positions: vec![Vec3::splat(0.5)],
joints: vec![[0, 0, 0, 0]],
weights: vec![[f32::MAX, f32::MAX, 0.0, 0.0]],
..Primitive::default()
};
let mut accumulator = BoundsAccumulator::default();
accumulate_skinned_bounds(
7,
3,
&primitive,
&[unrounded_slot(Mat4::IDENTITY)],
&mut accumulator,
)
.unwrap();
let (min, max) = accumulator.finish().unwrap();
assert_eq!(min, Vec3::splat(0.5));
assert_eq!(max, Vec3::splat(0.5));
}
#[test]
fn widened_weight_accumulation_stays_convex_at_f32_max() {
let stored: [f32; 2] = [19.0 / 64.0, 9.0 / 64.0];
let weight_scale = stored[0].max(stored[1]);
let scaled = [stored[0] / weight_scale, stored[1] / weight_scale];
let scaled_sum = scaled[0] + scaled[1];
let coefficients = [scaled[0] / scaled_sum, scaled[1] / scaled_sum];
assert!(f64::from(coefficients[0]) + f64::from(coefficients[1]) > 1.0);
let position = Vec3::splat(f32::MAX);
assert!(position.is_finite());
assert!(
!(coefficients[0] * position + coefficients[1] * position).is_finite(),
"the binary32 counterexample no longer overflows"
);
let primitive = Primitive {
positions: vec![position],
joints: vec![[0, 1, 0, 0]],
weights: vec![[stored[0], stored[1], 0.0, 0.0]],
..Primitive::default()
};
let mut accumulator = BoundsAccumulator::default();
accumulate_skinned_bounds(
7,
3,
&primitive,
&[
unrounded_slot(Mat4::IDENTITY),
unrounded_slot(Mat4::IDENTITY),
],
&mut accumulator,
)
.unwrap();
let (min, max) = accumulator.finish().unwrap();
assert_eq!(min, position);
assert_eq!(max, position);
}
#[test]
fn every_public_scale_boundary_rejects_a_negative_skin_weight() {
let doc = multi_joint_document();
let capability = complete_capability();
let operations = [
ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
ScaleOperation::WholeDocumentLinearUnits { factor: 2.0 },
];
let mut signed = doc.clone();
signed.assets.meshes[0].primitives[0].weights[3][3] = -f32::from_bits(1);
let expected = ScaleError::NegativeSkinWeight {
mesh_index: 0,
primitive_index: 0,
vertex_index: 3,
influence_index: 3,
};
for operation in operations {
let plan = plan_scale(&ScaleRequest {
operation,
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert_eq!(
plan_scale(&ScaleRequest {
operation,
document: &signed,
capability: &capability,
})
.unwrap_err(),
expected
);
assert_eq!(build_scale_candidate(&signed, &plan).unwrap_err(), expected);
assert_eq!(
prove_scale(&signed, &candidate, &plan).unwrap_err(),
expected
);
assert_eq!(
prove_scale(&doc, &ScaleCandidate::from_document(signed.clone()), &plan).unwrap_err(),
expected
);
}
let mut negative_zero = doc.clone();
negative_zero.assets.meshes[0].primitives[0].weights[3][3] = -0.0;
for operation in operations {
let plan = plan_scale(&ScaleRequest {
operation,
document: &negative_zero,
capability: &capability,
})
.expect("negative zero is a zero skin influence");
let candidate = build_scale_candidate(&negative_zero, &plan)
.expect("candidate construction accepts negative zero");
prove_scale(&negative_zero, &candidate, &plan)
.expect("proof accepts negative zero in source and candidate");
}
let mut non_finite = doc.clone();
non_finite.assets.meshes[0].primitives[0].weights[3][3] = f32::NEG_INFINITY;
for operation in operations {
let plan = plan_scale(&ScaleRequest {
operation,
document: &non_finite,
capability: &capability,
})
.expect("negative infinity is not a finite-negative plan refusal");
let candidate = build_scale_candidate(&non_finite, &plan)
.expect("candidate construction preserves non-finite routing");
assert!(matches!(
prove_scale(&non_finite, &candidate, &plan),
Err(ScaleError::InvalidSkinnedPrimitive {
reason: "non_finite_weight",
..
})
));
}
}
#[test]
fn representative_finite_negative_weights_are_all_refused() {
let doc = multi_joint_document();
let capability = complete_capability();
for weight in [
-f32::from_bits(1),
-f32::MIN_POSITIVE,
-0.1,
-0.25,
-f32::MAX,
] {
let mut signed = doc.clone();
signed.assets.meshes[0].primitives[0].weights[0][0] = weight;
assert!(matches!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 2.0 },
document: &signed,
capability: &capability,
}),
Err(ScaleError::NegativeSkinWeight { .. })
));
}
}
#[test]
fn a_negative_weight_reports_its_nonzero_mesh_and_primitive_location() {
let mut doc = multi_joint_document();
doc.assets.meshes.push(MeshAsset {
name: "later mesh".into(),
source_mesh_index: 99,
primitives: vec![
Primitive::default(),
Primitive::default(),
Primitive {
positions: vec![Vec3::ZERO],
joints: vec![[0, 0, 0, 0]],
weights: vec![[0.0, 0.0, -f32::from_bits(1), 0.0]],
..Primitive::default()
},
],
});
let capability = complete_capability();
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 2.0 },
document: &doc,
capability: &capability,
})
.unwrap_err(),
ScaleError::NegativeSkinWeight {
mesh_index: 1,
primitive_index: 2,
vertex_index: 0,
influence_index: 2,
}
);
}
fn rotating_rig_document(
rotations: [Quat; 2],
factor: f32,
locals: [Vec3; 2],
points: &[Vec3],
weights: &[[f32; 4]],
) -> Document {
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(factor),
},
RigNode {
parent: Some(0),
source_node_index: 1,
translation: locals[0],
rotation: rotations[0],
scale: Vec3::ONE,
},
RigNode {
parent: Some(1),
source_node_index: 2,
translation: locals[1],
rotation: rotations[1],
scale: Vec3::ONE,
},
];
let root = Mat4::from_scale(Vec3::splat(factor));
let first = root * Mat4::from_rotation_translation(rotations[0], locals[0]);
let second = first * Mat4::from_rotation_translation(rotations[1], locals[1]);
let mut doc = rig_document(&nodes, &[1, 2], 0, Mat4::IDENTITY);
doc.assets.instances[0].skin_ibms = vec![first.inverse(), second.inverse()];
let primitive = &mut doc.assets.meshes[0].primitives[0];
primitive.positions = points.to_vec();
primitive.joints = vec![[0, 1, 0, 0]; points.len()];
primitive.weights = weights.to_vec();
doc
}
fn rest_bind_plan(document: &Document, expected_factor: f64) -> ScalePlan {
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor,
},
document,
capability: &complete_capability(),
})
.expect("the rotating rig plans at its own observed factor")
}
fn reproducer_document() -> Document {
rotating_rig_document(
[
Quat::from_xyzw(-0.81788284, 0.343121, -0.45392478, -0.085369624),
Quat::from_xyzw(-0.12301501, 0.043325406, -0.015209139, 0.991342),
],
3190.0,
[Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.3, 0.4, 0.5)],
&[
Vec3::new(2827.01, -5.982, 3162.68),
Vec3::new(-1000.0, 7.5, -2000.0),
],
&[[1.0, 0.0, 0.0, 0.0], [0.5, 0.5, 0.0, 0.0]],
)
}
#[test]
fn a_rotated_rig_proves_a_correct_candidate_whose_bound_component_is_small() {
let doc = reproducer_document();
let plan = rest_bind_plan(&doc, 3190.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan)
.expect("a correct candidate under rotation must prove");
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
assert!(
proof.bounds.max() > policy.scalar_tolerance(5.982, 5.982),
"bounds residual {} no longer exceeds the per-axis band",
proof.bounds.max()
);
assert!(
proof.skin_matrix.max() > policy.scalar_tolerance(1.0, 1.0),
"skin residual {} no longer exceeds the near-identity band",
proof.skin_matrix.max()
);
}
#[test]
fn a_synthetic_corner_from_three_vertices_does_not_shrink_the_bounds_tolerance() {
let doc = rotating_rig_document(
[
Quat::from_xyzw(0.5992112, -0.6357324, 0.3481601, 0.33996314),
Quat::from_xyzw(0.56926024, -0.14522065, -0.20381902, 0.7831421),
],
3190.0,
[Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.3, 0.4, 0.5)],
&[
Vec3::new(3000.0, 0.001, 0.002),
Vec3::new(0.001, 3000.0, 0.003),
Vec3::new(0.002, 0.003, 3000.0),
],
&[[1.0, 0.0, 0.0, 0.0]; 3],
);
let plan = rest_bind_plan(&doc, 3190.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan)
.expect("a synthetic corner must not shrink the tolerance");
assert!(
proof.bounds.max() > policy_scalar_tolerance_at(0.002),
"bounds residual {} no longer exceeds the corner-derived band",
proof.bounds.max()
);
}
#[test]
fn a_joint_far_from_the_geometry_it_carries_still_proves_its_bounds() {
let doc = far_joint_document();
let plan = rest_bind_plan(&doc, 3190.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof =
prove_scale(&doc, &candidate, &plan).expect("a distant joint's bounds must still prove");
assert!(
proof.bounds.max() > 4.0 * 1.0 * f64::from(f32::EPSILON),
"bounds residual {} no longer exceeds four ulps of the skinned magnitude",
proof.bounds.max()
);
}
fn far_joint_document() -> Document {
rotating_rig_document(
[
Quat::from_xyzw(0.84815156, -0.23002678, -0.2828825, -0.3843229),
Quat::from_xyzw(0.6066518, -0.10115066, -0.7511764, 0.23974188),
],
3190.0,
[Vec3::new(0.0, 1000.0, 0.0), Vec3::new(200.0, -300.0, 400.0)],
&[Vec3::new(0.5, -0.25, 0.125), Vec3::new(-0.75, 0.5, -0.25)],
&[[1.0, 0.0, 0.0, 0.0], [0.5, 0.5, 0.0, 0.0]],
)
}
fn rig_skin_slots(document: &Document) -> Vec<SkinSlot> {
let worlds = rest_world_pose(&document.skeleton).expect("the rig composes");
let instance = &document.assets.instances[0];
instance
.skin_joints
.iter()
.enumerate()
.map(|(slot, &joint)| {
SkinSlot::compose(
worlds.bones[joint].matrix,
instance_bind(document, instance, slot, joint).expect("the rig binds"),
worlds.bones[joint].translation_rounding_magnitude,
)
})
.collect()
}
fn rig_slot_magnitude(document: &Document) -> f64 {
rig_skin_slots(document)
.iter()
.map(|slot| slot.rounding_magnitude)
.fold(0.0, f64::max)
}
fn rig_bounds_magnitude(document: &Document) -> f64 {
let slots = rig_skin_slots(document);
let instance = &document.assets.instances[0];
let mut accumulator = BoundsAccumulator::default();
for (primitive_index, primitive) in document.assets.meshes[instance.mesh]
.primitives
.iter()
.enumerate()
{
accumulate_skinned_bounds(0, primitive_index, primitive, &slots, &mut accumulator)
.expect("the rig skins");
}
accumulator.rounding_magnitude()
}
fn far_joint_conversion_at(factor: f64) -> (Document, ScalePlan, ScaleCandidate) {
let doc = far_joint_document();
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor },
document: &doc,
capability: &capability,
})
.expect("a whole-document conversion plans at any positive factor");
let candidate = build_scale_candidate(&doc, &plan).unwrap();
(doc, plan, candidate)
}
#[test]
fn the_far_joint_rig_admits_a_four_unit_bind_shift_and_refuses_the_next_one_up() {
let doc = far_joint_document();
let plan = rest_bind_plan(&doc, 3190.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let shifted = |shift: f32| {
let mut broken = candidate.document().clone();
broken.assets.instances[0].skin_ibms[0].w_axis.x += shift;
prove_scale(&doc, &ScaleCandidate { document: broken }, &plan)
};
const FLOOR: f32 = 4.09375;
let next_up = f32::from_bits(FLOOR.to_bits() + 1);
shifted(FLOOR).expect("a 4.09375-unit bind shift is inside the documented floor");
let error = shifted(next_up)
.expect_err("the next binary32 above the floor, 4.0937505, must be refused");
assert!(
matches!(
error,
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::SkinMatrix,
..
}
),
"expected a refused skin matrix just above the floor, got {error:?}"
);
}
const HALF_TURN_Z: Mat4 = Mat4::from_cols(
Vec4::new(-1.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, -1.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0, 0.0),
Vec4::new(0.0, 0.0, 0.0, 1.0),
);
fn cancelling_blend_document() -> Document {
cancelling_blend_document_reaching(1000.0)
}
fn cancelling_blend_document_reaching(reach: f32) -> Document {
scaled_cancelling_blend_document(1.0, reach)
}
fn scaled_cancelling_blend_document(scale: f32, reach: f32) -> Document {
let composed = Mat4::from_scale(Vec3::splat(scale));
composed_slot_document(
CANCELLING_BLEND_ROTATIONS,
3190.0,
CANCELLING_BLEND_LOCALS,
[composed, composed * HALF_TURN_Z],
&[Vec3::new(reach, 0.0, 0.0)],
&[[0.5, 0.5, 0.0, 0.0]],
)
}
fn amplifying_blend_document() -> Document {
composed_slot_document(
CANCELLING_BLEND_ROTATIONS,
3190.0,
CANCELLING_BLEND_LOCALS,
[Mat4::IDENTITY, HALF_TURN_Z],
&[Vec3::new(1000.0, 0.0, 0.0)],
&[[1.0, -0.99999, 0.0, 0.0]],
)
}
fn cancelling_numerator_blend_document() -> Document {
composed_slot_document(
CANCELLING_BLEND_ROTATIONS,
3190.0,
CANCELLING_BLEND_LOCALS,
[Mat4::IDENTITY, Mat4::IDENTITY],
&[Vec3::new(1000.0, 0.0, 0.0)],
&[[1.0, -0.99999, 0.0, 0.0]],
)
}
const CANCELLING_BLEND_ROTATIONS: [Quat; 2] = [
Quat::from_xyzw(-0.81788284, 0.343121, -0.45392478, -0.085369624),
Quat::from_xyzw(-0.12301501, 0.043325406, -0.015209139, 0.991342),
];
const CANCELLING_BLEND_LOCALS: [Vec3; 2] =
[Vec3::new(0.0, 1e-6, 0.0), Vec3::new(0.3e-6, 0.4e-6, 0.5e-6)];
fn composed_slot_document(
rotations: [Quat; 2],
factor: f32,
locals: [Vec3; 2],
composed: [Mat4; 2],
points: &[Vec3],
weights: &[[f32; 4]],
) -> Document {
let mut doc = rotating_rig_document(rotations, factor, locals, points, weights);
let first = Mat4::from_scale(Vec3::splat(factor))
* Mat4::from_rotation_translation(rotations[0], locals[0]);
let second = first * Mat4::from_rotation_translation(rotations[1], locals[1]);
doc.assets.instances[0].skin_ibms = [first, second]
.into_iter()
.zip(composed)
.map(|(world, composed)| (world.as_dmat4().inverse() * composed.as_dmat4()).as_mat4())
.collect();
doc
}
#[test]
fn two_slots_whose_composed_binds_cancel_a_vertex_still_prove_its_bounds() {
let doc = cancelling_blend_document();
let plan = rest_bind_plan(&doc, 3190.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let worlds = rest_world_pose(&doc.skeleton).unwrap();
let instance = &doc.assets.instances[0];
let mut blended = Vec3::ZERO;
let mut slot_magnitude = 0.0f64;
for slot in 0..2 {
let joint = instance.skin_joints[slot];
let bind = instance_bind(&doc, instance, slot, joint).unwrap();
let composed = SkinSlot::compose(
worlds.bones[joint].matrix,
bind,
worlds.bones[joint].translation_rounding_magnitude,
);
blended += 0.5
* composed
.matrix
.transform_point3(Vec3::new(1000.0, 0.0, 0.0));
slot_magnitude = slot_magnitude.max(composed.rounding_magnitude);
}
assert!(
blended.length() < 1.0 && slot_magnitude < 2.0,
"the blend no longer cancels ({blended}) or the slots are no longer near-unit \
({slot_magnitude}): the vertex is then covered by another stage",
);
let proof = prove_scale(&doc, &candidate, &plan)
.expect("a correct candidate whose slots cancel a vertex must still prove");
assert!(
proof.bounds.max() > 4.0 * slot_magnitude.max(1.0) * f64::from(f32::EPSILON),
"bounds residual {} no longer exceeds four ulps of every stage but the vertex",
proof.bounds.max()
);
}
#[test]
fn two_slots_with_a_scaled_composition_cancel_a_vertex_and_still_prove_its_bounds() {
let doc = scaled_cancelling_blend_document(1024.0, 65536.0);
let plan = rest_bind_plan(&doc, 3190.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let slots = rig_skin_slots(&doc);
let position = doc.assets.meshes[0].primitives[0].positions[0];
let blended: Vec3 = slots
.iter()
.map(|slot| 0.5 * slot.matrix.transform_point3(position))
.sum();
let composed_scale = slots[0].matrix.x_axis.truncate().length();
let stages_without_the_transform = slots
.iter()
.map(|slot| slot.rounding_magnitude)
.fold(0.0, f64::max)
.max(f64::from(position.length()))
.max(f64::from(blended.length()));
assert!(
composed_scale > 512.0 && blended.length() < 0.01 * position.length(),
"the composition no longer scales ({composed_scale}) or the blend no longer cancels \
({blended}): the transform's operand product is then covered by another stage",
);
assert!(
stages_without_the_transform < 1e5,
"some stage other than the transform now reaches {stages_without_the_transform}, so \
this fixture no longer isolates the transform's operand product",
);
let proof = prove_scale(&doc, &candidate, &plan).expect(
"a correct candidate whose scaled slots cancel a vertex must still prove: the \
transform ran on abs(W * B) * abs(p), and that is what the base must name",
);
assert!(
proof.bounds.max() > 4.0 * stages_without_the_transform * f64::from(f32::EPSILON),
"bounds residual {} no longer exceeds four ulps of every stage but the transform's \
own operand product",
proof.bounds.max()
);
}
#[test]
fn both_signed_blend_counterexamples_are_rejected_for_both_operations() {
let capability = complete_capability();
let operations = [
ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 3190.0,
},
ScaleOperation::WholeDocumentLinearUnits { factor: 3190.0 },
];
for doc in [
amplifying_blend_document(),
cancelling_numerator_blend_document(),
] {
for operation in operations {
assert_eq!(
plan_scale(&ScaleRequest {
operation,
document: &doc,
capability: &capability,
})
.unwrap_err(),
ScaleError::NegativeSkinWeight {
mesh_index: 0,
primitive_index: 0,
vertex_index: 0,
influence_index: 1,
}
);
}
}
}
#[test]
fn a_growing_conversion_reads_the_candidate_s_magnitude_not_the_source_s_unrebased() {
let (doc, plan, candidate) = far_joint_conversion_at(3190.0);
let source_slot = rig_slot_magnitude(&doc);
let candidate_slot = rig_slot_magnitude(candidate.document());
assert!(
candidate_slot > 1000.0 * source_slot,
"the candidate is no longer the larger side, so this fixture no longer refuses \
the source-side reading: {candidate_slot} / {source_slot}",
);
let proof = prove_scale(&doc, &candidate, &plan).expect(
"the two documents' composition magnitudes are 3190x apart, and the candidate's \
arithmetic is what both obligations must be given room for",
);
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
let source_band = policy.f32_rounded_tolerance(0.0, 0.0, source_slot);
assert!(
proof.skin_matrix.max() > source_band,
"skin matrix residual {} no longer exceeds the {source_band} the source side buys, \
so this fixture no longer kills the unrebased source reading",
proof.skin_matrix.max(),
);
assert!(
proof.bounds.max() > policy.f32_rounded_tolerance(0.0, 0.0, rig_bounds_magnitude(&doc)),
"bounds residual {} no longer exceeds what the source side buys",
proof.bounds.max(),
);
}
#[test]
fn a_shrinking_conversion_rebases_the_skin_matrix_magnitude_by_the_factor() {
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
for (factor, shift) in [(0.01f64, 1e-4f32), (1e-4, 1e-6)] {
let (doc, plan, candidate) = far_joint_conversion_at(factor);
let source_slot = rig_slot_magnitude(&doc);
let candidate_slot = rig_slot_magnitude(candidate.document());
assert!(
source_slot > 50.0 * candidate_slot,
"the source side is no longer the larger one at {factor}, so this fixture no \
longer separates the rebased magnitude from the max: {source_slot} / \
{candidate_slot}",
);
prove_scale(&doc, &candidate, &plan)
.expect("a correct candidate under a shrinking conversion must still prove");
let mut broken = candidate.document().clone();
broken.assets.instances[0].skin_ibms[0].w_axis.x += shift;
let broken = ScaleCandidate { document: broken };
let error = prove_scale(&doc, &broken, &plan)
.expect_err("a bind shift above the rebased band must be refused");
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::SkinMatrix,
observed,
tolerance,
} = error
else {
panic!("expected a refused skin matrix at {factor}, got {error:?}");
};
assert!(
observed > tolerance,
"skin matrix band moved at {factor}: observed {observed}, tolerance {tolerance}"
);
assert!(
observed < policy.f32_rounded_tolerance(0.0, 0.0, source_slot),
"skin matrix residual {observed} now exceeds what the unrebased source \
magnitude buys too, so this fixture no longer kills the max at {factor}",
);
}
}
#[test]
fn a_shrinking_conversion_rebases_the_bounds_magnitude_by_the_factor() {
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
for &factor in &[0.01f64, 1e-4] {
let doc = cancelling_blend_document_reaching(1e6);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor },
document: &doc,
capability: &capability,
})
.expect("a whole-document conversion plans at any positive factor");
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let source_bounds = rig_bounds_magnitude(&doc);
let candidate_bounds = rig_bounds_magnitude(candidate.document());
assert!(
source_bounds > 50.0 * candidate_bounds,
"the source side is no longer the larger one at {factor}: {source_bounds} / \
{candidate_bounds}",
);
prove_scale(&doc, &candidate, &plan)
.expect("a correct candidate under a shrinking conversion must still prove");
let mut broken = candidate.document().clone();
broken.assets.meshes[0].primitives[0].weights[0] = [0.5 + 1e-6, 0.5 - 1e-6, 0.0, 0.0];
let broken = ScaleCandidate { document: broken };
let error = prove_scale(&doc, &broken, &plan)
.expect_err("a reweighted blend above the rebased band must be refused");
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Bounds,
observed,
tolerance,
} = error
else {
panic!("expected a refused bound at {factor}, got {error:?}");
};
assert!(
observed > tolerance,
"bounds band moved at {factor}: observed {observed}, tolerance {tolerance}"
);
assert!(
observed < policy.f32_rounded_tolerance(0.0, 0.0, source_bounds),
"bounds residual {observed} now exceeds what the unrebased source magnitude \
buys too, so this fixture no longer kills the max at {factor}",
);
}
}
#[test]
fn a_growing_conversion_provisions_a_rebased_source_magnitude() {
let doc = rotating_rig_document(
[
Quat::from_xyzw(-0.4142528, 0.36644182, -0.4827506, -0.67901903),
Quat::from_xyzw(0.4266807, 0.07081859, 0.56996834, -0.698616),
],
1.0,
[
Vec3::new(-1.0505125e-7, 2.1474386e-6, 1.2482113e-6),
Vec3::new(-2.8671836e-4, -6.197663e-5, -3.234957e-5),
],
&[
Vec3::new(6.400283e-6, -6.33053e-6, -1.0025909e-6),
Vec3::new(9.629462e-5, -4.045991e-5, 4.1240072e-4),
],
&[[0.5, 0.5, 0.0, 0.0]; 2],
);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 3190.0 },
document: &doc,
capability: &capability,
})
.expect("a whole-document conversion plans at any positive factor");
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let source_slot = rig_slot_magnitude(&doc);
let candidate_slot = rig_slot_magnitude(candidate.document());
let source_bounds = rig_bounds_magnitude(&doc);
let candidate_bounds = rig_bounds_magnitude(candidate.document());
assert!(
3190.0 * source_slot > 100.0 * candidate_slot
&& 3190.0 * source_bounds > 100.0 * candidate_bounds,
"the rebased source magnitude no longer runs away from the candidate's, so this \
rig no longer reaches the regime: slots {source_slot} / {candidate_slot}, bounds \
{source_bounds} / {candidate_bounds}",
);
let proof = prove_scale(&doc, &candidate, &plan)
.expect("a correct candidate under a growing conversion must prove");
assert!(
proof.skin_matrix.comparisons() > 0 && proof.bounds.comparisons() > 0,
"the growing-conversion fixture must evaluate both provisioned obligations",
);
}
#[test]
fn a_rig_whose_skinned_extent_passes_the_square_root_of_f32_max_still_proves() {
let doc = rotating_rig_document(
[
Quat::from_xyzw(0.84815156, -0.23002678, -0.2828825, -0.3843229),
Quat::from_xyzw(0.6066518, -0.10115066, -0.7511764, 0.23974188),
],
1e3,
[Vec3::new(0.0, 1000.0, 0.0), Vec3::new(200.0, -300.0, 400.0)],
&[Vec3::new(1.9e19, 0.0, 0.0), Vec3::new(-0.75, 0.5, -0.25)],
&[[1.0, 0.0, 0.0, 0.0], [0.5, 0.5, 0.0, 0.0]],
);
let plan = rest_bind_plan(&doc, 1e3);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).expect(
"a rig whose skinned extent exceeds sqrt(f32::MAX) must still prove: the extent \
itself is finite, and squaring it is the proof's own arithmetic",
);
assert!(
proof.bounds.max().is_finite(),
"bounds residual {} is not finite",
proof.bounds.max()
);
}
fn far_joint_overflow_document() -> (Document, Mat4, Mat4) {
let rotations = [
Quat::from_xyzw(0.84815156, -0.23002678, -0.2828825, -0.3843229),
Quat::from_xyzw(0.6066518, -0.10115066, -0.7511764, 0.23974188),
];
let locals = [Vec3::new(0.0, 1e35, 0.0), Vec3::new(0.3, 0.4, 0.5)];
let mut doc = rotating_rig_document(
rotations,
3190.0,
locals,
&[Vec3::new(1.0, 0.0, 0.0), Vec3::new(-0.75, 0.5, -0.25)],
&[[1.0, 0.0, 0.0, 0.0], [0.5, 0.5, 0.0, 0.0]],
);
let first = Mat4::from_scale(Vec3::splat(3190.0))
* Mat4::from_rotation_translation(rotations[0], locals[0]);
let second = first * Mat4::from_rotation_translation(rotations[1], locals[1]);
let inverse_binds = vec![
first.as_dmat4().inverse().as_mat4(),
second.as_dmat4().inverse().as_mat4(),
];
doc.assets.instances[0].skin_ibms.clone_from(&inverse_binds);
(doc, first, inverse_binds[0])
}
#[test]
fn a_rig_whose_composition_operands_overflow_f32_still_proves() {
let (doc, world, inverse_bind) = far_joint_overflow_document();
assert!(
!largest_entry(mat4_abs(world) * mat4_abs(inverse_bind)).is_finite(),
"the fixture no longer overflows the f32 lane computation, so it no longer \
exercises the fallback",
);
assert!(
(world * inverse_bind).is_finite(),
"the composition itself must stay finite: an overflowing product is a different \
failure, and one that is allowed to be refused",
);
let slot = SkinSlot::compose(world, inverse_bind, 0.0);
assert!(
slot.rounding_magnitude.is_finite(),
"rounding magnitude {} is not finite",
slot.rounding_magnitude
);
let plan = rest_bind_plan(&doc, 3190.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).expect(
"a correct candidate whose composition operands overflow f32 must still prove: \
both operands are finite and so is their product",
);
assert!(
proof.skin_matrix.max().is_finite() && proof.bounds.max().is_finite(),
"skin {} / bounds {} residual is not finite",
proof.skin_matrix.max(),
proof.bounds.max()
);
}
fn cancelling_chain_overflow_document() -> Document {
let rotations = [
Quat::from_xyzw(0.84815156, -0.23002678, -0.2828825, -0.3843229),
Quat::from_xyzw(0.6066518, -0.10115066, -0.7511764, 0.23974188),
];
let locals = [
Vec3::new(0.0, 3e35, 0.0),
Vec3::new(5.1827603e34, 1.7963013e35, -2.3462077e35),
];
let mut doc = rotating_rig_document(
rotations,
1e3,
locals,
&[Vec3::new(1.0, 0.0, 0.0), Vec3::new(-0.75, 0.5, -0.25)],
&[[1.0, 0.0, 0.0, 0.0], [0.5, 0.5, 0.0, 0.0]],
);
let first = Mat4::from_scale(Vec3::splat(1e3))
* Mat4::from_rotation_translation(rotations[0], locals[0]);
let second = first * Mat4::from_rotation_translation(rotations[1], locals[1]);
doc.assets.instances[0].skin_ibms = vec![
first.as_dmat4().inverse().as_mat4(),
second.as_dmat4().inverse().as_mat4(),
];
doc
}
fn cancelling_chain_document(factor: f32) -> Document {
rotating_rig_document(
[
Quat::from_xyzw(-0.81788284, 0.343121, -0.45392478, -0.085369624),
Quat::from_xyzw(-0.12301501, 0.043325406, -0.015209139, 0.991342),
],
factor,
[
Vec3::new(0.0, 1000.0, 0.0),
Vec3::new(483.7628, 749.96, 451.14697),
],
&[Vec3::new(0.5, -0.25, 0.125), Vec3::new(-0.75, 0.5, -0.25)],
&[[1.0, 0.0, 0.0, 0.0], [0.5, 0.5, 0.0, 0.0]],
)
}
#[test]
fn a_parent_chain_whose_translations_cancel_still_proves_its_skin() {
let doc = cancelling_chain_document(3190.0);
let plan = rest_bind_plan(&doc, 3190.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let worlds = rest_world_pose(&doc.skeleton).unwrap();
let instance = &doc.assets.instances[0];
let joint = instance.skin_joints[1];
let bind = instance_bind(&doc, instance, 1, joint).unwrap();
let product = product_operand_magnitude(worlds.bones[joint].matrix, bind);
assert!(
product < 2.0,
"the composed product's own operands are no longer near-unit: {product}"
);
assert!(
worlds.bones[joint].translation_rounding_magnitude > 1e6,
"the parent chain no longer cancels a large translation: {}",
worlds.bones[joint].translation_rounding_magnitude
);
let proof = prove_scale(&doc, &candidate, &plan)
.expect("a correct candidate under a cancelling parent chain must prove");
assert!(
proof.skin_matrix.max() > 4.0 * product * f64::from(f32::EPSILON),
"skin residual {} no longer exceeds four ulps of the composition's own operands",
proof.skin_matrix.max()
);
let world_translation = worlds.bones[joint].matrix.w_axis.truncate().length() as f64;
assert!(
proof.rest_translation.max()
> ScaleTolerancePolicy::APPENDIX_D_V6
.scalar_tolerance(world_translation, world_translation),
"rest translation residual {} no longer exceeds the per-axis band its own \
translation buys",
proof.rest_translation.max()
);
}
fn cancelling_chain_conversion_at(factor: f64) -> (Document, ScalePlan, ScaleCandidate) {
let doc = cancelling_chain_document(1.0);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor },
document: &doc,
capability: &capability,
})
.expect("a whole-document conversion plans at any positive factor");
let candidate = build_scale_candidate(&doc, &plan).unwrap();
(doc, plan, candidate)
}
fn cancelling_chain_conversion() -> (Document, ScalePlan, ScaleCandidate) {
cancelling_chain_conversion_at(3190.0)
}
#[test]
fn a_cancelling_chain_under_conversion_holds_rest_translation_to_the_candidate_side() {
let (doc, plan, candidate) = cancelling_chain_conversion();
let source_chain =
rest_world_pose(&doc.skeleton).unwrap().bones[2].translation_rounding_magnitude;
let candidate_chain = rest_world_pose(&candidate.document().skeleton)
.unwrap()
.bones[2]
.translation_rounding_magnitude;
assert!(
candidate_chain > 1e3 * source_chain,
"the two sides' chains are no longer a factor apart: {source_chain} / \
{candidate_chain}",
);
let proof = prove_scale(&doc, &candidate, &plan).expect(
"the two documents' chain magnitudes are 3190x apart, and the candidate's \
arithmetic is what this obligation must be given room for",
);
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
assert!(
proof.rest_translation.max() > policy.f32_rounded_tolerance(0.0, 0.0, source_chain),
"rest translation residual {} no longer exceeds what the source side's chain \
buys, so this fixture no longer separates the two sides",
proof.rest_translation.max()
);
}
#[test]
fn a_shrinking_conversion_holds_rest_translation_to_the_candidate_s_own_chain() {
let (doc, plan, candidate) = unskinned_sibling_conversion(false, 0.01);
let source_chain = rest_world_pose(&doc.skeleton).unwrap().bones[UNSKINNED_SIBLING_BONE]
.translation_rounding_magnitude;
let candidate_chain = rest_world_pose(&candidate.document().skeleton)
.unwrap()
.bones[UNSKINNED_SIBLING_BONE]
.translation_rounding_magnitude;
assert!(
source_chain > 50.0 * candidate_chain,
"the source side is no longer the larger one, so this fixture no longer \
separates the candidate's chain from the max: {source_chain} / {candidate_chain}",
);
let mut nudged = candidate.document().clone();
nudged.skeleton.bones[UNSKINNED_SIBLING_BONE]
.rest
.translation
.x += 1e-5;
assert_the_defect_axis_is_invisible_to_the_skin(
&doc,
&plan,
&candidate,
&ScaleCandidate { document: nudged },
|document: &Document| {
document.skeleton.bones[UNSKINNED_SIBLING_BONE]
.rest
.translation
.x
},
);
let mut broken = candidate.document().clone();
broken.skeleton.bones[UNSKINNED_SIBLING_BONE]
.rest
.translation
.x += 1e-4;
let broken = ScaleCandidate { document: broken };
let error = prove_scale(&doc, &broken, &plan)
.expect_err("a 1e-4 joint displacement must be refused on the candidate's chain");
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::RestTranslation,
observed,
tolerance,
} = error
else {
panic!("expected a refused rest translation, got {error:?}");
};
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
assert!(
observed > tolerance,
"rest translation band moved: observed {observed}, tolerance {tolerance}"
);
assert!(
observed < policy.f32_rounded_tolerance(0.0, 0.0, source_chain),
"rest translation residual {observed} now exceeds what the source chain buys too, \
so this fixture no longer kills the source-side reading",
);
}
#[test]
fn the_rest_translation_v6_floor_is_an_adjacent_f32_transition() {
let (doc, plan, candidate) = unskinned_sibling_conversion(false, 3190.0);
let refuses = |delta: f32| {
let mut document = candidate.document().clone();
document.skeleton.bones[UNSKINNED_SIBLING_BONE]
.rest
.translation
.x += delta;
match prove_scale(&doc, &ScaleCandidate { document }, &plan) {
Ok(_) => false,
Err(ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::RestTranslation,
..
}) => true,
Err(error) => panic!("the floor stopped belonging to RestTranslation: {error:?}"),
}
};
let (accepted, refused) = adjacent_positive_refusal(10.0, refuses);
assert_eq!(
(accepted.to_bits(), refused.to_bits()),
(0x4092_0000, 0x4092_0001),
);
assert!(refused.to_bits() > V4_REST_TRAJECTORY_REFUSED_TRANSITION_BITS);
let mut inside = candidate.document().clone();
inside.skeleton.bones[UNSKINNED_SIBLING_BONE]
.rest
.translation
.x += accepted;
assert_the_defect_axis_is_invisible_to_the_skin(
&doc,
&plan,
&candidate,
&ScaleCandidate { document: inside },
|document: &Document| {
document.skeleton.bones[UNSKINNED_SIBLING_BONE]
.rest
.translation
.x
},
);
let mut first_refused = candidate.document().clone();
first_refused.skeleton.bones[UNSKINNED_SIBLING_BONE]
.rest
.translation
.x += refused;
let (observed, tolerance) = residual_refusal(
prove_scale(
&doc,
&ScaleCandidate {
document: first_refused,
},
&plan,
)
.unwrap_err(),
ProofResidualKind::RestTranslation,
);
assert_eq!(
(observed.to_bits(), tolerance.to_bits()),
(0x4012_464d_554e_2270, 0x4012_40e6_ce11_6746),
"the exact RestTranslation floor measurement drifted",
);
}
const UNSKINNED_SIBLING_BONE: BoneId = 3;
fn unskinned_sibling_document(with_clip: bool) -> Document {
let local = Vec3::new(483.7628, 749.96, 451.14697);
let mut doc = cancelling_chain_document(1.0);
doc.skeleton.bones.push(Bone {
name: "unskinned_sibling".into(),
parent: Some(1),
rest: Transform {
translation: local,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
inverse_bind: None,
});
doc.assets.source_skeleton.nodes.push(SourceNodeAsset {
source_node_index: 3,
name: None,
parent_source_node_index: Some(1),
scene_root_indices: Vec::new(),
local_rest: SourceNodeLocalRest::Trs {
translation: local,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
bone: Some(UNSKINNED_SIBLING_BONE),
});
if with_clip {
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: UNSKINNED_SIBLING_BONE,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![local, local * 2.0]),
}],
});
}
doc
}
fn unskinned_sibling_conversion(
with_clip: bool,
factor: f64,
) -> (Document, ScalePlan, ScaleCandidate) {
let doc = unskinned_sibling_document(with_clip);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor },
document: &doc,
capability: &capability,
})
.expect("a whole-document conversion plans at any positive factor");
let candidate = build_scale_candidate(&doc, &plan).unwrap();
(doc, plan, candidate)
}
fn assert_the_defect_axis_is_invisible_to_the_skin(
doc: &Document,
plan: &ScalePlan,
candidate: &ScaleCandidate,
nudged: &ScaleCandidate,
perturbed: impl Fn(&Document) -> f32,
) {
assert_ne!(
perturbed(candidate.document()),
perturbed(nudged.document()),
"the displacement did not survive storage, so the residuals below are equal because \
the two documents are and this assertion proves nothing about isolation",
);
assert!(
plan.affected_nodes().contains(&UNSKINNED_SIBLING_BONE),
"the defect's bone is outside the affected closure, so no obligation compares it",
);
for instance in &doc.assets.instances {
assert!(
!instance.skin_joints.contains(&UNSKINNED_SIBLING_BONE),
"the defect's bone is a skin joint again, so SkinMatrix and Bounds see the displacement and these brackets stop bracketing the chain term",
);
}
let clean = prove_scale(doc, candidate, plan).expect("the correct candidate proves");
let nudged =
prove_scale(doc, nudged, plan).expect("a displacement the bands admit must still prove");
assert_eq!(
(
clean.skin_matrix.max(),
clean.skin_matrix.comparisons(),
clean.bounds.max(),
clean.bounds.comparisons(),
),
(
nudged.skin_matrix.max(),
nudged.skin_matrix.comparisons(),
nudged.bounds.max(),
nudged.bounds.comparisons(),
),
"the defect axis moved a skinned residual, so SkinMatrix can refuse it and these brackets no longer isolate the chain term",
);
}
fn adjacent_positive_refusal(
upper_refused: f32,
mut refuses: impl FnMut(f32) -> bool,
) -> (f32, f32) {
const MONOTONICITY_SAMPLES: u32 = 4096;
let upper = upper_refused.to_bits();
assert!(upper_refused.is_sign_positive());
assert!(!refuses(0.0), "the unmodified candidate must prove");
assert!(refuses(upper_refused), "the upper endpoint must refuse");
let mut seen_refusal = false;
for sample in 0..=MONOTONICITY_SAMPLES {
let bits = (u64::from(upper) * u64::from(sample) / u64::from(MONOTONICITY_SAMPLES)) as u32;
let refused = refuses(f32::from_bits(bits));
assert!(
!seen_refusal || refused,
"the sampled refusal predicate reversed inside the searched positive-f32 interval"
);
seen_refusal |= refused;
}
let mut accepted = 0u32;
let mut refused = upper;
while accepted + 1 < refused {
let middle = accepted + (refused - accepted) / 2;
if refuses(f32::from_bits(middle)) {
refused = middle;
} else {
accepted = middle;
}
}
(f32::from_bits(accepted), f32::from_bits(refused))
}
fn residual_refusal(error: ScaleError, expected: ProofResidualKind) -> (f64, f64) {
match error {
ScaleError::ProofResidualExceeded {
kind,
observed,
tolerance,
} if kind == expected => (observed, tolerance),
error => panic!("the floor stopped belonging to {expected:?}: {error:?}"),
}
}
const V4_REST_TRAJECTORY_REFUSED_TRANSITION_BITS: u32 = 0x404c_0000;
const V4_SKIN_MATRIX_REFUSED_TRANSITION_BITS: u32 = 0x4064_91bb;
const V4_BOUNDS_REFUSED_TRANSITION_BITS: u32 = 0x403e_e3b7;
#[test]
fn the_historical_v4_floor_bits_are_pinned() {
assert_eq!(
[
V4_REST_TRAJECTORY_REFUSED_TRANSITION_BITS,
V4_SKIN_MATRIX_REFUSED_TRANSITION_BITS,
V4_BOUNDS_REFUSED_TRANSITION_BITS,
],
[0x404c_0000, 0x4064_91bb, 0x403e_e3b7],
"the independently reproduced v4 floor data changed",
);
}
fn chain_document(depth: usize, rotation: Quat, root_scale: f32, animated: bool) -> Document {
let mut nodes = vec![RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(root_scale),
}];
for index in 1..=depth {
nodes.push(RigNode {
parent: Some(index - 1),
source_node_index: index,
translation: Vec3::new(10.0, 0.0, 0.0),
rotation,
scale: Vec3::ONE,
});
}
let mut doc = rig_document(&nodes, &[depth], 0, Mat4::IDENTITY);
if animated {
doc.clips.push(Clip {
name: "deep-chain".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::new(10.0, 0.0, 0.0),
Vec3::new(20.0, 0.0, 0.0),
]),
}],
});
}
doc
}
fn deep_chain_document(depth: usize) -> Document {
chain_document(depth, DEEP_CHAIN_ROTATION, 1.0, false)
}
const DEEP_CHAIN_ROTATION: Quat = Quat::from_xyzw(0.0, 0.0, 0.996_194_7, 0.087_155_804);
const RING_CHAIN_ROTATION: Quat = Quat::from_xyzw(0.0, 0.0, 0.016_361_732, 0.999_866_1);
fn deep_chain_conversion(depth: usize) -> (Document, ScalePlan, ScaleCandidate) {
let doc = deep_chain_document(depth);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 1.5 },
document: &doc,
capability: &capability,
})
.expect("a whole-document conversion plans at any positive factor");
let candidate = build_scale_candidate(&doc, &plan).unwrap();
(doc, plan, candidate)
}
fn animated_deep_chain_conversion(depth: usize) -> (Document, ScalePlan, ScaleCandidate) {
let doc = chain_document(depth, DEEP_CHAIN_ROTATION, 1.0, true);
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 1.5 },
document: &doc,
capability: &complete_capability(),
})
.expect("an animated whole-document chain plans");
assert!(
plan.obligations()
.contains(&ScaleProofObligation::Trajectories)
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
(doc, plan, candidate)
}
fn deep_chain_demand(depth: usize) -> (f64, Result<ScaleProof, ScaleError>) {
let (doc, plan, candidate) = deep_chain_conversion(depth);
let proof = prove_scale(&doc, &candidate, &plan);
let demand = proof
.as_ref()
.map(|proof| proof.rest_translation_f32_rounding_demand)
.unwrap_or_default();
(demand, proof)
}
#[test]
fn accumulated_parent_chain_provenance_proves_through_depth_512() {
let mut worst = 0.0f64;
for depth in [8, 16, 32, 64, 128, 192, 256, 512] {
let (demand, proof) = deep_chain_demand(depth);
proof.unwrap_or_else(|error| {
panic!(
"a correct {depth}-link chain must prove with accumulated provenance: \
{error:?}"
)
});
worst = worst.max(demand);
}
assert!(
worst > 0.01 && worst < 1.0,
"the declared deep-chain population no longer exercises the rounding term inside \
its count: worst demand {worst}",
);
}
#[test]
fn sampled_parent_chain_provenance_proves_through_depth_512() {
for depth in [8, 16, 32, 64, 128, 192, 256, 512] {
let (doc, plan, candidate) = animated_deep_chain_conversion(depth);
let proof = prove_scale(&doc, &candidate, &plan).unwrap_or_else(|error| {
panic!(
"a correct sampled {depth}-link chain must prove with accumulated \
provenance: {error:?}"
)
});
assert_eq!(proof.sample_time_count, 2);
assert!(proof.trajectory.comparisons() > 0);
}
}
#[test]
fn a_closed_loop_chain_proves_with_accumulated_provenance() {
let doc = chain_document(192, RING_CHAIN_ROTATION, 1.0, true);
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 1.5 },
document: &doc,
capability: &complete_capability(),
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let source_pose = rest_world_pose(&doc.skeleton).unwrap();
assert!(
source_pose.bones[192].matrix.w_axis.truncate().length() < 1e-3,
"the ring no longer closes, so it no longer exercises cancellation",
);
let proof =
prove_scale(&doc, &candidate, &plan).expect("the correct closed-loop hierarchy must prove");
assert_eq!(proof.sample_time_count, 2);
assert!(proof.rest_translation.comparisons() > 0);
assert!(proof.trajectory.comparisons() > 0);
assert!(proof.skin_matrix.comparisons() > 0);
assert!(proof.bounds.comparisons() > 0);
}
#[test]
fn translation_provenance_sums_only_spatial_link_operands() {
let chain = |translations: &[f32]| {
let mut bones = vec![Bone {
name: "root".into(),
parent: None,
rest: Transform::default(),
inverse_bind: None,
}];
for (index, &x) in translations.iter().enumerate() {
bones.push(Bone {
name: format!("bone{}", index + 1),
parent: Some(index),
rest: Transform {
translation: Vec3::new(x, 0.0, 0.0),
..Transform::default()
},
inverse_bind: None,
});
}
rest_world_pose(&Skeleton { bones }).unwrap()
};
let identity = chain(&vec![0.0; 512]);
assert!(
identity
.bones
.iter()
.all(|pose| pose.translation_rounding_magnitude == 0.0),
"the exact homogeneous row must not charge identity links",
);
let accumulating = chain(&[10.0, 10.0, 10.0]);
assert_eq!(
accumulating
.bones
.iter()
.map(|pose| pose.translation_rounding_magnitude)
.collect::<Vec<_>>(),
[0.0, 10.0, 30.0, 60.0],
);
let uneven = chain(&[1.0, 2.0, 4.0]);
assert_eq!(
uneven
.bones
.iter()
.map(|pose| pose.translation_rounding_magnitude)
.collect::<Vec<_>>(),
[0.0, 1.0, 4.0, 11.0],
"each link must add its own accumulated parent/local base",
);
let one_cancellation = chain(&[1000.0, -1000.0, 0.0, 0.0, 0.0]);
assert_eq!(
one_cancellation
.bones
.iter()
.map(|pose| pose.translation_rounding_magnitude)
.collect::<Vec<_>>(),
[0.0, 1000.0, 3000.0, 3000.0, 3000.0, 3000.0],
"exact identity descendants must not turn one active cancellation into depth * max",
);
let scaled = rest_world_pose(&Skeleton {
bones: vec![
Bone {
name: "scaled-root".into(),
parent: None,
rest: Transform {
scale: Vec3::splat(1000.0),
..Transform::default()
},
inverse_bind: None,
},
Bone {
name: "unit-link".into(),
parent: Some(0),
rest: Transform {
translation: Vec3::X,
..Transform::default()
},
inverse_bind: None,
},
],
})
.unwrap();
assert_eq!(
scaled.bones[1].translation_rounding_magnitude, 1000.0,
"provenance must sum the actual parent-world/local operand product, not local sizes",
);
let absorbed_parent = Mat4::from_translation(Vec3::new(2f32.powi(100), 0.0, 0.0));
let absorbed_local = Mat4::from_translation(Vec3::new(2f32.powi(-100), 0.0, 0.0));
let absorbed = translation_composition_rounding_base(absorbed_parent, absorbed_local);
let contribution = 2f64.powi(-100);
assert_eq!(
absorbed,
contribution + contribution / f64::from(f32::EPSILON),
"an absorbed normal contribution must provision its own size, not the 2^100 parent",
);
let finite_parent = Mat4::from_cols(
Vec4::new(2f32.powi(24), 0.0, 0.0, 0.0),
Vec4::new(0.0, 1.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0, 0.0),
Vec4::new(1.0, 0.0, 0.0, 1.0),
);
let finite =
translation_composition_rounding_base(finite_parent, Mat4::from_translation(Vec3::X));
assert_eq!(
finite,
2f64.powi(24) + 1.0,
"the finite parent/local sum must retain the one binary64 sees",
);
assert_eq!(
finite as f32,
2f32.powi(24),
"binary32 would lose the local parent addend this provenance must retain",
);
let accumulated = child_translation_rounding_magnitude(
WorldBonePose {
matrix: finite_parent,
translation_rounding_magnitude: 2f64.powi(24),
},
Mat4::from_translation(Vec3::X),
);
assert_eq!(
accumulated,
2f64.powi(25) + 1.0,
"the chain accumulation itself must retain the binary64-only addend",
);
assert_eq!(
accumulated as f32,
2f32.powi(25),
"binary32 chain accumulation would lose the same addend",
);
let at = |parent_scale: f32, local_x: f32| {
translation_composition_rounding_base(
Mat4::from_scale(Vec3::splat(parent_scale)),
Mat4::from_translation(Vec3::new(local_x, 0.0, 0.0)),
)
};
assert_eq!(
at(1000.0, 0.25),
250.0,
"the link base must use the parent/local product, not either operand alone",
);
let minimum_subnormal = f32::from_bits(1);
let below_minimum_subnormal = at(0.5, minimum_subnormal);
let at_minimum_subnormal = at(1.0, minimum_subnormal);
let below_minimum_normal = at(1.0, f32::from_bits(f32::MIN_POSITIVE.to_bits() - 1));
let at_minimum_normal = at(1.0, f32::MIN_POSITIVE);
assert!(
0.0 < below_minimum_subnormal
&& below_minimum_subnormal < at_minimum_subnormal
&& at_minimum_subnormal < below_minimum_normal
&& below_minimum_normal < at_minimum_normal,
"the subnormal floor must stay nonzero and monotone: \
{below_minimum_subnormal:e}, {at_minimum_subnormal:e}, \
{below_minimum_normal:e}, {at_minimum_normal:e}",
);
assert_eq!(
at_minimum_subnormal,
f64::from(f32::MIN_POSITIVE) + f64::from(minimum_subnormal),
"the minimum-normal floor must provision one subnormal rounding step",
);
}
#[test]
fn sampled_and_rest_provenance_match_on_the_z_spatial_row() {
let local = Vec3::new(0.0, 0.0, 10.0);
let skeleton = Skeleton {
bones: vec![
Bone {
name: "root".into(),
parent: None,
rest: Transform::default(),
inverse_bind: None,
},
Bone {
name: "z-child".into(),
parent: Some(0),
rest: Transform {
translation: local,
..Transform::default()
},
inverse_bind: None,
},
],
};
let clip = Clip {
name: "rest-equivalent-z".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![local, local]),
}],
};
let rest = rest_world_pose(&skeleton).expect("the rest pose composes");
let sampled = world_at_time(&skeleton, &clip, 0.0).expect("the sampled pose composes");
assert_eq!(rest.bones[1].matrix, sampled.bones[1].matrix);
assert_eq!(
(
rest.bones[1].translation_rounding_magnitude,
sampled.bones[1].translation_rounding_magnitude,
),
(10.0, 10.0),
"rest and sampled poses must share the recurrence across all three spatial rows",
);
}
#[test]
fn zero_translation_descendants_do_not_recharge_a_translated_parent() {
let mut nodes = vec![RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::new(1_000_000.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
}];
for index in 1..=512 {
nodes.push(RigNode {
parent: Some(index - 1),
source_node_index: index,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
});
}
let doc = rig_document(&nodes, &[512], 0, Mat4::IDENTITY);
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 1.5 },
document: &doc,
capability: &complete_capability(),
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let pose = rest_world_pose(&candidate.document().skeleton).unwrap();
assert!(
pose.bones
.iter()
.all(|bone| bone.translation_rounding_magnitude == 0.0),
"copying a translated parent through zero locals is exact and must add no provenance",
);
let mut broken = candidate.document().clone();
broken.skeleton.bones[512].rest.translation.x += 100.0;
assert!(matches!(
prove_scale(&doc, &ScaleCandidate { document: broken }, &plan),
Err(ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::RestTranslation,
..
})
));
}
#[test]
fn underflowed_translation_descendants_do_not_recharge_a_translated_parent() {
let mut nodes = vec![RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::new(1_000_000.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::splat(1e-30),
}];
for index in 1..=512 {
nodes.push(RigNode {
parent: Some(index - 1),
source_node_index: index,
translation: Vec3::new(f32::from_bits(1), 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
});
}
let doc = rig_document(&nodes, &[512], 0, Mat4::IDENTITY);
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 1.5 },
document: &doc,
capability: &complete_capability(),
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let pose = rest_world_pose(&candidate.document().skeleton).unwrap();
assert!(
pose.bones[512].translation_rounding_magnitude > 0.0
&& pose.bones[512].translation_rounding_magnitude < 1e-60,
"products that binary32 rounds to zero may carry their tiny loss, but must not \
recharge the million-unit parent: {}",
pose.bones[512].translation_rounding_magnitude,
);
let mut broken = candidate.document().clone();
broken.skeleton.bones[512].rest.translation.x = 1e32;
assert!(matches!(
prove_scale(&doc, &ScaleCandidate { document: broken }, &plan),
Err(ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::RestTranslation,
..
})
));
}
fn cancelling_chain_clip_conversion_at(factor: f64) -> (Document, ScalePlan, ScaleCandidate) {
let mut doc = cancelling_chain_document(1.0);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::new(0.0, 1000.0, 0.0),
Vec3::new(0.0, 2000.0, 0.0),
]),
}],
});
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor },
document: &doc,
capability: &capability,
})
.expect("a whole-document conversion plans at any positive factor");
assert!(
plan.obligations()
.contains(&ScaleProofObligation::Trajectories)
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
(doc, plan, candidate)
}
fn cancelling_chain_clip_conversion() -> (Document, ScalePlan, ScaleCandidate) {
cancelling_chain_clip_conversion_at(3190.0)
}
#[test]
fn a_sampled_pose_whose_parent_chain_cancels_still_proves_its_trajectory() {
let (doc, plan, candidate) = cancelling_chain_clip_conversion();
let proof = prove_scale(&doc, &candidate, &plan)
.expect("a correct candidate whose sampled chain cancels must still prove");
assert_eq!(proof.sample_time_count, 2);
let source_chain =
rest_world_pose(&doc.skeleton).unwrap().bones[2].translation_rounding_magnitude;
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
assert!(
proof.trajectory.max() > policy.f32_rounded_tolerance(0.0, 0.0, source_chain),
"trajectory residual {} no longer exceeds what the source side's chain buys, so \
this fixture no longer exercises the sampled chain term",
proof.trajectory.max()
);
}
const INHERITED_CHAIN_LEAF: BoneId = 3;
fn inherited_chain_document() -> Document {
let mut doc = cancelling_chain_document(1.0);
let rest = Transform {
translation: Vec3::new(1e-3, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
doc.skeleton.bones.push(Bone {
name: "bone3".into(),
parent: Some(2),
rest,
inverse_bind: None,
});
doc.assets.source_skeleton.nodes.push(SourceNodeAsset {
source_node_index: 3,
name: None,
parent_source_node_index: Some(2),
scene_root_indices: Vec::new(),
local_rest: SourceNodeLocalRest::Trs {
translation: rest.translation,
rotation: rest.rotation,
scale: rest.scale,
},
bone: Some(INHERITED_CHAIN_LEAF),
});
doc
}
fn inherited_chain_conversion(animated: bool) -> (Document, ScalePlan, ScaleCandidate) {
let mut doc = inherited_chain_document();
if animated {
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::new(0.0, 1000.0, 0.0),
Vec3::new(0.0, 2000.0, 0.0),
]),
}],
});
}
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 3190.0 },
document: &doc,
capability: &capability,
})
.expect("a whole-document conversion plans at any positive factor");
assert_eq!(
plan.obligations()
.contains(&ScaleProofObligation::Trajectories),
animated
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
(doc, plan, candidate)
}
fn inherited_chain_terms(skeleton: &Skeleton, pose: &WorldPose) -> (f64, f64) {
let parent = pose.bones[2];
let local = skeleton.bones[INHERITED_CHAIN_LEAF].rest.to_mat4();
let own_link = translation_composition_rounding_base(parent.matrix, local);
(pose.bones[2].translation_rounding_magnitude, own_link)
}
#[test]
fn a_bone_below_a_cancelling_chain_inherits_its_parent_s_magnitude_and_still_proves() {
let (doc, plan, candidate) = inherited_chain_conversion(false);
let pose = rest_world_pose(&candidate.document().skeleton).unwrap();
let (chain, own_link) = inherited_chain_terms(&candidate.document().skeleton, &pose);
assert!(
chain > 1e5 * own_link,
"the leaf's own link now composes on {own_link} against a parent chain of {chain}, \
so this fixture no longer separates the two halves of the chain magnitude",
);
let proof = prove_scale(&doc, &candidate, &plan).expect(
"a correct candidate whose leaf sits below a cancelling chain must still prove: the \
rounding its world translation carries is its ancestors', and only the inherited \
chain magnitude names it",
);
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
assert!(
proof.rest_translation.max() > policy.f32_rounded_tolerance(0.0, 0.0, own_link),
"rest translation residual {} no longer exceeds what the leaf's own link buys, so \
dropping the inherited term would no longer refuse this correct candidate",
proof.rest_translation.max(),
);
}
#[test]
fn a_sampled_bone_below_a_cancelling_chain_inherits_its_parent_s_magnitude_and_still_proves() {
let (doc, plan, candidate) = inherited_chain_conversion(true);
let skeleton = &candidate.document().skeleton;
let pose = world_at_time(skeleton, &candidate.document().clips[0], 0.0).unwrap();
let (chain, own_link) = inherited_chain_terms(skeleton, &pose);
assert!(
chain > 1e5 * own_link,
"the sampled leaf's own link now composes on {own_link} against a parent chain of \
{chain}, so this fixture no longer separates the two halves of the sampled chain",
);
let proof = prove_scale(&doc, &candidate, &plan).expect(
"a correct candidate whose sampled leaf sits below a cancelling chain must still \
prove, for the rest fixture's reason",
);
assert_eq!(proof.sample_time_count, 2);
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
assert!(
proof.trajectory.max() > policy.f32_rounded_tolerance(0.0, 0.0, own_link),
"trajectory residual {} no longer exceeds what the sampled leaf's own link buys, so \
dropping the inherited term in `world_at_time` would no longer refuse this correct \
candidate",
proof.trajectory.max(),
);
}
#[test]
fn a_shrinking_conversion_holds_trajectory_to_the_candidate_s_own_chain() {
let (doc, plan, candidate) = unskinned_sibling_conversion(true, 0.01);
let mut nudged = candidate.document().clone();
let TrackValues::Vec3s(values) = &mut nudged.clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
values[0].x += 1e-6;
values[1].x += 1e-6;
assert_the_defect_axis_is_invisible_to_the_skin(
&doc,
&plan,
&candidate,
&ScaleCandidate { document: nudged },
|document: &Document| {
let TrackValues::Vec3s(values) = &document.clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
values[0].x
},
);
let mut broken = candidate.document().clone();
let TrackValues::Vec3s(values) = &mut broken.clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
values[0].x += 3e-5;
values[1].x += 3e-5;
let broken = ScaleCandidate { document: broken };
let error = prove_scale(&doc, &broken, &plan)
.expect_err("a 3e-5 sampled displacement must be refused on the candidate's chain");
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Trajectory,
observed,
tolerance,
} = error
else {
panic!("expected a refused trajectory, got {error:?}");
};
let source_chain = rest_world_pose(&doc.skeleton).unwrap().bones[UNSKINNED_SIBLING_BONE]
.translation_rounding_magnitude;
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
assert!(
observed > tolerance,
"trajectory band moved: observed {observed}, tolerance {tolerance}"
);
assert!(
observed < policy.f32_rounded_tolerance(0.0, 0.0, source_chain),
"trajectory residual {observed} now exceeds what the source chain buys too, so \
this fixture no longer kills the source-side reading",
);
}
#[test]
fn the_trajectory_v6_floor_is_an_adjacent_f32_transition() {
let (doc, plan, candidate) = unskinned_sibling_conversion(true, 3190.0);
let refuses = |delta: f32| {
let mut document = candidate.document().clone();
let TrackValues::Vec3s(values) = &mut document.clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
values[0].x += delta;
values[1].x += delta;
match prove_scale(&doc, &ScaleCandidate { document }, &plan) {
Ok(_) => false,
Err(ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Trajectory,
..
}) => true,
Err(error) => panic!("the floor stopped belonging to Trajectory: {error:?}"),
}
};
let (accepted, refused) = adjacent_positive_refusal(10.0, refuses);
assert_eq!(
(accepted.to_bits(), refused.to_bits()),
(0x4092_0000, 0x4092_0001),
);
assert!(refused.to_bits() > V4_REST_TRAJECTORY_REFUSED_TRANSITION_BITS);
let mut nudged = candidate.document().clone();
let TrackValues::Vec3s(values) = &mut nudged.clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
values[0].x += accepted;
values[1].x += accepted;
assert_the_defect_axis_is_invisible_to_the_skin(
&doc,
&plan,
&candidate,
&ScaleCandidate { document: nudged },
|document: &Document| {
let TrackValues::Vec3s(values) = &document.clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
values[0].x
},
);
let mut first_refused = candidate.document().clone();
let TrackValues::Vec3s(values) = &mut first_refused.clips[0].tracks[0].values else {
panic!("expected a vec3 track");
};
values[0].x += refused;
values[1].x += refused;
let (observed, tolerance) = residual_refusal(
prove_scale(
&doc,
&ScaleCandidate {
document: first_refused,
},
&plan,
)
.unwrap_err(),
ProofResidualKind::Trajectory,
);
assert_eq!(
(observed.to_bits(), tolerance.to_bits()),
(0x4012_464d_554e_2270, 0x4012_40e6_ce11_6746),
"the exact Trajectory floor measurement drifted",
);
}
#[test]
fn a_parent_chain_whose_operand_sums_overflow_f32_still_proves() {
let doc = cancelling_chain_overflow_document();
let local = doc.skeleton.bones[2].rest.to_mat4();
let worlds = world_rests(&doc.skeleton).unwrap();
assert!(
!(mat4_abs(worlds[1]) * local.w_axis.abs())
.max_element()
.is_finite(),
"the fixture no longer overflows the f32 lane computation, so it no longer \
exercises the fallback",
);
assert!(
mat4_is_finite(worlds[2]),
"the composed world must stay finite: an overflowing world is a different failure, \
and one that is allowed to be refused",
);
let pose = rest_world_pose(&doc.skeleton).unwrap();
assert!(
pose.bones[2].translation_rounding_magnitude.is_finite(),
"chain magnitude {} is not finite",
pose.bones[2].translation_rounding_magnitude
);
let plan = rest_bind_plan(&doc, 1e3);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).expect(
"a correct candidate whose chain operands overflow f32 must still prove: every \
operand is finite and so is the world they compose to",
);
assert!(
proof.skin_matrix.max().is_finite() && proof.bounds.max().is_finite(),
"skin {} / bounds {} residual is not finite",
proof.skin_matrix.max(),
proof.bounds.max()
);
}
fn policy_scalar_tolerance_at(magnitude: f64) -> f64 {
ScaleTolerancePolicy::APPENDIX_D_V6.scalar_tolerance(magnitude, magnitude)
}
#[test]
fn the_bounds_v6_floor_is_an_adjacent_f32_transition() {
let mut doc = cancelling_chain_document(3190.0);
let primitive = &mut doc.assets.meshes[0].primitives[0];
primitive.positions = vec![Vec3::new(1_000_000.0, 0.0, 0.0)];
primitive.joints = vec![[1, 0, 0, 0]];
primitive.weights = vec![[1.0, 0.0, 0.0, 0.0]];
let plan = rest_bind_plan(&doc, 3190.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert!(
rig_slot_magnitude(candidate.document()) > 4_000_000.0,
"the chain no longer dominates the point's transform base"
);
let refuses = |delta: f32| {
let mut document = candidate.document().clone();
document.assets.meshes[0].primitives[0].positions[0].y += delta;
match prove_scale(&doc, &ScaleCandidate { document }, &plan) {
Ok(_) => false,
Err(ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Bounds,
..
}) => true,
Err(error) => panic!("the floor stopped belonging to Bounds: {error:?}"),
}
};
let (accepted, refused) = adjacent_positive_refusal(8.0, refuses);
assert_eq!(
(accepted.to_bits(), refused.to_bits()),
(0x4090_1eeb, 0x4090_1eec),
);
assert!(refused.to_bits() > V4_BOUNDS_REFUSED_TRANSITION_BITS);
let mut first_refused = candidate.document().clone();
first_refused.assets.meshes[0].primitives[0].positions[0].y += refused;
let (observed, tolerance) = residual_refusal(
prove_scale(
&doc,
&ScaleCandidate {
document: first_refused,
},
&plan,
)
.unwrap_err(),
ProofResidualKind::Bounds,
);
assert_eq!(
(observed.to_bits(), tolerance.to_bits()),
(0x4012_40e6_6000_0000, 0x4012_40e6_54a0_13ff),
"the exact Bounds floor measurement drifted",
);
}
#[test]
fn the_skin_matrix_v6_floor_is_an_adjacent_f32_transition() {
let doc = cancelling_chain_document(3190.0);
let plan = rest_bind_plan(&doc, 3190.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert!(
rig_slot_magnitude(candidate.document()) > 1_000_000.0,
"the cancelling chain no longer dominates the near-unit W * B product"
);
let refuses = |delta: f32| {
let mut document = candidate.document().clone();
document.assets.instances[0].skin_ibms[1].w_axis.x += delta;
match prove_scale(&doc, &ScaleCandidate { document }, &plan) {
Ok(_) => false,
Err(ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::SkinMatrix,
..
}) => true,
Err(error) => panic!("the floor stopped belonging to SkinMatrix: {error:?}"),
}
};
let (accepted, refused) = adjacent_positive_refusal(16.0, refuses);
assert_eq!(
(accepted.to_bits(), refused.to_bits()),
(0x40ab_6d4b, 0x40ab_6d4c),
);
assert!(refused.to_bits() > V4_SKIN_MATRIX_REFUSED_TRANSITION_BITS);
let mut first_refused = candidate.document().clone();
first_refused.assets.instances[0].skin_ibms[1].w_axis.x += refused;
let (observed, tolerance) = residual_refusal(
prove_scale(
&doc,
&ScaleCandidate {
document: first_refused,
},
&plan,
)
.unwrap_err(),
ProofResidualKind::SkinMatrix,
);
assert_eq!(
(observed.to_bits(), tolerance.to_bits()),
(0x4012_40e6_6000_0000, 0x4012_40e6_40a0_13ff),
"the exact SkinMatrix floor measurement drifted",
);
}
#[test]
fn the_f32_rounding_term_is_absolute_so_it_cannot_widen_a_comparison_of_its_own_magnitude() {
let policy = ScaleTolerancePolicy::APPENDIX_D_V6;
for magnitude in [1e-6, 1.0, 3190.0, 1e9] {
let plain = policy.scalar_tolerance(magnitude, magnitude);
let rounded = policy.f32_rounded_tolerance(magnitude, magnitude, magnitude);
let added = rounded - plain;
assert!(
added <= policy.scalar_relative * magnitude / 20.0,
"at {magnitude} the rounding term {added} is not far below the relative band"
);
}
let small = policy.f32_rounded_tolerance(5.982, 5.982, 4242.0);
assert!(
small > 2.44e-4,
"the reproducer's residual is not admitted: {small}"
);
}
#[test]
fn an_overflowing_skinned_position_is_named_as_overflow_not_as_a_generic_non_finite() {
let overflowing = [unrounded_slot(Mat4::from_scale(Vec3::splat(1e30)))];
let mut accumulator = BoundsAccumulator::default();
assert_eq!(
accumulate_skinned_bounds(
4,
2,
&Primitive {
positions: vec![Vec3::splat(1e30)],
joints: vec![[0, 0, 0, 0]],
weights: vec![[1.0, 0.0, 0.0, 0.0]],
..Primitive::default()
},
&overflowing,
&mut accumulator,
),
Err(ScaleError::InvalidSkinnedPrimitive {
instance_index: 4,
primitive_index: 2,
reason: "skinned_magnitude_overflow",
})
);
let nan_producing = [unrounded_slot(Mat4::from_cols(
Vec4::new(f32::INFINITY, 0.0, 0.0, 0.0),
Vec4::new(0.0, 1.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 1.0, 0.0),
Vec4::new(0.0, 0.0, 0.0, 1.0),
))];
let mut accumulator = BoundsAccumulator::default();
assert_eq!(
accumulate_skinned_bounds(
4,
2,
&Primitive {
positions: vec![Vec3::new(0.0, 1.0, 0.0)],
joints: vec![[0, 0, 0, 0]],
weights: vec![[1.0, 0.0, 0.0, 0.0]],
..Primitive::default()
},
&nan_producing,
&mut accumulator,
),
Err(ScaleError::InvalidSkinnedPrimitive {
instance_index: 4,
primitive_index: 2,
reason: "non_finite_result",
})
);
}
#[test]
fn bounds_provenance_uses_the_per_axis_transform_and_slot_composition() {
let primitive = Primitive {
positions: vec![Vec3::splat(300.0)],
joints: vec![[0, 0, 0, 0]],
weights: vec![[1.0, 0.0, 0.0, 0.0]],
..Primitive::default()
};
let mut points_dominate = BoundsAccumulator::default();
accumulate_skinned_bounds(
0,
0,
&primitive,
&[unrounded_slot(Mat4::IDENTITY)],
&mut points_dominate,
)
.unwrap();
assert!((points_dominate.rounding_magnitude() - 300.0).abs() < 1e-6);
let world = Mat4::from_translation(Vec3::new(5000.0, 0.0, 0.0));
let mut composition_dominates = BoundsAccumulator::default();
accumulate_skinned_bounds(
0,
0,
&primitive,
&[SkinSlot::compose(world, world.inverse(), 0.0)],
&mut composition_dominates,
)
.unwrap();
assert!(
composition_dominates.rounding_magnitude() >= 5000.0,
"the composition's magnitude was lost: {}",
composition_dominates.rounding_magnitude()
);
}
#[test]
fn a_tiny_influence_carries_only_its_proportional_bounds_provenance() {
let primitive = Primitive {
positions: vec![Vec3::ZERO],
joints: vec![[0, 1, 0, 0]],
weights: vec![[1.0, 1e-20, 0.0, 0.0]],
..Primitive::default()
};
let slots = [
SkinSlot {
matrix: Mat4::IDENTITY,
absolute: Mat4::IDENTITY,
rounding_magnitude: 1.0,
},
SkinSlot {
matrix: Mat4::IDENTITY,
absolute: Mat4::IDENTITY,
rounding_magnitude: 1e20,
},
];
let mut accumulator = BoundsAccumulator::default();
accumulate_skinned_bounds(0, 0, &primitive, &slots, &mut accumulator).unwrap();
let magnitude = accumulator.rounding_magnitude();
assert!(
(magnitude - 2.0).abs() < 1e-6,
"the tiny slot bought more than its proportional provenance: {magnitude}"
);
}
#[test]
fn a_tiny_influence_carries_only_its_proportional_transform_provenance() {
let primitive = Primitive {
positions: vec![Vec3::new(1.0, 0.0, 0.0)],
joints: vec![[0, 1, 0, 0]],
weights: vec![[1.0, 1e-20, 0.0, 0.0]],
..Primitive::default()
};
let slots = [
SkinSlot {
matrix: Mat4::IDENTITY,
absolute: Mat4::IDENTITY,
rounding_magnitude: 1.0,
},
SkinSlot {
matrix: Mat4::from_scale(Vec3::splat(1e20)),
absolute: Mat4::from_scale(Vec3::splat(1e20)),
rounding_magnitude: 1.0,
},
];
let mut accumulator = BoundsAccumulator::default();
accumulate_skinned_bounds(0, 0, &primitive, &slots, &mut accumulator).unwrap();
let magnitude = accumulator.rounding_magnitude();
assert!(
(magnitude - 2.0).abs() < 1e-5,
"the tiny transform bought more than its proportional provenance: {magnitude}"
);
}
#[test]
fn weighted_bounds_provenance_recovers_the_tiny_influence_detection_floor() {
let doc = composed_slot_document(
[Quat::IDENTITY; 2],
1.0,
[Vec3::ZERO, Vec3::new(1e20, 0.0, 0.0)],
[
Mat4::IDENTITY,
Mat4::from_translation(Vec3::new(1e17, 0.0, 0.0)),
],
&[Vec3::ZERO],
&[[1.0, 1e-20, 0.0, 0.0]],
);
let plan = rest_bind_plan(&doc, 1.0);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).expect("the valid tiny influence must prove");
let slots = rig_skin_slots(&doc);
let old_unweighted_base = slots
.iter()
.map(|slot| slot.rounding_magnitude)
.fold(0.0, f64::max);
let weighted_base = rig_bounds_magnitude(&doc);
assert!(
old_unweighted_base >= 1e20 && weighted_base < 3.0,
"fixture did not separate old and weighted bases: {old_unweighted_base} / \
{weighted_base}"
);
let mut broken = candidate.document().clone();
broken.assets.meshes[0].primitives[0].weights[0][1] = 0.0;
let error = prove_scale(&doc, &ScaleCandidate { document: broken }, &plan)
.expect_err("dropping the tiny influence must no longer hide behind the far joint");
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Bounds,
observed,
tolerance,
} = error
else {
panic!("expected the bounds obligation, got {error:?}");
};
let old_unweighted_tolerance =
ScaleTolerancePolicy::APPENDIX_D_V6.f32_rounded_tolerance(0.0, 0.0, old_unweighted_base);
assert!(
observed > tolerance && observed < old_unweighted_tolerance,
"detection floor did not tighten: observed {observed}, v4 {tolerance}, old \
unweighted {old_unweighted_tolerance}"
);
fn v3_bounds(document: &Document) -> ((Vec3, Vec3), f64) {
let slots = rig_skin_slots(document);
let instance = &document.assets.instances[0];
let mesh = &document.assets.meshes[instance.mesh];
let mut min = Vec3::splat(f32::INFINITY);
let mut max = Vec3::splat(f32::NEG_INFINITY);
let mut magnitude = 0.0f64;
for primitive in &mesh.primitives {
for (vertex, &position) in primitive.positions.iter().enumerate() {
let joints = primitive.joints[vertex];
let weights = primitive.weights[vertex];
let mut skinned = Vec3::ZERO;
let mut weight_sum = 0.0f32;
let mut vertex_magnitude = 0.0f64;
for slot_index in 0..4 {
let weight = weights[slot_index];
if weight == 0.0 {
continue;
}
let slot = &slots[joints[slot_index] as usize];
skinned += weight * slot.matrix.transform_point3(position);
weight_sum += weight;
vertex_magnitude = vertex_magnitude.max(column_operand_magnitude(
slot.absolute,
position.extend(1.0),
));
vertex_magnitude = vertex_magnitude.max(slot.rounding_magnitude);
}
if weight_sum > 0.0 {
skinned /= weight_sum;
min = min.min(skinned);
max = max.max(skinned);
let length = skinned.length();
let length = if length.is_finite() {
f64::from(length)
} else {
skinned.as_dvec3().length()
};
magnitude = magnitude.max(vertex_magnitude).max(length);
}
}
}
((min, max), magnitude)
}
fn v3_bounds_outcome(
source: (Vec3, Vec3),
candidate: (Vec3, Vec3),
magnitude: f64,
) -> (bool, f64) {
let mut refused = false;
let mut max_residual = 0.0f64;
for (before, after) in [(source.0, candidate.0), (source.1, candidate.1)] {
for (before, after) in before.to_array().into_iter().zip(after.to_array()) {
let expected = f64::from(before);
let observed = f64::from(after);
let residual = (observed - expected).abs();
let tolerance = 1e-6
+ 1e-5 * expected.abs().max(observed.abs())
+ 4.0 * magnitude.abs() * f64::from(f32::EPSILON);
refused |= residual > tolerance;
max_residual = max_residual.max(residual);
}
}
(refused, max_residual)
}
fn adjacent_transition(
accepted_value: f32,
refused_value: f32,
mut refuses: impl FnMut(f32) -> bool,
) -> (f32, f32) {
let mut accepted = accepted_value.to_bits();
let mut refused = refused_value.to_bits();
assert!(!refuses(f32::from_bits(accepted)));
assert!(refuses(f32::from_bits(refused)));
while accepted + 1 < refused {
let middle = accepted + (refused - accepted) / 2;
if refuses(f32::from_bits(middle)) {
refused = middle;
} else {
accepted = middle;
}
}
(f32::from_bits(accepted), f32::from_bits(refused))
}
fn adjacent_reverse_transition(
refused_value: f32,
accepted_value: f32,
mut refuses: impl FnMut(f32) -> bool,
) -> (f32, f32) {
let mut refused = refused_value.to_bits();
let mut accepted = accepted_value.to_bits();
assert!(refuses(f32::from_bits(refused)));
assert!(!refuses(f32::from_bits(accepted)));
while refused + 1 < accepted {
let middle = refused + (accepted - refused) / 2;
if refuses(f32::from_bits(middle)) {
refused = middle;
} else {
accepted = middle;
}
}
(f32::from_bits(refused), f32::from_bits(accepted))
}
let (source_v3_bounds, source_v3_magnitude) = v3_bounds(&doc);
let original_weight = 1e-20f32;
let mutate = |far_weight: f32| {
let mut broken = candidate.document().clone();
broken.assets.meshes[0].primitives[0].weights[0][1] = far_weight;
broken
};
let v4_refuses = |far_weight: f32| {
let broken = ScaleCandidate {
document: mutate(far_weight),
};
match prove_scale(&doc, &broken, &plan) {
Ok(_) => false,
Err(ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Bounds,
..
}) => true,
Err(error) => panic!("the v4 floor reached another obligation: {error:?}"),
}
};
let (v4_lower_refused, v4_lower_accepted) =
adjacent_reverse_transition(0.0, original_weight, v4_refuses);
let (v4_upper_accepted, v4_upper_refused) =
adjacent_transition(original_weight, 1.0, v4_refuses);
let (v3_accepted, v3_refused) = adjacent_transition(original_weight, 1.0, |far_weight| {
let broken = mutate(far_weight);
let (candidate_v3_bounds, candidate_v3_magnitude) = v3_bounds(&broken);
let v3_magnitude = candidate_v3_magnitude.max(source_v3_magnitude);
assert_eq!(
v3_magnitude, old_unweighted_base,
"the historical base moved at candidate weight {far_weight}"
);
v3_bounds_outcome(source_v3_bounds, candidate_v3_bounds, v3_magnitude).0
});
let zero_v3 = v3_bounds(&mutate(0.0));
assert!(
!v3_bounds_outcome(
source_v3_bounds,
zero_v3.0,
source_v3_magnitude.max(zero_v3.1),
)
.0,
"v3 unexpectedly had a lower transition"
);
let v4_refusal = |far_weight: f32| {
let broken = ScaleCandidate {
document: mutate(far_weight),
};
let error = prove_scale(&doc, &broken, &plan).unwrap_err();
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Bounds,
observed,
tolerance,
} = error
else {
panic!("the v4 endpoint reached another obligation: {error:?}");
};
(observed, tolerance)
};
let v4_lower_outcome = v4_refusal(v4_lower_refused);
let v4_upper_outcome = v4_refusal(v4_upper_refused);
let v4_floor = v4_lower_outcome.0.min(v4_upper_outcome.0);
let (v3_refused_bounds, v3_refused_magnitude) = v3_bounds(&mutate(v3_refused));
let v3_floor = v3_bounds_outcome(
source_v3_bounds,
v3_refused_bounds,
source_v3_magnitude.max(v3_refused_magnitude),
)
.1;
assert_eq!(
[
v4_lower_refused.to_bits(),
v4_lower_accepted.to_bits(),
v4_upper_accepted.to_bits(),
v4_upper_refused.to_bits(),
v3_accepted.to_bits(),
v3_refused.to_bits(),
],
[
0x1e3c_6f0a,
0x1e3c_6f0b,
0x1e3d_5b21,
0x1e3d_5b22,
0x3a7a_1be3,
0x3a7a_1be4,
],
"the recorded before/after weight brackets moved; recalibrate and update \
DESIGN.md Appendix D section D.1"
);
assert_eq!(v4_lower_refused.to_bits() + 1, v4_lower_accepted.to_bits());
assert_eq!(v4_upper_accepted.to_bits() + 1, v4_upper_refused.to_bits());
assert_eq!(v3_accepted.to_bits() + 1, v3_refused.to_bits());
assert_eq!(v4_floor.to_bits(), 0x3ec4_7800_0000_0000);
assert_eq!(v3_floor.to_bits(), 0x42d5_ac65_4000_0000);
assert!(
v3_floor / v4_floor > 3.9e19,
"the weighted base recovered too little detection power: v3 residual {v3_floor}, \
v4 residual {v4_floor}"
);
}
#[test]
fn the_fourth_skin_influence_of_a_vertex_is_walked_like_the_first_three() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(0), 2, Vec3::new(0.0, 2.0, 0.0)),
rig(Some(0), 3, Vec3::new(0.0, 3.0, 0.0)),
rig(Some(0), 4, Vec3::new(0.0, 4.0, 0.0)),
];
let mut doc = rig_document(&nodes, &[1, 2, 3, 4], 0, Mat4::IDENTITY);
doc.assets.meshes[0].primitives[0] = Primitive {
positions: vec![Vec3::new(1.0, 0.0, 0.0)],
joints: vec![[0, 1, 2, 3]],
weights: vec![[0.25, 0.25, 0.25, 0.25]],
..Primitive::default()
};
assert_eq!(doc.assets.instances[0].skin_joints, vec![1, 2, 3, 4]);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert!(
plan.obligations()
.contains(&ScaleProofObligation::SkinAndBounds)
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.assets.meshes[0].primitives[0].joints[0] = [0, 1, 2, 0];
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Bounds,
..
}
));
}
#[test]
fn rest_bind_rebases_translation_tracks_and_proves_every_sampled_obligation() {
let doc = multi_joint_document();
let capability = complete_capability();
let plan = multi_joint_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
let obligations = plan.obligations().to_vec();
assert!(obligations.contains(&ScaleProofObligation::KeyTranslations));
assert!(obligations.contains(&ScaleProofObligation::CubicInteriors));
assert!(obligations.contains(&ScaleProofObligation::Trajectories));
assert!(obligations.contains(&ScaleProofObligation::SkinAndBounds));
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let clip = &candidate.document().clips[0];
let TrackValues::Vec3s(linear) = &clip.tracks[0].values else {
panic!("expected a vec3 track");
};
let expected_linear = [Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, 2.0, 0.0)];
for (value, expected) in linear.iter().zip(expected_linear) {
assert!((*value - expected).length() < 1e-6, "{value:?}");
}
let TrackValues::Vec3s(cubic) = &clip.tracks[1].values else {
panic!("expected a vec3 track");
};
let expected_cubic = [
Vec3::ZERO,
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(0.0, 0.6, 0.0),
Vec3::new(0.0, 0.6, 0.0),
Vec3::new(0.0, 3.0, 0.0),
Vec3::ZERO,
];
for (value, expected) in cubic.iter().zip(expected_cubic) {
assert!((*value - expected).length() < 1e-6, "{value:?}");
}
let binds = &candidate.document().assets.instances[0].skin_ibms;
assert!(binds[0].abs_diff_eq(Mat4::from_translation(Vec3::new(0.0, -1.0, 0.0)), 1e-5));
assert!(binds[1].abs_diff_eq(Mat4::from_translation(Vec3::new(0.0, -2.0, 0.0)), 1e-5));
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 3);
assert!(proof.key_translation.max() < 1e-4);
assert!(proof.cubic_interior.max() < 1e-4);
assert!(proof.trajectory.max() < 1e-4);
assert!(proof.skin_matrix.max() < 1e-4);
assert!(proof.bounds.max() < 1e-4);
}
#[test]
fn a_reweighted_vertex_is_named_by_the_bounds_obligation() {
let doc = multi_joint_document();
let capability = complete_capability();
let plan = multi_joint_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut broken = candidate.document().clone();
broken.assets.meshes[0].primitives[0].weights[2] = [0.75, 0.25, 0.0, 0.0];
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Bounds,
..
}
));
}
type CapabilityDomainCase = (&'static str, fn(&mut ScaleCapabilityFacts));
#[test]
fn every_unsupported_capability_domain_rejects_planning_on_its_own() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let domains: [CapabilityDomainCase; 12] = [
("cameras_present", |f| f.cameras_present = true),
("lights_present", |f| f.lights_present = true),
("instancing_present", |f| f.instancing_present = true),
("unregistered_extensions_present", |f| {
f.unregistered_extensions_present = true
}),
("extras_present", |f| f.extras_present = true),
("unknown_source_members_present", |f| {
f.unknown_source_members_present = true
}),
("non_triangle_primitives_present", |f| {
f.non_triangle_primitives_present = true
}),
("unsupported_vertex_attributes_present", |f| {
f.unsupported_vertex_attributes_present = true
}),
("secondary_skin_influences_present", |f| {
f.secondary_skin_influences_present = true
}),
("inverse_bind_issues_present", |f| {
f.inverse_bind_issues_present = true
}),
("unsafe_accessor_layout_present", |f| {
f.unsafe_accessor_layout_present = true
}),
("external_resources_present", |f| {
f.external_resources_present = true
}),
];
let operations = [
ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
];
for (name, set_flag) in domains {
let mut capability = complete_capability();
set_flag(&mut capability);
assert!(!capability.is_supported(), "{name} must not be supported");
for operation in operations {
let request = ScaleRequest {
operation,
document: &doc,
capability: &capability,
};
assert!(
matches!(
plan_scale(&request).unwrap_err(),
ScaleError::IncompleteCapability
),
"{name} must reject {operation:?}"
);
}
}
assert!(complete_capability().is_supported());
}
#[test]
fn morph_capabilities_are_whole_document_only() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
for (name, set_flag) in [
(
"morphs_present",
(|facts: &mut ScaleCapabilityFacts| facts.morphs_present = true)
as fn(&mut ScaleCapabilityFacts),
),
(
"morph_weights_present",
|facts: &mut ScaleCapabilityFacts| {
facts.morph_weights_present = true;
},
),
] {
let mut capability = complete_capability();
set_flag(&mut capability);
assert!(
!capability.is_supported(),
"the operation-agnostic query remains conservative for {name}"
);
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
})
.unwrap_err(),
ScaleError::IncompleteCapability,
"presence without a raw preservation witness must reject {name}"
);
capability.whole_document_morphs_preservable = true;
let whole_document = ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &doc,
capability: &capability,
};
assert!(
plan_scale(&whole_document).is_ok(),
"raw format adapters may discharge {name} for whole-document conversion"
);
let rest_bind = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
};
assert_eq!(
plan_scale(&rest_bind).unwrap_err(),
ScaleError::IncompleteCapability,
"rest/bind has no raw morph preservation proof for {name}"
);
}
}
#[test]
fn the_appendix_d_v6_tolerance_identity_is_pinned_through_plan_and_proof() {
fn assert_appendix_d_v6(policy: ScaleTolerancePolicy) {
assert_eq!(policy.id, "appendix-d-v6");
assert_eq!(policy.f32_rounding_ulps, 4);
assert_eq!(policy.relative_orthogonality, 1e-5);
assert_eq!(policy.equal_axis, 1e-5);
assert_eq!(policy.common_factor, 1e-5);
assert_eq!(policy.singular_determinant_relative, 1e-6);
assert_eq!(policy.scalar_absolute, 1e-6);
assert_eq!(policy.scalar_relative, 1e-5);
assert_eq!(policy.rotation_residual_radians, 1e-5);
assert_eq!(policy.postcondition_unit_scale_residual, 6.103_515_625e-5);
assert_eq!(policy.proof_sample_work_budget, 400_000_000);
assert!((policy.scalar_tolerance(0.0, 100.0) - 0.001_001).abs() < 1e-12);
let bands = ScaleTolerancePolicy::UNIT_SCALE_BANDS * policy.common_factor;
assert!((bands - 4e-5).abs() < 1e-20, "four bands {bands}");
assert!(policy.postcondition_unit_scale_residual >= bands);
assert_eq!(policy.postcondition_unit_scale_residual, 2f64.powi(-14));
assert_eq!(
policy.postcondition_unit_scale_residual,
512.0 * 2f64.powi(-23)
);
assert!(policy.postcondition_unit_scale_residual / 2.0 < bands);
let c = policy.common_factor;
let analytic_worst_case = (1.0 - c).powi(-3) - 1.0;
assert!(
(analytic_worst_case - 3.00006e-5).abs() < 1e-13,
"three composed bands {analytic_worst_case}"
);
assert!(
policy.postcondition_unit_scale_residual - analytic_worst_case > c,
"headroom {} is under one band",
policy.postcondition_unit_scale_residual - analytic_worst_case
);
}
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let whole_document = whole_document_plan(&doc, &capability);
assert_appendix_d_v6(whole_document.tolerance_policy());
let candidate = build_scale_candidate(&doc, &whole_document).unwrap();
let proof = prove_scale(&doc, &candidate, &whole_document).unwrap();
assert_appendix_d_v6(proof.tolerance_policy);
let rest_bind = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
})
.unwrap();
assert_appendix_d_v6(rest_bind.tolerance_policy());
let candidate = build_scale_candidate(&doc, &rest_bind).unwrap();
let proof = prove_scale(&doc, &candidate, &rest_bind).unwrap();
assert_appendix_d_v6(proof.tolerance_policy);
}
#[test]
fn a_residual_exactly_at_its_tolerance_is_accepted_and_the_next_one_up_is_not() {
let bound = ScaleTolerancePolicy::APPENDIX_D_V6.postcondition_unit_scale_residual;
assert_eq!(
check_residual(ProofResidualKind::UnitScale, bound, bound),
Ok(())
);
let above = f64::from_bits(bound.to_bits() + 1);
assert_eq!(
check_residual(ProofResidualKind::UnitScale, above, bound),
Err(ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::UnitScale,
observed: above,
tolerance: bound,
})
);
}
#[test]
fn a_unit_scale_residual_exactly_on_the_policy_bound_still_proves() {
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.5),
},
rig(Some(0), 1, Vec3::ZERO),
];
let doc = rig_document(&nodes, &[1], 0, Mat4::from_scale(Vec3::splat(2.0)));
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.5,
},
document: &doc,
capability: &capability,
})
.unwrap();
assert!(
plan.obligations()
.contains(&ScaleProofObligation::RestWorldAndUnitScale)
);
let bound = plan.tolerance_policy().postcondition_unit_scale_residual;
let mut boundary = build_scale_candidate(&doc, &plan).unwrap().into_document();
boundary.skeleton.bones[0].rest.scale = Vec3::splat(1.0 + 2.0f32.powi(-14));
boundary.assets.instances[0].skin_ibms[0] =
Mat4::from_scale(Vec3::splat(1.0 - 2.0f32.powi(-14)));
let boundary = ScaleCandidate { document: boundary };
let proof = prove_scale(&doc, &boundary, &plan).unwrap();
assert_eq!(proof.unit_scale.max(), bound);
assert_eq!(proof.unit_scale.max(), 2f64.powi(-14));
}
#[test]
fn a_successful_proof_reports_the_residual_maxima_it_actually_observed() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
let expected = 0.01_f64 - (0.01_f32 as f64);
assert_eq!(expected, 2.235_174_181_158_816_6e-10);
assert_eq!(proof.rest_translation.max(), expected);
assert_eq!(proof.mesh_position.max(), expected);
assert_eq!(proof.bounds.max(), expected);
assert_eq!(proof.skin_matrix.max(), 0.0);
assert!(proof.skin_matrix.evaluated());
}
#[test]
fn the_reported_unit_scale_residual_is_the_maximum_not_the_last_node_seen() {
let u = 2f32.powi(-17);
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.5 * (1.0 + u)),
},
RigNode {
parent: Some(0),
source_node_index: 1,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(1.0 - u * 0.5),
},
];
let doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.5,
},
document: &doc,
capability: &capability,
})
.unwrap();
assert_eq!(plan.affected_nodes(), &[0, 1]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.unit_scale.max(), 64.0 * 2f64.powi(-23));
assert!(proof.unit_scale.max() > 32.0 * 2f64.powi(-23));
}
#[test]
fn each_clip_is_proved_against_its_own_sample_times() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::ZERO),
rig(Some(0), 2, Vec3::ZERO),
];
let mut doc = rig_document(&nodes, &[1, 2], 0, Mat4::IDENTITY);
doc.assets.meshes[0].primitives[0] = Primitive {
positions: vec![Vec3::new(1.0, 0.0, 0.0)],
joints: vec![[0, 1, 0, 0]],
weights: vec![[0.5, 0.5, 0.0, 0.0]],
..Primitive::default()
};
let clip = |name: &str, times: Vec<f32>, values: Vec<Vec3>| Clip {
name: name.into(),
duration_s: f64::from(*times.last().expect("at least one key time")),
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times,
values: TrackValues::Vec3s(values),
}],
};
doc.clips = vec![
clip("still", vec![0.0, 1.0], vec![Vec3::ZERO; 2]),
clip(
"lift",
vec![0.0, 1.0, 2.0],
vec![Vec3::ZERO, Vec3::ZERO, Vec3::new(0.0, 10.0, 0.0)],
),
];
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 1.0 },
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 5);
let mut broken = candidate.document().clone();
broken.assets.meshes[0].primitives[0].weights[0] = [1.0, 0.0, 0.0, 0.0];
let broken = ScaleCandidate { document: broken };
assert!(matches!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::Bounds,
..
}
));
}
fn budget_document(key_times: usize, vertices: usize) -> Document {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.assets.meshes[0].primitives[0] = Primitive {
positions: (0..vertices)
.map(|v| Vec3::new(v as f32 * 0.5, 1.0, 0.0))
.collect(),
joints: vec![[0, 0, 0, 0]; vertices],
weights: vec![[1.0, 0.0, 0.0, 0.0]; vertices],
..Primitive::default()
};
let times: Vec<f32> = (0..key_times).map(|i| i as f32 / 1000.0).collect();
doc.clips.push(Clip {
name: "clip".into(),
duration_s: f64::from(*times.last().expect("at least one key time")),
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times,
values: TrackValues::Vec3s(vec![Vec3::new(0.0, 1.0, 0.0); key_times]),
}],
});
doc
}
#[test]
fn a_document_above_the_sampling_budget_is_refused_with_a_typed_error() {
let doc = budget_document(200_000, 1_000);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert_eq!(
prove_scale(&doc, &candidate, &plan).unwrap_err(),
ScaleError::ProofSamplingBudgetExceeded {
policy_id: "appendix-d-v6",
sample_times: 200_000,
per_sample_cost: 2_007,
work: 401_400_000,
budget: 400_000_000,
}
);
}
#[test]
fn a_representative_in_budget_document_still_proves() {
let doc = budget_document(240, 2_000);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert!(
plan.obligations()
.contains(&ScaleProofObligation::SkinAndBounds)
);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 240);
}
fn work_unit_document() -> Document {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(1), 2, Vec3::new(0.0, 1.0, 0.0)),
];
let mut doc = rig_document(&nodes, &[1, 2], 0, Mat4::IDENTITY);
let primitive = |vertices: usize| Primitive {
positions: vec![Vec3::new(1.0, 0.0, 0.0); vertices],
joints: vec![[0, 0, 0, 0]; vertices],
weights: vec![[1.0, 0.0, 0.0, 0.0]; vertices],
..Primitive::default()
};
doc.assets.meshes[0].primitives = vec![primitive(5), primitive(7)];
let instance = |skin_joints: Vec<BoneId>| MeshInstance {
source_node_index: 2,
node: 1,
mesh: 0,
skin_ibms: vec![Mat4::IDENTITY; skin_joints.len()],
skin_joints,
};
doc.assets.instances = vec![
instance(vec![1, 1, 2]),
instance(vec![2]),
instance(vec![0]),
];
doc
}
#[test]
fn per_sample_work_units_charges_every_slot_primitive_and_document_side() {
let doc = work_unit_document();
let affected: BTreeSet<BoneId> = [1, 2].into_iter().collect();
let affected_skin_instances = affected_skin_instance_indices(&doc, &affected);
assert_eq!(affected_skin_instances, vec![0, 1]);
assert_eq!(per_sample_work_units(&doc, &affected_skin_instances), 66);
assert_eq!(per_sample_work_units(&doc, &[]), 6);
}
#[test]
fn per_sample_work_units_isolates_bone_slot_and_vertex_terms() {
let mut doc = work_unit_document();
doc.assets.instances.truncate(1);
doc.assets.meshes[0].primitives.clear();
assert_eq!(
per_sample_work_units(&doc, &[]),
6,
"two sides times three bones"
);
assert_eq!(
per_sample_work_units(&doc, &[0]),
15,
"bone term 6 + slot products on two sides 6 + slot residuals 3"
);
doc.assets.meshes[0].primitives.push(Primitive {
positions: vec![Vec3::ZERO; 5],
joints: vec![[0; 4]; 5],
weights: vec![[1.0, 0.0, 0.0, 0.0]; 5],
..Primitive::default()
});
assert_eq!(
per_sample_work_units(&doc, &[0]),
25,
"the five-vertex term adds exactly two sides times five"
);
}
#[test]
fn sampled_proof_classifies_unrelated_skin_palettes_once() {
let mut doc = compensated_document_with_unrelated_skin(Some(Mat4::IDENTITY));
doc.assets.instances[1].skin_joints = vec![3; 10_000];
doc.assets.instances[1].skin_ibms = vec![Mat4::IDENTITY; 10_000];
doc.clips.push(Clip {
name: "three-samples".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 0.5, 1.0],
values: TrackValues::Vec3s(vec![Vec3::new(0.0, 100.0, 0.0); 3]),
}],
});
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
reset_affected_skin_classification_steps();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(affected_skin_classification_steps(), 10_001);
assert_eq!(proof.sample_time_count, 3);
assert_eq!(proof.skin_matrix.comparisons(), 4);
}
#[test]
fn a_document_whose_skin_slots_dominate_its_work_is_refused() {
let mut doc = budget_document(26_484, 1);
let instance = MeshInstance {
source_node_index: 1,
node: 1,
mesh: 0,
skin_joints: vec![1; 100],
skin_ibms: vec![Mat4::IDENTITY; 100],
};
doc.assets.instances = vec![instance; 50];
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert!(
plan.obligations()
.contains(&ScaleProofObligation::SkinAndBounds)
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert_eq!(
prove_scale(&doc, &candidate, &plan).unwrap_err(),
ScaleError::ProofSamplingBudgetExceeded {
policy_id: "appendix-d-v6",
sample_times: 26_484,
per_sample_cost: 15_104,
work: 400_014_336,
budget: 400_000_000,
}
);
}
#[test]
fn cubic_segment_interior_times_count_toward_the_sampling_budget() {
let mut doc = budget_document(9_998, 10_000);
let keys = doc.clips[0].tracks[0].times.len();
assert_eq!(keys, 9_998);
doc.clips[0].tracks[0].interpolation = Interpolation::CubicSpline;
doc.clips[0].tracks[0].values = TrackValues::Vec3s(vec![Vec3::new(0.0, 1.0, 0.0); keys * 3]);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert_eq!(
prove_scale(&doc, &candidate, &plan).unwrap_err(),
ScaleError::ProofSamplingBudgetExceeded {
policy_id: "appendix-d-v6",
sample_times: 19_995,
per_sample_cost: 20_007,
work: 400_039_965,
budget: 400_000_000,
}
);
}
#[test]
fn the_sampling_budget_is_a_ceiling_a_document_may_reach() {
let tol = ScaleTolerancePolicy::APPENDIX_D_V6;
assert_eq!(tol.proof_sample_work_budget, 400_000_000);
assert_eq!(check_sampling_budget(&tol, 20_000, 20_000), Ok(()));
assert_eq!(
check_sampling_budget(&tol, 20_001, 20_000),
Err(ScaleError::ProofSamplingBudgetExceeded {
policy_id: "appendix-d-v6",
sample_times: 20_001,
per_sample_cost: 20_000,
work: 400_020_000,
budget: 400_000_000,
})
);
assert_eq!(
check_sampling_budget(&tol, 400_000_001, 1),
Err(ScaleError::ProofSamplingBudgetExceeded {
policy_id: "appendix-d-v6",
sample_times: 400_000_001,
per_sample_cost: 1,
work: 400_000_001,
budget: 400_000_000,
})
);
}
#[test]
fn an_overflowing_work_product_saturates_instead_of_wrapping_under_the_budget() {
let tol = ScaleTolerancePolicy::APPENDIX_D_V6;
let sample_times = 9_223_372_036_854_775_808; assert_eq!(
check_sampling_budget(&tol, sample_times, 2),
Err(ScaleError::ProofSamplingBudgetExceeded {
policy_id: "appendix-d-v6",
sample_times,
per_sample_cost: 2,
work: u64::MAX,
budget: 400_000_000,
})
);
}
#[test]
fn duplicate_key_and_interior_times_across_tracks_are_charged_and_sampled_once() {
let mut doc = budget_document(4, 1);
let track = &mut doc.clips[0].tracks[0];
assert_eq!(track.bone, 1);
track.interpolation = Interpolation::CubicSpline;
track.values = TrackValues::Vec3s(vec![Vec3::new(0.0, 1.0, 0.0); 4 * 3]);
let mut twin = track.clone();
twin.bone = 0;
doc.clips[0].tracks.push(twin);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 7);
}
#[test]
fn a_candidate_skeleton_may_not_carry_bones_the_budget_never_charged() {
let doc = budget_document(4_000, 1);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let affected = plan.affected_set();
let affected_skin_instances = affected_skin_instance_indices(&doc, &affected);
assert_eq!(per_sample_work_units(&doc, &affected_skin_instances), 9);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut padded = candidate.document().clone();
let root = padded.skeleton.bones[0].clone();
padded
.skeleton
.bones
.extend(std::iter::repeat_n(root, 60_000));
assert_eq!(padded.skeleton.bones.len(), 60_002);
let padded = ScaleCandidate { document: padded };
assert_eq!(
prove_scale(&doc, &padded, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "bone_count_mismatch",
}
);
prove_scale(&doc, &candidate, &plan).unwrap();
}
#[test]
fn a_non_positive_or_non_finite_expected_factor_is_invalid_not_a_factor_mismatch() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let capability = complete_capability();
for factor in [0.0, -1.0, f64::NAN] {
let request = ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: factor,
},
document: &doc,
capability: &capability,
};
match plan_scale(&request).unwrap_err() {
ScaleError::InvalidExpectedFactor { factor: rejected } => {
assert_eq!(rejected.is_nan(), factor.is_nan(), "{factor}");
if !factor.is_nan() {
assert_eq!(rejected, factor);
}
}
other => panic!("expected InvalidExpectedFactor for {factor}, got {other:?}"),
}
}
}
const NON_IDENTITY_BIND: Mat4 = Mat4::from_cols(
Vec4::new(0.0, 4.0, 0.0, 0.0),
Vec4::new(-4.0, 0.0, 0.0, 0.0),
Vec4::new(0.0, 0.0, 4.0, 0.0),
Vec4::new(5.0, -6.0, 7.0, 1.0),
);
#[test]
fn whole_document_conversion_conjugates_a_non_identity_bind_and_the_bone_convenience_value() {
let mut doc = rig_document(&unit_rig(), &[1], 0, NON_IDENTITY_BIND);
doc.skeleton.bones[1].inverse_bind = Some(NON_IDENTITY_BIND);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
for (label, converted) in [
(
"instance",
candidate.document().assets.instances[0].skin_ibms[0],
),
(
"bone",
candidate.document().skeleton.bones[1]
.inverse_bind
.expect("bone bind is retained"),
),
] {
assert_eq!(converted.x_axis, Vec4::new(0.0, 4.0, 0.0, 0.0), "{label}");
assert_eq!(converted.y_axis, Vec4::new(-4.0, 0.0, 0.0, 0.0), "{label}");
assert_eq!(converted.z_axis, Vec4::new(0.0, 0.0, 4.0, 0.0), "{label}");
assert!(
converted
.w_axis
.abs_diff_eq(Vec4::new(0.05, -0.06, 0.07, 1.0), 1e-7),
"{label}: {:?}",
converted.w_axis
);
}
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert!(proof.skin_matrix.max() < 1e-4);
}
#[test]
fn rest_bind_rewrites_the_bone_convenience_inverse_bind_it_falls_back_to() {
let nodes = vec![
RigNode {
parent: None,
source_node_index: 0,
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::splat(0.01),
},
rig(Some(0), 1, Vec3::new(0.0, 100.0, 0.0)),
];
let mut doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
doc.assets.instances[0].skin_ibms.clear();
doc.skeleton.bones[1].inverse_bind = Some(Mat4::from_scale_rotation_translation(
Vec3::splat(100.0),
Quat::IDENTITY,
Vec3::new(0.0, -100.0, 0.0),
));
let capability = complete_capability();
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: &doc,
capability: &capability,
})
.unwrap();
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let expected = Mat4::from_translation(Vec3::new(0.0, -1.0, 0.0));
let bone_bind = candidate.document().skeleton.bones[1]
.inverse_bind
.expect("bone bind is retained");
assert!(bone_bind.abs_diff_eq(expected, 1e-5), "{bone_bind:?}");
let materialized = &candidate.document().assets.instances[0].skin_ibms;
assert_eq!(materialized.len(), 1);
assert!(materialized[0].abs_diff_eq(expected, 1e-5));
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert!(proof.skin_matrix.max() < 1e-4);
}
fn push_unrelated_skin(doc: &mut Document, bind: Option<Mat4>) -> BoneId {
let bone = doc.skeleton.bones.len();
let source_node_index = doc
.assets
.source_skeleton
.nodes
.iter()
.map(|node| node.source_node_index)
.max()
.expect("the base document projects at least one node")
+ 1;
doc.skeleton.bones.push(Bone {
name: "unrelated".into(),
parent: None,
rest: Transform {
translation: Vec3::new(5.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
inverse_bind: None,
});
doc.assets.source_skeleton.nodes.push(SourceNodeAsset {
source_node_index,
name: None,
parent_source_node_index: None,
scene_root_indices: vec![0],
local_rest: SourceNodeLocalRest::Trs {
translation: Vec3::new(5.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
bone: Some(bone),
});
let source_skin_index = doc
.assets
.source_skeleton
.skins
.iter()
.map(|skin| skin.source_skin_index)
.max()
.expect("the base document projects at least one skin")
+ 1;
doc.assets.source_skeleton.skins.push(SourceSkinAsset {
source_skin_index,
name: None,
skeleton_root_source_node_index: None,
joint_source_node_indices: vec![source_node_index],
inverse_bind_accessor: SourceInverseBindAccessor::default(),
attachments: Vec::new(),
});
doc.assets.instances.push(MeshInstance {
source_node_index,
node: bone,
mesh: 0,
skin_joints: vec![bone],
skin_ibms: bind.into_iter().collect(),
});
bone
}
fn compensated_document_with_unrelated_skin(bind: Option<Mat4>) -> Document {
let mut doc = compensated_document();
assert_eq!(push_unrelated_skin(&mut doc, bind), 3);
doc
}
fn attach_unrelated_source_skin(doc: &mut Document, status: SourceInverseBindAccessorStatus) {
assert_eq!(
doc.assets.source_skeleton.coverage,
SourceSkeletonCoverage::Complete
);
let instance = &doc.assets.instances[1];
let source_node_index = instance.source_node_index;
let source_mesh_index = Some(doc.assets.meshes[instance.mesh].source_mesh_index);
let skin = doc
.assets
.source_skeleton
.skins
.last_mut()
.expect("the unrelated skin has source evidence");
assert_eq!(
skin.inverse_bind_accessor.status,
SourceInverseBindAccessorStatus::Absent
);
skin.inverse_bind_accessor.status = status;
skin.attachments = vec![SourceSkinAttachment {
source_node_index,
source_mesh_index,
}];
}
fn compensated_rest_bind_plan(doc: &Document, capability: &ScaleCapabilityFacts) -> ScalePlan {
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 0.01,
},
document: doc,
capability,
})
.unwrap()
}
#[test]
fn a_rewritten_unaffected_skins_inverse_binds_are_named_by_their_own_obligation() {
let doc = compensated_document_with_unrelated_skin(Some(Mat4::from_scale(Vec3::splat(2.0))));
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert_eq!(
candidate.document().assets.instances[1].skin_ibms,
doc.assets.instances[1].skin_ibms
);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.unaffected_inverse_bind.max(), 0.0);
let mut broken = candidate.document().clone();
broken.assets.instances[1].skin_ibms[0] = Mat4::from_scale(Vec3::splat(3.0));
let error = prove_scale(&doc, &ScaleCandidate { document: broken }, &plan).unwrap_err();
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::UnaffectedInverseBind,
observed,
..
} = error
else {
panic!("expected an unaffected-inverse-bind residual, got {error:?}");
};
assert_eq!(observed, 1.0);
}
#[test]
fn a_successful_unaffected_bind_proof_reports_the_residual_maximum_it_observed() {
let bind = |scale: Vec3| {
let mut bind = Mat4::from_scale(scale);
bind.w_axis.x = 64.0;
bind
};
let doc = compensated_document_with_unrelated_skin(Some(bind(Vec3::splat(2.0))));
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut nudged = candidate.document().clone();
nudged.assets.instances[1].skin_ibms[0] =
bind(Vec3::new(2.0 + 2f32.powi(-18), 2.0 + 2f32.powi(-17), 2.0));
let proof = prove_scale(&doc, &ScaleCandidate { document: nudged }, &plan).unwrap();
assert_eq!(proof.unaffected_inverse_bind.max(), 2f64.powi(-17));
}
#[test]
fn a_partially_affected_skin_stays_with_the_skin_obligation_that_owns_it() {
let unaffected_bind = Mat4::from_scale(Vec3::splat(2.0));
let mut doc = compensated_document_with_unrelated_skin(Some(unaffected_bind));
let affected_bind = doc.assets.instances[0].skin_ibms[0];
doc.assets.instances[1].skin_joints = vec![3, 1];
doc.assets.instances[1].skin_ibms = vec![unaffected_bind, affected_bind];
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let rebased = &candidate.document().assets.instances[1].skin_ibms;
assert_eq!(rebased[0], unaffected_bind);
assert_ne!(rebased[1], affected_bind);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.unaffected_inverse_bind.max(), 0.0);
}
#[test]
fn a_slot_carrying_both_an_array_and_a_bone_bind_is_compared_through_the_array() {
let array_bind = Mat4::from_scale(Vec3::splat(2.0));
let bone_bind = Mat4::from_scale(Vec3::splat(4.0));
let mut doc = compensated_document_with_unrelated_skin(Some(array_bind));
doc.skeleton.bones[3].inverse_bind = Some(bone_bind);
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut rewritten_array = candidate.document().clone();
rewritten_array.assets.instances[1].skin_ibms[0] = Mat4::from_scale(Vec3::splat(3.0));
let error = prove_scale(
&doc,
&ScaleCandidate {
document: rewritten_array,
},
&plan,
)
.unwrap_err();
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::UnaffectedInverseBind,
observed,
..
} = error
else {
panic!("expected an unaffected-inverse-bind residual, got {error:?}");
};
assert_eq!(observed, 1.0);
let mut rewritten_bone = candidate.document().clone();
rewritten_bone.skeleton.bones[3].inverse_bind = Some(Mat4::from_scale(Vec3::splat(5.0)));
let proof = prove_scale(
&doc,
&ScaleCandidate {
document: rewritten_bone,
},
&plan,
)
.unwrap();
assert_eq!(proof.unaffected_inverse_bind.max(), 0.0);
}
#[test]
fn an_unaffected_skin_resolved_through_its_bones_is_proved_the_same_way() {
let mut doc = compensated_document_with_unrelated_skin(None);
doc.skeleton.bones[3].inverse_bind = Some(Mat4::from_scale(Vec3::splat(2.0)));
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.unaffected_inverse_bind.max(), 0.0);
let mut broken = candidate.document().clone();
broken.skeleton.bones[3].inverse_bind = Some(Mat4::from_scale(Vec3::splat(3.0)));
let error = prove_scale(&doc, &ScaleCandidate { document: broken }, &plan).unwrap_err();
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::UnaffectedInverseBind,
observed,
..
} = error
else {
panic!("expected an unaffected-inverse-bind residual, got {error:?}");
};
assert_eq!(observed, 1.0);
}
#[test]
fn an_unaffected_skin_with_no_bind_evidence_on_either_side_still_proves() {
let mut doc = compensated_document_with_unrelated_skin(None);
attach_unrelated_source_skin(&mut doc, SourceInverseBindAccessorStatus::Unreadable);
assert!(doc.assets.instances[1].skin_ibms.is_empty());
assert!(doc.skeleton.bones[3].inverse_bind.is_none());
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.unaffected_inverse_bind.comparisons(), 0);
assert_eq!(proof.unaffected_inverse_bind.max(), 0.0);
}
fn two_defaulted_identity_slots_document() -> Document {
let mut doc = compensated_document_with_unrelated_skin(None);
attach_unrelated_source_skin(&mut doc, SourceInverseBindAccessorStatus::Absent);
let second_bone = doc.skeleton.bones.len();
let second_source_node_index = doc
.assets
.source_skeleton
.nodes
.iter()
.map(|node| node.source_node_index)
.max()
.expect("the document projects at least one node")
+ 1;
let second_rest = Transform {
translation: Vec3::new(7.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
doc.skeleton.bones.push(Bone {
name: "unrelated-second".into(),
parent: None,
rest: second_rest,
inverse_bind: None,
});
doc.assets.source_skeleton.nodes.push(SourceNodeAsset {
source_node_index: second_source_node_index,
name: None,
parent_source_node_index: None,
scene_root_indices: vec![0],
local_rest: SourceNodeLocalRest::Trs {
translation: second_rest.translation,
rotation: second_rest.rotation,
scale: second_rest.scale,
},
bone: Some(second_bone),
});
doc.assets
.source_skeleton
.skins
.last_mut()
.expect("the unrelated skin has source evidence")
.joint_source_node_indices
.push(second_source_node_index);
doc.assets.instances[1].skin_joints.push(second_bone);
assert!(doc.assets.instances[1].skin_ibms.is_empty());
assert!(
doc.skeleton.bones[3..]
.iter()
.all(|bone| bone.inverse_bind.is_none())
);
doc
}
#[test]
fn two_defaulted_identity_slots_are_each_compared_as_effective_binds() {
let doc = two_defaulted_identity_slots_document();
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
assert!(
candidate.document().assets.instances[1]
.skin_ibms
.is_empty()
);
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.unaffected_inverse_bind.comparisons(), 2);
assert_eq!(proof.unaffected_inverse_bind.max(), 0.0);
}
fn assert_unaffected_bind_rewrite(error: ScaleError) {
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::UnaffectedInverseBind,
observed,
..
} = error
else {
panic!("expected an unaffected-inverse-bind residual, got {error:?}");
};
assert_eq!(observed, 1.0);
}
#[test]
fn materializing_a_different_bind_in_defaulted_slot_zero_is_refused() {
let doc = two_defaulted_identity_slots_document();
let plan = compensated_rest_bind_plan(&doc, &complete_capability());
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut rewritten = candidate.document().clone();
rewritten.assets.instances[1].skin_ibms =
vec![Mat4::from_scale(Vec3::splat(2.0)), Mat4::IDENTITY];
let error = prove_scale(
&doc,
&ScaleCandidate {
document: rewritten,
},
&plan,
)
.unwrap_err();
assert_unaffected_bind_rewrite(error);
}
#[test]
fn materializing_a_different_bind_in_defaulted_slot_one_is_refused() {
let doc = two_defaulted_identity_slots_document();
let plan = compensated_rest_bind_plan(&doc, &complete_capability());
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut rewritten = candidate.document().clone();
rewritten.assets.instances[1].skin_ibms =
vec![Mat4::IDENTITY, Mat4::from_scale(Vec3::splat(2.0))];
let error = prove_scale(
&doc,
&ScaleCandidate {
document: rewritten,
},
&plan,
)
.unwrap_err();
assert_unaffected_bind_rewrite(error);
}
#[test]
fn explicit_and_defaulted_identity_are_the_same_unaffected_bind_in_both_directions() {
let capability = complete_capability();
let mut defaulted = compensated_document_with_unrelated_skin(None);
attach_unrelated_source_skin(&mut defaulted, SourceInverseBindAccessorStatus::Absent);
let plan = compensated_rest_bind_plan(&defaulted, &capability);
let candidate = build_scale_candidate(&defaulted, &plan).unwrap();
let mut explicit = candidate.document().clone();
explicit.assets.instances[1].skin_ibms = vec![Mat4::IDENTITY];
let proof = prove_scale(&defaulted, &ScaleCandidate { document: explicit }, &plan).unwrap();
assert_eq!(proof.unaffected_inverse_bind.comparisons(), 1);
assert_eq!(proof.unaffected_inverse_bind.max(), 0.0);
let mut explicit = compensated_document_with_unrelated_skin(Some(Mat4::IDENTITY));
attach_unrelated_source_skin(&mut explicit, SourceInverseBindAccessorStatus::Absent);
let plan = compensated_rest_bind_plan(&explicit, &capability);
let candidate = build_scale_candidate(&explicit, &plan).unwrap();
let mut defaulted = candidate.document().clone();
defaulted.assets.instances[1].skin_ibms.clear();
let proof = prove_scale(
&explicit,
&ScaleCandidate {
document: defaulted,
},
&plan,
)
.unwrap();
assert_eq!(proof.unaffected_inverse_bind.comparisons(), 1);
assert_eq!(proof.unaffected_inverse_bind.max(), 0.0);
}
#[test]
fn a_materialized_nonidentity_default_is_a_rewritten_unaffected_bind() {
let capability = complete_capability();
let mut defaulted = compensated_document_with_unrelated_skin(None);
attach_unrelated_source_skin(&mut defaulted, SourceInverseBindAccessorStatus::Absent);
let plan = compensated_rest_bind_plan(&defaulted, &capability);
let candidate = build_scale_candidate(&defaulted, &plan).unwrap();
let mut rewritten = candidate.document().clone();
rewritten.assets.instances[1].skin_ibms = vec![Mat4::from_scale(Vec3::splat(2.0))];
let error = prove_scale(
&defaulted,
&ScaleCandidate {
document: rewritten,
},
&plan,
)
.unwrap_err();
let ScaleError::ProofResidualExceeded {
kind: ProofResidualKind::UnaffectedInverseBind,
observed,
..
} = error
else {
panic!("expected an unaffected-inverse-bind residual, got {error:?}");
};
assert_eq!(observed, 1.0);
}
#[test]
fn an_unaffected_skin_whose_bind_evidence_appears_on_only_one_side_is_missing_not_proven() {
let capability = complete_capability();
let doc = compensated_document_with_unrelated_skin(Some(Mat4::from_scale(Vec3::splat(2.0))));
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut dropped = candidate.document().clone();
dropped.assets.instances[1].skin_ibms.clear();
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: dropped }, &plan).unwrap_err(),
ScaleError::MissingProofEvidence {
kind: ProofResidualKind::UnaffectedInverseBind,
detail: "candidate_slot_bind_missing",
}
);
let bare = compensated_document_with_unrelated_skin(None);
let bare_plan = compensated_rest_bind_plan(&bare, &capability);
let bare_candidate = build_scale_candidate(&bare, &bare_plan).unwrap();
let mut invented = bare_candidate.document().clone();
invented.assets.instances[1].skin_ibms = vec![Mat4::from_scale(Vec3::splat(2.0))];
assert_eq!(
prove_scale(&bare, &ScaleCandidate { document: invented }, &bare_plan).unwrap_err(),
ScaleError::MissingProofEvidence {
kind: ProofResidualKind::UnaffectedInverseBind,
detail: "source_slot_bind_missing",
}
);
}
#[test]
fn a_whole_document_plan_cannot_omit_a_bone_added_after_planning() {
let capability = complete_capability();
let planned = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let plan = plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &planned,
capability: &capability,
})
.unwrap();
assert_eq!(plan.affected_nodes(), &[0, 1]);
let mut wider = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let added = push_unrelated_skin(
&mut wider,
Some(Mat4::from_translation(Vec3::new(100.0, 0.0, 0.0))),
);
assert_eq!(added, 2);
let expected = ScaleError::PlanDocumentMismatch {
reason: "affected_nodes_mismatch",
};
assert_eq!(build_scale_candidate(&wider, &plan).unwrap_err(), expected);
let wider_plan = whole_document_plan(&wider, &complete_capability());
let mut omitted = build_whole_document(&wider, &wider_plan).unwrap();
omitted.skeleton.bones[added].rest = wider.skeleton.bones[added].rest;
let source_local_rest = wider
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(added))
.unwrap()
.local_rest
.clone();
omitted
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(added))
.unwrap()
.local_rest = source_local_rest;
omitted.assets.instances[1].skin_ibms[0] = wider.assets.instances[1].skin_ibms[0];
assert_eq!(
prove_scale(&wider, &ScaleCandidate { document: omitted }, &plan).unwrap_err(),
expected
);
}
fn rest_bind_reject_reason(document: &Document) -> ScaleError {
let capability = complete_capability();
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document,
capability: &capability,
})
.unwrap_err()
}
fn closure_reject_reason(document: &Document, source_root_node_index: usize) -> ScaleError {
let by_source_index = source_node_index_map(document);
let skin = resolve_rest_bind_skin(document, 0).expect("the fixture declares source skin 0");
rest_bind_affected_closure(document, &by_source_index, skin, source_root_node_index)
.expect_err("the fixture's projection is malformed")
}
fn source_world_reject_reason(document: &Document, start: usize) -> ScaleError {
let by_source_index = source_node_index_map(document);
let mut cache = BTreeMap::new();
source_world_matrix(start, &by_source_index, &BTreeSet::new(), &mut cache)
.expect_err("the fixture's projection is malformed")
}
#[test]
fn a_skin_joint_with_no_source_node_projection_names_its_own_closure_reason() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.assets.source_skeleton.skins[0]
.joint_source_node_indices
.push(99);
assert_eq!(
rest_bind_reject_reason(&doc),
ScaleError::IncompleteClosure {
reason: "skin_joint_source_node_missing"
}
);
}
#[test]
fn a_valid_unprojected_selected_joint_is_refused_as_not_normalized() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let mut joint = SourceNodeAsset::new(5, SourceNodeLocalRest::Matrix(Mat4::IDENTITY));
joint.parent_source_node_index = Some(0);
doc.assets.source_skeleton.nodes.push(joint);
doc.assets.source_skeleton.skins[0]
.joint_source_node_indices
.push(5);
assert_eq!(
rest_bind_reject_reason(&doc),
ScaleError::SourceNodeNotNormalized {
source_node_index: 5
}
);
}
#[test]
fn an_unprojected_selected_root_is_refused_as_not_normalized() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.assets.source_skeleton.nodes[0].bone = None;
doc.skeleton.bones[1].parent = None;
let capability = complete_capability();
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::RestBindUniformScale {
source_skin_index: 0,
source_root_node_index: 0,
expected_factor: 1.0,
},
document: &doc,
capability: &capability,
})
.unwrap_err(),
ScaleError::SourceNodeNotNormalized {
source_node_index: 0
}
);
}
fn unprojected_skin_joint_document(chain: &[(usize, Option<usize>)]) -> Document {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
for &(source_node_index, parent) in chain {
let mut evidence = SourceNodeAsset::new(
source_node_index,
SourceNodeLocalRest::Matrix(Mat4::IDENTITY),
);
evidence.parent_source_node_index = parent;
assert_eq!(evidence.bone, None);
doc.assets.source_skeleton.nodes.push(evidence);
}
doc.assets.source_skeleton.skins[0]
.joint_source_node_indices
.push(chain[0].0);
doc
}
#[test]
fn an_unprojected_skin_joint_with_a_dangling_parent_is_refused_by_planning() {
let doc = unprojected_skin_joint_document(&[(5, Some(99))]);
assert_eq!(
rest_bind_reject_reason(&doc),
ScaleError::IncompleteClosure {
reason: "dangling_source_parent_node_index"
}
);
}
#[test]
fn an_unprojected_skin_joint_on_a_cyclic_chain_is_refused_by_planning() {
let doc = unprojected_skin_joint_document(&[(5, Some(6)), (6, Some(5))]);
assert_eq!(
rest_bind_reject_reason(&doc),
ScaleError::IncompleteClosure {
reason: "cyclic_or_unbounded_source_parent_chain"
}
);
}
#[test]
fn a_descendant_claimed_as_a_joint_by_another_skin_names_its_own_closure_reason() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(1), 2, Vec3::new(0.0, 1.0, 0.0)),
];
let mut doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
doc.assets.source_skeleton.skins.push(SourceSkinAsset {
source_skin_index: 1,
name: None,
skeleton_root_source_node_index: None,
joint_source_node_indices: vec![2],
inverse_bind_accessor: SourceInverseBindAccessor::default(),
attachments: Vec::new(),
});
assert_eq!(
rest_bind_reject_reason(&doc),
ScaleError::IncompleteClosure {
reason: "descendant_joint_of_another_skin"
}
);
}
#[test]
fn a_joint_ancestor_chain_that_never_reaches_the_root_names_its_own_closure_reason() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(1), 2, Vec3::new(0.0, 1.0, 0.0)),
];
let mut doc = rig_document(&nodes, &[2], 0, Mat4::IDENTITY);
doc.assets.source_skeleton.nodes[1].parent_source_node_index = Some(2);
assert_eq!(
closure_reject_reason(&doc, 0),
ScaleError::IncompleteClosure {
reason: "cyclic_or_unbounded_source_parent_chain"
}
);
assert_eq!(
rest_bind_reject_reason(&doc),
ScaleError::InvalidDocumentShape(DocumentShapeError::SourceProjection {
source_node_index: 1,
violation: SourceProjectionViolation::NearestProjectedParentMismatch,
})
);
}
#[test]
fn a_cyclic_rest_world_parent_chain_names_its_own_closure_reason() {
let nodes = vec![
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(0), 2, Vec3::new(0.0, 1.0, 0.0)),
];
let mut doc = rig_document(&nodes, &[1], 0, Mat4::IDENTITY);
doc.assets.source_skeleton.nodes[0].parent_source_node_index = Some(2);
assert_eq!(
source_world_reject_reason(&doc, 0),
ScaleError::IncompleteClosure {
reason: "cyclic_source_parent_chain"
}
);
assert_eq!(
rest_bind_reject_reason(&doc),
ScaleError::InvalidDocumentShape(DocumentShapeError::SourceProjection {
source_node_index: 0,
violation: SourceProjectionViolation::NearestProjectedParentMismatch,
})
);
}
#[test]
fn a_rest_world_ancestor_outside_the_projection_names_its_own_closure_reason() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.assets.source_skeleton.nodes[0].parent_source_node_index = Some(99);
assert_eq!(
source_world_reject_reason(&doc, 0),
ScaleError::IncompleteClosure {
reason: "missing_source_node"
}
);
assert_eq!(
rest_bind_reject_reason(&doc),
ScaleError::InvalidDocumentShape(DocumentShapeError::SourceProjection {
source_node_index: 0,
violation: SourceProjectionViolation::ParentSourceNodeMissing,
})
);
}
type StructureMismatchCase = (&'static str, fn(&mut Document));
#[test]
fn every_candidate_structure_mismatch_names_its_own_reason() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
doc.clips.push(Clip {
name: "clip".into(),
duration_s: 1.0,
tracks: vec![Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::Linear,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![Vec3::ZERO, Vec3::ONE]),
}],
});
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let cases: [StructureMismatchCase; 14] = [
("bone_count_mismatch", |d| {
let root = d.skeleton.bones[0].clone();
d.skeleton.bones.push(root);
}),
("skeleton_topology_mismatch", |d| {
d.skeleton.bones[1].parent = None;
d.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(1))
.unwrap()
.parent_source_node_index = None;
}),
("track_count_mismatch", |d| {
d.clips[0].tracks.push(Track {
bone: 1,
property: Property::Rotation,
interpolation: Interpolation::Linear,
times: vec![0.0],
values: TrackValues::Quats(vec![Quat::IDENTITY]),
})
}),
("track_shape_mismatch", |d| {
d.clips[0].tracks[0].interpolation = Interpolation::Step
}),
("track_shape_mismatch", |d| d.clips[0].tracks[0].bone = 0),
("track_shape_mismatch", |d| {
d.clips[0].tracks[0].property = Property::Scale
}),
("instance_count_mismatch", |d| {
let extra = d.assets.instances[0].clone();
d.assets.instances.push(extra);
}),
("instance_node_mismatch", |d| d.assets.instances[0].node = 0),
("instance_source_node_index_mismatch", |d| {
d.assets.instances[0].source_node_index = 0
}),
("instance_mesh_mismatch", |d| {
let extra = d.assets.meshes[0].clone();
d.assets.meshes.push(extra);
d.assets.instances[0].mesh = 1;
}),
("instance_skin_joints_mismatch", |d| {
d.assets.instances[0].skin_joints = vec![0];
}),
("mesh_count_mismatch", |d| {
let extra = d.assets.meshes[0].clone();
d.assets.meshes.push(extra);
}),
("primitive_count_mismatch", |d| {
let extra = d.assets.meshes[0].primitives[0].clone();
d.assets.meshes[0].primitives.push(extra);
}),
("primitive_vertex_count_mismatch", |d| {
d.assets.meshes[0].primitives[0].positions.push(Vec3::ZERO);
d.assets.meshes[0].primitives[0].joints.push([0, 0, 0, 0]);
d.assets.meshes[0].primitives[0]
.weights
.push([1.0, 0.0, 0.0, 0.0]);
}),
];
for (expected, doctor) in cases {
let mut broken = candidate.document().clone();
doctor(&mut broken);
let broken = ScaleCandidate { document: broken };
assert_eq!(
prove_scale(&doc, &broken, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch { reason: expected }
);
}
}
#[test]
fn a_candidate_that_relocates_a_mesh_instance_is_refused_field_by_field() {
let doc = rig_document(
&[
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::new(0.0, 1.0, 0.0)),
rig(Some(1), 2, Vec3::new(0.0, 1.0, 0.0)),
],
&[1],
0,
Mat4::IDENTITY,
);
assert_eq!(doc.skeleton.bones.len(), 3);
assert_eq!(doc.assets.instances[0].node, 1);
assert_eq!(doc.assets.instances[0].source_node_index, 1);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).unwrap();
let refuse = |doctor: &dyn Fn(&mut MeshInstance)| {
let mut broken = candidate.document().clone();
doctor(&mut broken.assets.instances[0]);
prove_scale(&doc, &ScaleCandidate { document: broken }, &plan).unwrap_err()
};
for doctored in [0, 2] {
assert_eq!(
refuse(&|instance| instance.node = doctored),
ScaleError::CandidateStructureMismatch {
reason: "instance_node_mismatch"
},
"moving the instance onto bone {doctored} must be refused"
);
}
for doctored in [0, 2] {
assert_eq!(
refuse(&|instance| instance.source_node_index = doctored),
ScaleError::CandidateStructureMismatch {
reason: "instance_source_node_index_mismatch"
},
"re-pointing the instance at source node {doctored} must be refused"
);
}
assert_eq!(
refuse(&|instance| {
instance.node = 0;
instance.source_node_index = 0;
}),
ScaleError::CandidateStructureMismatch {
reason: "instance_node_mismatch"
}
);
}
#[test]
fn a_candidate_that_swaps_two_payload_identical_instances_is_refused() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let twin = MeshInstance {
source_node_index: 0,
node: 0,
..doc.assets.instances[0].clone()
};
doc.assets.instances.push(twin);
let (first, second) = (&doc.assets.instances[0], &doc.assets.instances[1]);
assert_eq!(first.mesh, second.mesh);
assert_eq!(first.skin_joints, second.skin_joints);
assert_eq!(first.skin_ibms, second.skin_ibms);
assert_ne!(first.node, second.node);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).unwrap();
let mut swapped = candidate.document().clone();
swapped.assets.instances.swap(0, 1);
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: swapped }, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "instance_node_mismatch"
}
);
}
fn compensated_document_with_unskinned_prop() -> (Document, BoneId) {
let mut doc = compensated_document();
let bone = doc.skeleton.bones.len();
let source_node_index = doc
.assets
.source_skeleton
.nodes
.iter()
.map(|node| node.source_node_index)
.max()
.expect("the base document projects at least one node")
+ 1;
let rest = Transform {
translation: Vec3::new(5.0, 0.0, 0.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
doc.skeleton.bones.push(Bone {
name: "prop".into(),
parent: None,
rest,
inverse_bind: None,
});
doc.assets.source_skeleton.nodes.push(SourceNodeAsset {
source_node_index,
name: None,
parent_source_node_index: None,
scene_root_indices: vec![0],
local_rest: SourceNodeLocalRest::Trs {
translation: rest.translation,
rotation: rest.rotation,
scale: rest.scale,
},
bone: Some(bone),
});
doc.assets.meshes.push(MeshAsset {
name: "prop".into(),
source_mesh_index: 1,
primitives: vec![Primitive {
positions: vec![Vec3::new(1.0, 0.0, 0.0)],
..Primitive::default()
}],
});
let mesh = doc.assets.meshes.len() - 1;
doc.assets.instances.push(MeshInstance {
source_node_index,
node: bone,
mesh,
skin_joints: Vec::new(),
skin_ibms: Vec::new(),
});
(doc, bone)
}
#[test]
fn a_rest_bind_candidate_that_relocates_an_unskinned_prop_is_refused() {
let (doc, prop) = compensated_document_with_unskinned_prop();
assert_eq!(prop, 3);
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
assert_eq!(doc.assets.instances[1].node, prop);
assert!(doc.assets.instances[1].skin_joints.is_empty());
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.rest_translation.max(), 0.0);
assert_eq!(proof.rest_rotation.max(), 0.0);
assert!(proof.rest_rotation.evaluated());
assert_eq!(proof.mesh_position.max(), 0.0);
assert_eq!(proof.unaffected_inverse_bind.max(), 0.0);
let mut relocated = candidate.document().clone();
relocated.assets.instances[1].node = 2;
assert_eq!(
relocated.assets.instances[1].source_node_index,
candidate.document().assets.instances[1].source_node_index,
"only the node placing the prop may differ"
);
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate {
document: relocated
},
&plan
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "instance_node_mismatch"
}
);
}
#[test]
fn a_rest_bind_candidate_that_changes_an_unaffected_world_rest_is_refused() {
let (doc, prop) = compensated_document_with_unskinned_prop();
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
assert!(!plan.affected_nodes().contains(&prop));
let candidate = build_scale_candidate(&doc, &plan).unwrap();
prove_scale(&doc, &candidate, &plan).unwrap();
for mutate in [
|rest: &mut Transform| rest.translation.x = 500.0,
|rest: &mut Transform| {
rest.translation.x = f32::from_bits(rest.translation.x.to_bits() + 1)
},
|rest: &mut Transform| rest.rotation = Quat::from_rotation_z(0.5),
|rest: &mut Transform| rest.scale.y = 2.0,
] as [fn(&mut Transform); 4]
{
let mut changed = candidate.document().clone();
mutate(&mut changed.skeleton.bones[prop].rest);
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: changed }, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "unaffected_world_rest_mismatch"
}
);
}
}
#[test]
fn a_coherently_reparented_candidate_is_a_topology_mismatch() {
let (doc, prop) = compensated_document_with_unskinned_prop();
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut reparented = candidate.document().clone();
let new_parent_source = reparented
.assets
.source_skeleton
.nodes
.iter()
.find(|node| node.bone == Some(2))
.map(|node| node.source_node_index)
.unwrap();
reparented.skeleton.bones[prop].parent = Some(2);
reparented
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.bone == Some(prop))
.unwrap()
.parent_source_node_index = Some(new_parent_source);
validate_scale_input(&reparented).unwrap();
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate {
document: reparented
},
&plan
)
.unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "skeleton_topology_mismatch"
}
);
}
#[test]
fn topology_remains_exact_when_whole_document_affects_every_bone() {
let (mut doc, prop) = compensated_document_with_unskinned_prop();
doc.assets.source_skeleton.coverage = SourceSkeletonCoverage::Unavailable;
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2, 3]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut changed = candidate.document().clone();
changed.skeleton.bones[prop].parent = Some(2);
validate_scale_input(&changed).unwrap();
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: changed }, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "skeleton_topology_mismatch"
}
);
}
#[test]
fn source_projection_identity_cannot_be_downgraded_by_the_candidate() {
let (doc, _) = compensated_document_with_unskinned_prop();
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut changed = candidate.document().clone();
changed.assets.source_skeleton.coverage = SourceSkeletonCoverage::Unavailable;
validate_scale_input(&changed).unwrap();
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: changed }, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "skeleton_topology_mismatch"
}
);
}
#[test]
fn complete_projection_row_identity_is_compared_independently_of_bone_parents() {
let (doc, _) = compensated_document_with_unskinned_prop();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut changed = candidate.into_document();
changed.assets.source_skeleton.nodes.push(SourceNodeAsset {
source_node_index: 100,
name: None,
parent_source_node_index: None,
scene_root_indices: vec![0],
local_rest: SourceNodeLocalRest::Trs {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
bone: None,
});
validate_scale_input(&changed).unwrap();
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: changed }, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "skeleton_topology_mismatch"
}
);
}
#[test]
fn complete_projection_raw_parents_are_compared_independently_of_bone_parents() {
let (mut doc, _) = compensated_document_with_unskinned_prop();
for source_node_index in [100, 101] {
doc.assets.source_skeleton.nodes.push(SourceNodeAsset {
source_node_index,
name: None,
parent_source_node_index: None,
scene_root_indices: vec![0],
local_rest: SourceNodeLocalRest::Trs {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
},
bone: None,
});
}
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut changed = candidate.into_document();
changed
.assets
.source_skeleton
.nodes
.iter_mut()
.find(|node| node.source_node_index == 101)
.unwrap()
.parent_source_node_index = Some(100);
validate_scale_input(&changed).unwrap();
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: changed }, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "skeleton_topology_mismatch"
}
);
}
#[test]
fn complete_projection_bone_identity_is_compared_between_same_parent_siblings() {
let doc = rig_document(
&[
rig(None, 0, Vec3::ZERO),
rig(Some(0), 1, Vec3::ZERO),
rig(Some(0), 2, Vec3::ZERO),
],
&[1],
0,
Mat4::IDENTITY,
);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut changed = candidate.into_document();
for node in &mut changed.assets.source_skeleton.nodes {
node.bone = match node.source_node_index {
1 => Some(2),
2 => Some(1),
_ => node.bone,
};
}
validate_scale_input(&changed).unwrap();
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: changed }, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "skeleton_topology_mismatch"
}
);
}
#[test]
fn complete_projection_rows_are_keyed_by_source_identity_not_array_order() {
let (doc, _) = compensated_document_with_unskinned_prop();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut reordered = candidate.into_document();
reordered.assets.source_skeleton.nodes.reverse();
validate_scale_input(&reordered).unwrap();
prove_scale(
&doc,
&ScaleCandidate {
document: reordered,
},
&plan,
)
.unwrap();
}
#[test]
fn unavailable_projection_rows_are_not_candidate_identity_evidence() {
let (mut doc, _) = compensated_document_with_unskinned_prop();
doc.assets.source_skeleton.coverage = SourceSkeletonCoverage::Unavailable;
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut without_rows = candidate.into_document();
without_rows.assets.source_skeleton.nodes.clear();
prove_scale(
&doc,
&ScaleCandidate {
document: without_rows,
},
&plan,
)
.unwrap();
}
#[test]
fn unavailable_source_coverage_cannot_be_upgraded_by_the_candidate() {
let (mut doc, _) = compensated_document_with_unskinned_prop();
doc.assets.source_skeleton.coverage = SourceSkeletonCoverage::Unavailable;
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let mut upgraded = candidate.into_document();
upgraded.assets.source_skeleton.coverage = SourceSkeletonCoverage::Complete;
validate_scale_input(&upgraded).unwrap();
assert_eq!(
prove_scale(&doc, &ScaleCandidate { document: upgraded }, &plan).unwrap_err(),
ScaleError::CandidateStructureMismatch {
reason: "skeleton_topology_mismatch"
}
);
}
#[test]
fn a_mesh_instance_node_outside_the_skeleton_is_refused_on_either_document() {
let mut doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
let twin = MeshInstance {
source_node_index: 0,
node: 0,
..doc.assets.instances[0].clone()
};
doc.assets.instances.push(twin);
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let expected = ScaleError::InvalidDocumentShape(DocumentShapeError::MeshInstanceShape {
instance_index: 1,
violation: MeshInstanceShapeViolation::NodeIndexOutOfRange,
});
let mut broken_source = doc.clone();
broken_source.assets.instances[1].node = doc.skeleton.bones.len();
assert_eq!(
plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor: 0.01 },
document: &broken_source,
capability: &capability,
})
.unwrap_err(),
expected
);
assert_eq!(
prove_scale(&broken_source, &candidate, &plan).unwrap_err(),
expected
);
let mut broken_candidate = candidate.document().clone();
broken_candidate.assets.instances[1].node = doc.skeleton.bones.len();
assert_eq!(
prove_scale(
&doc,
&ScaleCandidate {
document: broken_candidate
},
&plan
)
.unwrap_err(),
expected
);
}
fn comparison_counts(proof: &ScaleProof) -> [(&'static str, usize); 12] {
[
("rest_translation", proof.rest_translation.comparisons()),
("rest_rotation", proof.rest_rotation.comparisons()),
("unit_scale", proof.unit_scale.comparisons()),
(
"transform_only_affine",
proof.transform_only_affine.comparisons(),
),
("track_value", proof.track_value.comparisons()),
("mesh_position", proof.mesh_position.comparisons()),
("key_translation", proof.key_translation.comparisons()),
("cubic_interior", proof.cubic_interior.comparisons()),
("trajectory", proof.trajectory.comparisons()),
("skin_matrix", proof.skin_matrix.comparisons()),
("bounds", proof.bounds.comparisons()),
(
"unaffected_inverse_bind",
proof.unaffected_inverse_bind.comparisons(),
),
]
}
const NARROWING_AT_ONE: f64 = 2.2351741811588166e-10;
fn sampled_payload_document() -> Document {
let mut doc = payload_document();
doc.clips[0].tracks.push(Track {
bone: 1,
property: Property::Translation,
interpolation: Interpolation::CubicSpline,
times: vec![0.0, 1.0],
values: TrackValues::Vec3s(vec![
Vec3::ZERO, Vec3::new(0.0, 1.0, 0.0), Vec3::ZERO, Vec3::ZERO, Vec3::new(0.0, 1.0, 0.0), Vec3::ZERO, ]),
});
doc
}
#[test]
fn a_whole_document_proof_counts_and_measures_every_comparison_it_makes() {
let doc = sampled_payload_document();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 3);
assert_eq!(
comparison_counts(&proof),
[
("rest_translation", 2),
("rest_rotation", 2),
("unit_scale", 0),
("transform_only_affine", 0),
("track_value", 8),
("mesh_position", 3),
("key_translation", 2),
("cubic_interior", 1),
("trajectory", 6),
("skin_matrix", 4),
("bounds", 24),
("unaffected_inverse_bind", 0),
]
);
assert_eq!(proof.rest_translation.max(), NARROWING_AT_ONE);
assert_eq!(proof.trajectory.max(), NARROWING_AT_ONE);
assert_eq!(proof.track_value.max(), NARROWING_AT_ONE);
assert_eq!(proof.key_translation.max(), NARROWING_AT_ONE);
assert_eq!(proof.cubic_interior.max(), NARROWING_AT_ONE);
assert_eq!(proof.mesh_position.max(), 3.871435245533232e-10);
assert_eq!(proof.bounds.max(), 2.0 * NARROWING_AT_ONE);
assert_eq!(proof.skin_matrix.max(), 0.0);
assert_eq!(proof.rest_rotation.max(), 0.0);
}
#[test]
fn an_unanimated_skinned_document_still_compares_its_skin_at_rest() {
let doc = compensated_document();
assert!(doc.clips.is_empty());
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
assert_eq!(plan.transform_only_attachments(), &[2]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 0);
assert!(!proof.track_value.evaluated());
assert!(proof.mesh_position.evaluated());
assert_eq!(
comparison_counts(&proof),
[
("rest_translation", 3),
("rest_rotation", 3),
("unit_scale", 3),
("transform_only_affine", 1),
("track_value", 0),
("mesh_position", 1),
("key_translation", 0),
("cubic_interior", 0),
("trajectory", 0),
("skin_matrix", 1),
("bounds", 6),
("unaffected_inverse_bind", 0),
]
);
}
#[test]
fn a_rest_bind_proof_counts_the_comparisons_its_own_obligations_walk() {
let doc = multi_joint_document();
let capability = complete_capability();
let plan = multi_joint_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
assert!(plan.transform_only_attachments().is_empty());
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 3);
assert_eq!(
comparison_counts(&proof),
[
("rest_translation", 3),
("rest_rotation", 3),
("unit_scale", 3),
("transform_only_affine", 0),
("track_value", 8),
("mesh_position", 4),
("key_translation", 4),
("cubic_interior", 2),
("trajectory", 9),
("skin_matrix", 8),
("bounds", 24),
("unaffected_inverse_bind", 0),
]
);
}
#[test]
fn a_clipless_plan_compares_no_track_value_while_still_comparing_its_mesh() {
let doc = rig_document(&unit_rig(), &[1], 0, Mat4::IDENTITY);
assert!(doc.clips.is_empty());
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 0);
assert_eq!(
comparison_counts(&proof),
[
("rest_translation", 2),
("rest_rotation", 2),
("unit_scale", 0),
("transform_only_affine", 0),
("track_value", 0),
("mesh_position", 1),
("key_translation", 0),
("cubic_interior", 0),
("trajectory", 0),
("skin_matrix", 1),
("bounds", 6),
("unaffected_inverse_bind", 0),
]
);
}
#[test]
fn a_meshless_plan_compares_no_mesh_position_while_still_comparing_its_tracks() {
let mut doc = payload_document();
doc.assets.meshes.clear();
doc.assets.instances.clear();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 2);
assert_eq!(
comparison_counts(&proof),
[
("rest_translation", 2),
("rest_rotation", 2),
("unit_scale", 0),
("transform_only_affine", 0),
("track_value", 2),
("mesh_position", 0),
("key_translation", 0),
("cubic_interior", 0),
("trajectory", 4),
("skin_matrix", 0),
("bounds", 0),
("unaffected_inverse_bind", 0),
]
);
}
#[test]
fn a_skin_outside_the_closure_is_the_only_source_of_an_unaffected_bind_comparison() {
let doc = compensated_document_with_unrelated_skin(Some(Mat4::from_scale(Vec3::splat(2.0))));
let capability = complete_capability();
let plan = compensated_rest_bind_plan(&doc, &capability);
assert_eq!(plan.affected_nodes(), &[0, 1, 2]);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.unaffected_inverse_bind.comparisons(), 1);
assert_eq!(proof.unaffected_inverse_bind.max(), 0.0);
assert_eq!(proof.rest_translation.comparisons(), 3);
assert_eq!(proof.skin_matrix.comparisons(), 1);
}
#[test]
fn an_unskinned_document_compares_neither_skin_matrices_nor_bounds() {
let doc = unskinned_document();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
assert!(
!plan
.obligations()
.contains(&ScaleProofObligation::SkinAndBounds)
);
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(
comparison_counts(&proof),
[
("rest_translation", 1),
("rest_rotation", 1),
("unit_scale", 0),
("transform_only_affine", 0),
("track_value", 0),
("mesh_position", 1),
("key_translation", 0),
("cubic_interior", 0),
("trajectory", 0),
("skin_matrix", 0),
("bounds", 0),
("unaffected_inverse_bind", 0),
]
);
}
#[test]
fn a_boneless_whole_document_plan_compares_nothing_at_all() {
let doc = Document::default();
let capability = complete_capability();
let plan = whole_document_plan(&doc, &capability);
let obligations = plan.obligations().to_vec();
assert!(plan.rest_obligation().is_none());
assert!(!obligations.contains(&ScaleProofObligation::KeyTranslations));
let candidate = build_scale_candidate(&doc, &plan).unwrap();
let proof = prove_scale(&doc, &candidate, &plan).unwrap();
assert_eq!(proof.sample_time_count, 0);
assert_eq!(
comparison_counts(&proof).map(|(_, count)| count),
[0usize; 12]
);
}
struct SweepRng(u64);
impl SweepRng {
fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn unit(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
}
fn decades(&mut self, lo: f64, hi: f64) -> f32 {
10f64.powf(lo + self.unit() * (hi - lo)) as f32
}
fn direction(&mut self) -> Vec3 {
let z = 2.0 * self.unit() - 1.0;
let angle = core::f64::consts::TAU * self.unit();
let radius = (1.0 - z * z).max(0.0).sqrt();
Vec3::new(
(radius * angle.cos()) as f32,
(radius * angle.sin()) as f32,
z as f32,
)
.normalize()
}
fn rotation(&mut self) -> Quat {
Quat::from_axis_angle(
self.direction(),
(core::f64::consts::TAU * self.unit()) as f32,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SweepComposition {
Analytic,
Scaled(i32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SweepBlend {
Cancelling,
Independent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SweepWeights {
Balanced,
Mismatched,
}
#[derive(Debug, Clone, Copy)]
struct SweepCell {
conversion: Option<f64>,
composition: SweepComposition,
blend: SweepBlend,
weights: SweepWeights,
}
impl SweepCell {
fn seed(self) -> u64 {
let mut state = 0x5EED_0000_0000_0000u64;
for word in [
self.conversion.unwrap_or(0.0).to_bits(),
match self.composition {
SweepComposition::Analytic => 0,
SweepComposition::Scaled(exponent) => 1 + (i64::from(exponent) + 1024) as u64,
},
match self.blend {
SweepBlend::Cancelling => 0,
SweepBlend::Independent => 1,
},
match self.weights {
SweepWeights::Balanced => 0,
SweepWeights::Mismatched => 1,
},
] {
state = SweepRng(state ^ word).next_u64();
}
state
}
}
#[derive(Debug, Clone, Copy, Default)]
struct SweepWorst {
bounds: f64,
skin_matrix: f64,
rest_translation: f64,
}
#[derive(Debug, Clone, Copy, Default)]
struct SweepSample {
worst: SweepWorst,
mismatched_vertices: usize,
larger_slot_zero: usize,
larger_slot_one: usize,
}
impl SweepWorst {
fn fold(&mut self, other: Self) {
self.bounds = self.bounds.max(other.bounds);
self.skin_matrix = self.skin_matrix.max(other.skin_matrix);
self.rest_translation = self.rest_translation.max(other.rest_translation);
}
}
#[derive(Debug, Clone, Copy, Default)]
struct DeepChainWorst {
rest_translation: f64,
trajectory: f64,
skin_matrix: f64,
bounds: f64,
rest_comparisons: usize,
trajectory_comparisons: usize,
skin_comparisons: usize,
bounds_comparisons: usize,
}
impl DeepChainWorst {
fn fold(&mut self, other: Self) {
self.rest_translation = self.rest_translation.max(other.rest_translation);
self.trajectory = self.trajectory.max(other.trajectory);
self.skin_matrix = self.skin_matrix.max(other.skin_matrix);
self.bounds = self.bounds.max(other.bounds);
self.rest_comparisons += other.rest_comparisons;
self.trajectory_comparisons += other.trajectory_comparisons;
self.skin_comparisons += other.skin_comparisons;
self.bounds_comparisons += other.bounds_comparisons;
}
fn from_proof(proof: ScaleProof) -> Self {
Self {
rest_translation: proof.rest_translation_f32_rounding_demand,
trajectory: proof.trajectory_f32_rounding_demand,
skin_matrix: proof.skin_matrix_f32_rounding_demand,
bounds: proof.bounds_f32_rounding_demand,
rest_comparisons: proof.rest_translation.comparisons(),
trajectory_comparisons: proof.trajectory.comparisons(),
skin_comparisons: proof.skin_matrix.comparisons(),
bounds_comparisons: proof.bounds.comparisons(),
}
}
}
#[test]
fn calibration_demand_ownership_is_not_swappable() {
let (doc, plan, candidate) = animated_deep_chain_conversion(8);
let mut proof = prove_scale(&doc, &candidate, &plan).expect("the calibration rig proves");
proof.rest_translation_f32_rounding_demand = 0.0;
proof.trajectory_f32_rounding_demand = 0.0;
proof.skin_matrix_f32_rounding_demand = 0.0;
proof.bounds_f32_rounding_demand = 0.0;
proof.unaffected_inverse_bind_f32_rounding_demand = 0.0;
let epsilon = f64::from(f32::EPSILON);
for (kind, demand) in [
(ProofResidualKind::RestTranslation, 1.0),
(ProofResidualKind::Trajectory, 2.0),
(ProofResidualKind::SkinMatrix, 3.0),
(ProofResidualKind::Bounds, 4.0),
(ProofResidualKind::UnaffectedInverseBind, 5.0),
] {
proof.record_f32_rounding_demand(kind, demand * epsilon, 1.0);
}
assert_eq!(
(
proof.rest_translation_f32_rounding_demand,
proof.trajectory_f32_rounding_demand,
proof.skin_matrix_f32_rounding_demand,
proof.bounds_f32_rounding_demand,
proof.unaffected_inverse_bind_f32_rounding_demand,
),
(1.0, 2.0, 3.0, 4.0, 5.0),
"the central production diagnostic must keep every residual kind in its own field",
);
proof.record_f32_rounding_demand(ProofResidualKind::UnaffectedInverseBind, 1.0, 0.0);
assert!(
proof
.unaffected_inverse_bind_f32_rounding_demand
.is_infinite(),
"a nonzero residual with no rounding provenance must fail calibration closed",
);
let expected_counts = (
proof.rest_translation.comparisons(),
proof.trajectory.comparisons(),
proof.skin_matrix.comparisons(),
proof.bounds.comparisons(),
);
let measured = DeepChainWorst::from_proof(proof);
assert_eq!(
(
measured.rest_translation,
measured.trajectory,
measured.skin_matrix,
measured.bounds,
),
(1.0, 2.0, 3.0, 4.0),
"the deep calibration adapter must not swap or duplicate obligation demands",
);
assert_eq!(
(
measured.rest_comparisons,
measured.trajectory_comparisons,
measured.skin_comparisons,
measured.bounds_comparisons,
),
expected_counts,
"the deep calibration adapter must retain each obligation's production count",
);
}
fn deep_chain_case(
depth: usize,
rotation: Quat,
conversion: Option<f64>,
) -> Result<DeepChainWorst, ScaleError> {
let root_scale = if conversion.is_some() { 1.0 } else { 3190.0 };
let doc = chain_document(depth, rotation, root_scale, true);
let plan = match conversion {
Some(factor) => plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor },
document: &doc,
capability: &complete_capability(),
})?,
None => rest_bind_plan(&doc, f64::from(root_scale)),
};
let candidate = build_scale_candidate(&doc, &plan)?;
let proof = prove_scale(&doc, &candidate, &plan)?;
assert_eq!(
proof.sample_time_count, 2,
"every animated deep calibration case must retain two production sample times",
);
Ok(DeepChainWorst::from_proof(proof))
}
fn sweep_one(rng: &mut SweepRng, cell: SweepCell) -> Result<SweepSample, ScaleError> {
let root = if cell.conversion.is_some() {
1.0
} else {
3190.0
};
let rotations = [rng.rotation(), rng.rotation()];
let mut locals = [
rng.direction() * rng.decades(-3.0, 3.0),
rng.direction() * rng.decades(-3.0, 3.0),
];
if rng.unit() < 0.5 {
locals[1] = -(rotations[0].inverse() * locals[0]);
}
let reach = rng.decades(-3.0, 5.0);
let points = [
Vec3::new(reach, 0.0, 0.0),
rng.direction() * rng.decades(-3.0, 5.0),
];
let weights = [[0.5, 0.5, 0.0, 0.0]; 2];
let mut doc = match cell.composition {
SweepComposition::Analytic => {
rotating_rig_document(rotations, root, locals, &points, &weights)
}
SweepComposition::Scaled(exponent) => {
let first = Mat4::from_scale(Vec3::splat(10f32.powi(exponent)))
* Mat4::from_quat(rng.rotation());
let second = match cell.blend {
SweepBlend::Cancelling => first * HALF_TURN_Z,
SweepBlend::Independent => {
Mat4::from_scale(Vec3::splat(10f32.powi(exponent)))
* Mat4::from_quat(rng.rotation())
}
};
composed_slot_document(rotations, root, locals, [first, second], &points, &weights)
}
};
let mut sample = SweepSample::default();
if cell.weights == SweepWeights::Mismatched {
let slots = rig_skin_slots(&doc);
for (vertex, &point) in points.iter().enumerate() {
let bases = [
skin_influence_magnitude(&slots[0], point),
skin_influence_magnitude(&slots[1], point),
];
let Some((larger, smaller)) = (bases[0] > bases[1])
.then_some((0, 1))
.or_else(|| (bases[1] > bases[0]).then_some((1, 0)))
else {
continue;
};
let mut mismatched = [0.0; 4];
mismatched[larger] = rng.decades(-20.0, -2.0);
mismatched[smaller] = 1.0;
doc.assets.meshes[0].primitives[0].weights[vertex] = mismatched;
let installed = doc.assets.meshes[0].primitives[0].weights[vertex];
assert!(
installed[larger] > 0.0
&& installed[larger] <= 1e-2
&& installed[larger] < installed[smaller]
&& installed[smaller] == 1.0,
"the production document did not retain the requested mismatch: bases \
{bases:?}, weights {installed:?}"
);
sample.mismatched_vertices += 1;
sample.larger_slot_zero += usize::from(larger == 0);
sample.larger_slot_one += usize::from(larger == 1);
}
}
let plan = match cell.conversion {
None => rest_bind_plan(&doc, f64::from(root)),
Some(factor) => plan_scale(&ScaleRequest {
operation: ScaleOperation::WholeDocumentLinearUnits { factor },
document: &doc,
capability: &complete_capability(),
})
.expect("a whole-document conversion plans at any positive factor"),
};
let candidate = build_scale_candidate(&doc, &plan).expect("a planned rig builds");
let proof = prove_scale(&doc, &candidate, &plan)?;
sample.worst = SweepWorst {
bounds: proof.bounds_f32_rounding_demand,
skin_matrix: proof.skin_matrix_f32_rounding_demand,
rest_translation: proof.rest_translation_f32_rounding_demand,
};
Ok(sample)
}
#[test]
#[ignore = "calibration: 360,000 shallow proofs plus 80 deep cases. See docs/scale-calibration.md."]
fn calibrate_f32_rounding_ulps() {
const TRIALS: usize = 2_500;
let conversions = [
None,
Some(1e-4),
Some(0.01),
Some(0.1),
Some(1.5),
Some(7.3),
Some(100.0),
Some(3190.0),
Some(1e6),
];
let compositions = [
SweepComposition::Analytic,
SweepComposition::Scaled(-3),
SweepComposition::Scaled(0),
SweepComposition::Scaled(3),
];
let blends = [SweepBlend::Cancelling, SweepBlend::Independent];
let weight_profiles = [SweepWeights::Balanced, SweepWeights::Mismatched];
let mut overall = SweepWorst::default();
let mut refusals = Vec::new();
let mut cells = 0usize;
let mut mismatched_profile_candidates = 0usize;
let mut mismatched_vertices = 0usize;
let mut larger_slot_zero = 0usize;
let mut larger_slot_one = 0usize;
println!(
"{:>8} {:>10} {:>12} {:>10} {:>8} {:>8} {:>8} {:>8}",
"conv", "abs(W*B)", "blend", "weights", "refused", "bounds", "skin", "rest"
);
for &conversion in &conversions {
for &composition in &compositions {
for &blend in &blends {
for &weights in &weight_profiles {
let cell = SweepCell {
conversion,
composition,
blend,
weights,
};
let mut rng = SweepRng(cell.seed());
let mut worst = SweepWorst::default();
let mut refused = 0usize;
for _ in 0..TRIALS {
match sweep_one(&mut rng, cell) {
Ok(measured) => {
worst.fold(measured.worst);
mismatched_vertices += measured.mismatched_vertices;
larger_slot_zero += measured.larger_slot_zero;
larger_slot_one += measured.larger_slot_one;
}
Err(error) => {
refused += 1;
if refused == 1 {
refusals.push(format!("{cell:?}: {error:?}"));
}
}
}
}
println!(
"{:>8} {:>10} {:>12} {:>10} {:>8} {:>8.3} {:>8.3} {:>8.3}",
conversion.map_or("rest/bind".into(), |q| format!("{q:e}")),
match composition {
SweepComposition::Analytic => "1 (exact)".into(),
SweepComposition::Scaled(e) => format!("1e{e}"),
},
format!("{blend:?}"),
format!("{weights:?}"),
format!("{refused}/{TRIALS}"),
worst.bounds,
worst.skin_matrix,
worst.rest_translation,
);
overall.fold(worst);
cells += 1;
mismatched_profile_candidates +=
usize::from(weights == SweepWeights::Mismatched) * TRIALS;
}
}
}
}
let deep_depths = [8, 16, 32, 64, 128, 192, 256, 512];
let mut deep_by_depth = [DeepChainWorst::default(); 8];
let mut deep = DeepChainWorst::default();
let mut deep_cases = 0usize;
for (depth_index, &depth) in deep_depths.iter().enumerate() {
for &conversion in &conversions {
let rotation = if conversion.is_some() {
DEEP_CHAIN_ROTATION
} else {
Quat::from_xyzw(0.0, 0.0, 1.0, 0.0)
};
match deep_chain_case(depth, rotation, conversion) {
Ok(measured) => {
deep_by_depth[depth_index].fold(measured);
deep.fold(measured);
}
Err(error) => refusals.push(format!(
"deep chain depth {depth}, conversion {conversion:?}: {error:?}"
)),
}
deep_cases += 1;
}
}
for &conversion in conversions.iter().flatten() {
match deep_chain_case(192, RING_CHAIN_ROTATION, Some(conversion)) {
Ok(measured) => {
deep_by_depth[5].fold(measured);
deep.fold(measured);
}
Err(error) => refusals.push(format!(
"ring chain depth 192, conversion {conversion}: {error:?}"
)),
}
deep_cases += 1;
}
let total = cells * TRIALS;
println!(
"\n{total} correct candidates over {cells} cells; {mismatched_vertices} realized \
mismatched vertices ({larger_slot_zero} with slot 0 larger, {larger_slot_one} with \
slot 1 larger); worst ulps of the comparison base: \
bounds {:.3}, skin matrix {:.3}, rest translation {:.3}; \
f32_rounding_ulps = {}",
overall.bounds,
overall.skin_matrix,
overall.rest_translation,
ScaleTolerancePolicy::APPENDIX_D_V6.f32_rounding_ulps,
);
println!(
"deep-chain calibration: {deep_cases} cases through 512 links; worst per-comparison \
ulps: rest {:.3}, trajectory {:.3}, skin matrix {:.3}, bounds {:.3}; comparisons \
{}/{}/{}/{}",
deep.rest_translation,
deep.trajectory,
deep.skin_matrix,
deep.bounds,
deep.rest_comparisons,
deep.trajectory_comparisons,
deep.skin_comparisons,
deep.bounds_comparisons,
);
println!("depth rest trajectory skin bounds comparisons r/t/s/b");
for (depth, measured) in deep_depths.into_iter().zip(deep_by_depth) {
println!(
"{depth:>5} {:>8.3} {:>14.3} {:>8.3} {:>8.3} {}/{}/{}/{}",
measured.rest_translation,
measured.trajectory,
measured.skin_matrix,
measured.bounds,
measured.rest_comparisons,
measured.trajectory_comparisons,
measured.skin_comparisons,
measured.bounds_comparisons,
);
}
assert!(
refusals.is_empty(),
"the sweep refused correct candidates, one per cell shown: {refusals:#?}",
);
assert_eq!(
cells, 144,
"the sweep no longer runs the 144 cells docs/scale-calibration.md names. If a \
dimension was deliberately added or removed, this literal and the prose that quotes \
it move together.",
);
assert_eq!(
cells * TRIALS,
360_000,
"the sweep no longer draws the 360_000 candidates docs/scale-calibration.md names, so \
the figures below are not the ones that note quotes.",
);
assert_eq!(
mismatched_profile_candidates, 180_000,
"half of the sweep must use the explicit mismatched-weight profile",
);
assert_eq!(
deep_cases, 80,
"the deep calibration must retain eight declared depths across nine operations plus \
the eight whole-document conversions of the 192-link ring",
);
assert!(
deep.rest_comparisons > 0
&& deep.trajectory_comparisons > 0
&& deep.skin_comparisons > 0
&& deep.bounds_comparisons > 0,
"every affected obligation must own measured deep-chain comparisons: {deep:?}",
);
assert_eq!(
(
deep.rest_comparisons,
deep.trajectory_comparisons,
deep.skin_comparisons,
deep.bounds_comparisons,
),
(12_488, 24_976, 240, 1_440),
"the deep phase's production comparison counts no longer match \
docs/scale-calibration.md",
);
let deep_counts = deep_by_depth.map(|measured| {
(
measured.rest_comparisons,
measured.trajectory_comparisons,
measured.skin_comparisons,
measured.bounds_comparisons,
)
});
assert_eq!(
deep_counts,
[
(81, 162, 27, 162),
(153, 306, 27, 162),
(297, 594, 27, 162),
(585, 1_170, 27, 162),
(1_161, 2_322, 27, 162),
(3_281, 6_562, 51, 306),
(2_313, 4_626, 27, 162),
(4_617, 9_234, 27, 162),
],
"a declared depth no longer owns the comparison population \
docs/scale-calibration.md records",
);
let deep_demands_milli = deep_by_depth.map(|measured| {
(
(measured.rest_translation * 1_000.0).round() as u16,
(measured.trajectory * 1_000.0).round() as u16,
(measured.skin_matrix * 1_000.0).round() as u16,
(measured.bounds * 1_000.0).round() as u16,
)
});
assert_eq!(
deep_demands_milli,
[
(578, 578, 143, 149),
(578, 578, 67, 73),
(578, 578, 76, 78),
(578, 578, 63, 63),
(578, 578, 37, 39),
(715, 715, 34, 33),
(578, 578, 34, 34),
(578, 578, 19, 19),
],
"the literal deep-chain demand table no longer matches the values recorded to three decimals",
);
assert!(
deep_by_depth.iter().all(|measured| {
measured.rest_comparisons > 0
&& measured.trajectory_comparisons > 0
&& measured.skin_comparisons > 0
&& measured.bounds_comparisons > 0
}),
"every declared depth must record every affected obligation: {deep_by_depth:#?}",
);
assert!(
(273_000..=276_000).contains(&mismatched_vertices)
&& (73_000..=76_000).contains(&larger_slot_zero)
&& (199_000..=202_000).contains(&larger_slot_one),
"the mismatch profile did not realize both production-base orientations: \
{mismatched_vertices} vertices total, slot 0 larger {larger_slot_zero}, slot 1 \
larger {larger_slot_one}; expected the platform-tolerant brackets around the \
recorded 274670/74085/200585 population",
);
assert!(
(2.70..2.85).contains(&overall.bounds)
&& (2.10..2.30).contains(&overall.skin_matrix)
&& (2.50..2.70).contains(&overall.rest_translation),
"the sweep measured almost no demand at all: bounds {:.3}, skin matrix {:.3}, rest \
translation {:.3}. A base has been loosened, or the population no longer reaches \
the cancellations it is built to reach — either way these figures are not a \
calibration of anything.",
overall.bounds,
overall.skin_matrix,
overall.rest_translation,
);
let allowed = f64::from(ScaleTolerancePolicy::APPENDIX_D_V6.f32_rounding_ulps);
assert!(
overall.bounds < allowed
&& overall.skin_matrix < allowed
&& overall.rest_translation < allowed,
"a correct candidate asked more of f32_rounding_ulps than the count allows: \
bounds {:.3}, skin matrix {:.3}, rest translation {:.3} against {allowed}. \
That is evidence about the comparison base before it is evidence about the count \
— read docs/scale-calibration.md and the normative DESIGN.md Appendix D section D.1 \
before raising anything.",
overall.bounds,
overall.skin_matrix,
overall.rest_translation,
);
assert!(
deep.rest_translation < allowed
&& deep.trajectory < allowed
&& deep.skin_matrix < allowed
&& deep.bounds < allowed,
"deep-chain demand escaped the calibrated count: RestTranslation {:.3}, Trajectory \
{:.3}, SkinMatrix {:.3}, Bounds {:.3}; policy allows {allowed}",
deep.rest_translation,
deep.trajectory,
deep.skin_matrix,
deep.bounds,
);
assert!(
deep_by_depth.iter().all(|measured| {
measured.rest_translation < allowed
&& measured.trajectory < allowed
&& measured.skin_matrix < allowed
&& measured.bounds < allowed
}),
"a declared depth escaped the calibrated count: {deep_by_depth:#?}",
);
assert!(
deep.rest_translation > 0.7
&& deep.trajectory > 0.7
&& deep.skin_matrix > 0.13
&& deep.bounds > 0.14,
"the deep-chain calibration went silent or its bases were over-inflated: \
RestTranslation {:.3}, Trajectory {:.3}, SkinMatrix {:.3}, Bounds {:.3}",
deep.rest_translation,
deep.trajectory,
deep.skin_matrix,
deep.bounds,
);
}