From 915ee8a178fa3403e9e631fb4ff407bef608d876 Mon Sep 17 00:00:00 2001 From: Tyler Chiriboga Date: Wed, 21 Oct 2026 18:56:51 +0000 Subject: [PATCH] Fuzzer: a chamfer may add material, as a fillet may (#3868) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fuzzer chamfers randomly picked edges and expected every chamfer to shrink the body, so a chamfer of a concave edge, which fills the corner, was reported as a wrong volume: 53 of the 6,000 designs of a run (seed 7100465: a box, a shell, a chamfer of an inside edge). Fillets already expect any volume change for the same reason; chamfers now do too. The regression test checks both directions on a shelled 20 mm box (an inside-edge chamfer of 1 mm adds 8 mm³, an outside one takes 10 mm³ away) and that seed 7100465 is no longer a volume finding. Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- crates/engine/src/fuzz.rs | 4 +++- crates/engine/src/tests/robustness.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/fuzz.rs b/crates/engine/src/fuzz.rs index c1f51c5f33..2219c5ed6a 100644 --- a/crates/engine/src/fuzz.rs +++ b/crates/engine/src/fuzz.rs @@ -246,7 +246,9 @@ fn next_step(r: &mut Rng, s: &Session, last_feature: &Option) -> Option< let edges = body.body.edges(tol).ok()?; let n = 1 + r.below(3); let picks: Vec<[f64; 3]> = (0..n).filter_map(|_| edges.get(r.below(edges.len()))).map(|e| [e.mid.x, e.mid.y, e.mid.z]).collect(); - step("chamfer", Expect::Shrink, vec![("solid.chamfer", json!({"edges": picks, "distance": r.pick(&[0.5, 1.0, 2.0])?}))]) + // Any, as for fillets: the edges are picked at random, and a chamfer on a concave edge + // fills the corner, so it adds material. + step("chamfer", Expect::Any, vec![("solid.chamfer", json!({"edges": picks, "distance": r.pick(&[0.5, 1.0, 2.0])?}))]) } 8 => { let faces: Vec<_> = body.body.faces(tol).ok()?.into_iter().filter(|f| f.plane_normal.is_some()).collect(); diff --git a/crates/engine/src/tests/robustness.rs b/crates/engine/src/tests/robustness.rs index 217e1cc4bd..7e71d95cb4 100644 --- a/crates/engine/src/tests/robustness.rs +++ b/crates/engine/src/tests/robustness.rs @@ -575,3 +575,30 @@ fn seed_33_display_plan_and_measure_agree() { assert!((planned - measured).abs() < 2e-5 * measured, "{}: planned {planned}, measured {measured}", b.name); } } + +/// A chamfer on a concave edge fills the corner, so it adds material, as a fillet there does. The +/// fuzzer expected every chamfer to shrink the body and reported these as a wrong volume (about +/// 1 % of the designs of a 6,000-seed run; seed 7100465: a box, a shell, a chamfer of an inside +/// edge). On a shelled 20 mm box, a 1 mm chamfer of an inside bottom edge adds its 16 mm long +/// half-square prism, and one of an outside bottom edge takes 20 mm of it away. +#[test] +fn chamfer_of_a_concave_edge_adds_material() { + let shelled = || { + vec![ + json!({"command": "solid.box", "params": {"corner": [0, 0, 0], "length": 20, "width": 20, "height": 20}}), + json!({"command": "solid.shell", "params": {"faces": [[10, 10, 20]], "thickness": 2}}), + ] + }; + let base = total_volume(&script(Value::Array(shelled()))); + let chamfered = |edge: [f64; 3]| { + let mut steps = shelled(); + steps.push(json!({"command": "solid.chamfer", "params": {"edges": [edge], "distance": 1}})); + total_volume(&script(Value::Array(steps))) + }; + let inside = chamfered([10.0, 2.0, 2.0]); + let outside = chamfered([10.0, 0.0, 0.0]); + assert!((inside - (base + 16.0 * 0.5)).abs() < 1e-6, "inside edge: {base} -> {inside}"); + assert!((outside - (base - 20.0 * 0.5)).abs() < 1e-6, "outside edge: {base} -> {outside}"); + let r = fuzz::run(7100465, 8); + assert!(r.outcome.kind != "volume", "seed 7100465: {:?}\n{}", r.outcome, r.script()); +}