fix(io): purge near-collinear arcs that hang load

A 35-unit arc sweeping 6.5e-7 rad has arc length 2.3e-5 — past the
arc-length floor — yet start/mid/end bow off the chord by only ~2e-12.
truck's 3-point circle_arc fit then returns a near-infinite radius and
parameter_division subdivides without bound, hanging the load. Gate on
the sagitta (r·(1−cos(sweep/2))), the true measure of the fit's
conditioning, so long thin arcs survive and sub-precision ones drop.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-11 17:10:00 +03:00
1 parent 8f39e77a0d
commit cf817ff9eb
1 file changed
+30
+30
View File
@@ -465,6 +465,18 @@ pub(crate) fn is_entity_corrupt(e: &EntityType) -> bool {
// radius small-sweep arc (still a visible curve) survives while
// sub-precision arcs are dropped.
|| a.radius.abs() * (a.end_angle - a.start_angle).abs() < 1.0e-6
// Near-collinear sample points: even when the arc *length* clears
// the floor above, a small sweep over a modest radius leaves
// start/mid/end almost on one line (a 35-unit, 6.5e-7-rad arc has
// arc length 2.3e-5 — past the gate — yet bows off its chord by
// only ~2e-12). truck's 3-point `circle_arc` fit then returns a
// near-infinite radius and `parameter_division` subdivides without
// bound. Gate on the sagitta (chord height = r·(1−cos(sweep/2))),
// the true measure of how far the arc departs a straight line and
// of the fit's conditioning.
|| a.radius.abs()
* (1.0 - ((a.end_angle - a.start_angle).abs() * 0.5).cos())
< 1.0e-6
|| !finite_unit_normal(&a.normal)
}
E::Ellipse(e) => {
@@ -648,6 +660,24 @@ mod corrupt_guard_tests {
assert!(is_entity_corrupt(&EntityType::Arc(a)));
}
// A 35-unit-radius arc sweeping 6.5e-7 rad has arc length 2.3e-5 — past the
// arc-length floor — yet its start/mid/end bow off the chord by only ~2e-12,
// so truck's 3-point circle fit blows up and parameter_division hangs. The
// sagitta floor must reject it where the arc-length floor alone does not.
#[test]
fn rejects_near_collinear_arc() {
let mut a = Arc::new();
a.center = Vector3::new(551435.3071786845, 4051623.7156955916, 0.0);
a.radius = 35.0;
a.start_angle = 5.823361856481176;
a.end_angle = 5.823362506017916;
a.normal = Vector3::new(0.0, 0.0, 1.0);
// Sanity: arc length clears the old gate, proving the sagitta gate is
// what catches this one.
assert!(a.radius * (a.end_angle - a.start_angle).abs() > 1.0e-6);
assert!(is_entity_corrupt(&EntityType::Arc(a)));
}
// A large-radius small-sweep arc is still a visible curve and must survive:
// radius 1e6 × sweep 1e-4 ≈ 100 units of arc.
#[test]