Problem
Three call sites each carried their own frame validity rule, under three different tolerance policies, and only two of the three checked handedness.
| Site |
Tolerance used |
Handedness checked |
sampled/field/src/config.rs |
tolerance.angular() |
yes |
execution/dispatch/src/section.rs |
angular, clamped to 1e-6 |
yes |
parametric/evaluate/src/surface.rs |
hardcoded 1e-9 |
no |
Two consequences.
A left-handed surface frame was accepted. to_local in evaluate/surface.rs checked unit length and pairwise perpendicularity but never the triple product. A mirrored basis satisfies both, so invert accepted it and returned reflected parameters. The reflection is self-consistent: those parameters round-trip through the same bad frame and look correct.
Surface evaluation ignored the caller's tolerance. invert already takes a Tolerance, but frame validation used a hardcoded 1e-9 regardless of the model unit.
Reproduction
Both added to crates/algorithms/parametric/evaluate/tests/invert.rs:
a_left_handed_surface_frame_is_refused ... FAILED
A Frame3 with z: -Vec3::Z and identity x/y is orthonormal, mirrored, and was accepted.
Resolution
SpaceFrame in axiolid-core: origin plus a right-handed orthonormal triad, private fields, to_local/to_world. It is the 3-axis counterpart to PlaneFrame and shares its error vocabulary, now renamed FrameError with a new NotRightHanded variant.
- Unit length, perpendicularity and handedness are dimensionless, so all three are judged against the ANGULAR tolerance.
- Handedness compares the triple product against
+1. On an orthonormal triad that value is exactly +1 right-handed and -1 mirrored, so the two are separated by a margin of 2 rather than by a tolerance.
- Private fields make an invalid frame unrepresentable, not merely rejected.
All three call sites now delegate. dispatch/section.rs keeps its deliberate 1e-6 tolerance ceiling by capping before it calls the shared validator, so tighter caller tolerances still apply and looser ones still do not. evaluate/surface.rs threads its existing caller tolerance through instead of ignoring it.
Verification
- workspace
1113 passed / 0 failed, GATE PASSED
- mutation: deleting the handedness check fails
a_left_handed_basis_is_refused in core AND a_left_handed_surface_frame_is_refused in the consumer, so the guard is proven at both layers
- zero hand-rolled orthonormality checks remain in production code
Landed in 6181dd2.
Problem
Three call sites each carried their own frame validity rule, under three different tolerance policies, and only two of the three checked handedness.
sampled/field/src/config.rstolerance.angular()execution/dispatch/src/section.rs1e-6parametric/evaluate/src/surface.rs1e-9Two consequences.
A left-handed surface frame was accepted.
to_localinevaluate/surface.rschecked unit length and pairwise perpendicularity but never the triple product. A mirrored basis satisfies both, soinvertaccepted it and returned reflected parameters. The reflection is self-consistent: those parameters round-trip through the same bad frame and look correct.Surface evaluation ignored the caller's tolerance.
invertalready takes aTolerance, but frame validation used a hardcoded1e-9regardless of the model unit.Reproduction
Both added to
crates/algorithms/parametric/evaluate/tests/invert.rs:A
Frame3withz: -Vec3::Zand identityx/yis orthonormal, mirrored, and was accepted.Resolution
SpaceFrameinaxiolid-core: origin plus a right-handed orthonormal triad, private fields,to_local/to_world. It is the 3-axis counterpart toPlaneFrameand shares its error vocabulary, now renamedFrameErrorwith a newNotRightHandedvariant.+1. On an orthonormal triad that value is exactly+1right-handed and-1mirrored, so the two are separated by a margin of 2 rather than by a tolerance.All three call sites now delegate.
dispatch/section.rskeeps its deliberate1e-6tolerance ceiling by capping before it calls the shared validator, so tighter caller tolerances still apply and looser ones still do not.evaluate/surface.rsthreads its existing caller tolerance through instead of ignoring it.Verification
1113 passed / 0 failed,GATE PASSEDa_left_handed_basis_is_refusedin core ANDa_left_handed_surface_frame_is_refusedin the consumer, so the guard is proven at both layersLanded in
6181dd2.