M6.10: Boolean operations hold their area identities on almost-horizontal edges

The sweep compares a curve with another segment over a y-span shorter than
its tolerance by bounding boxes only, so an edge rising by less than that
tolerance (e.g. a rectangle edge cut through a circle's cap, whose computed
intersection end sits ~1e-12 off the horizontal) was mis-ordered and the
result lost a corner: xor/difference/area disagreed by up to 10%. Every
sweep input now snaps on-curve y values within the tolerance to one level
(handles follow and are levelled with their anchor), so such edges become
exact horizontals, which the sweep handles exactly.

- tests/regressions.rs: the six minimal failures found by the properties,
  the root cause in isolation, and the self-crossing "star" polygon.
- The star-polygon strategy rejected no angular step of half a turn or
  more, so it sometimes drew self-crossing polygons (the shoelace failure).
- Property suites run from a fixed seed (PROPTEST_CASES / PROPTEST_RNG_SEED
  still override), so CI cannot fail at random.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bflatasticandClaude Opus 5.5 committed 2026-10-05 03:36:30 +07:00
1 parent bc59c53b5a
commit be7006ccf5
8 files changed
+301 -10

No files matched your search

+7 -2
View File
@@ -19,9 +19,14 @@ pub fn arb_rect() -> impl Strategy<Value = Rect> {
}
/// A simple (non self-intersecting) star-shaped polygon: points at increasing angles around a
/// centre with random radii.
/// centre with random radii. Every angular step stays below half a turn: a wider one puts the
/// centre outside and can make the polygon cross itself.
pub fn arb_star_polygon() -> impl Strategy<Value = Vec<Point>> {
(arb_point(40.0, 160.0), prop::collection::vec((0.2f64..1.0, 5.0f64..40.0), 3..12)).prop_map(|(c, v)| {
let steps = prop::collection::vec((0.2f64..1.0, 5.0f64..40.0), 3..12).prop_filter("an angular step of half a turn or more", |v| {
let total: f64 = v.iter().map(|(a, _)| a).sum();
v.iter().all(|(a, _)| 2.0 * a < total)
});
(arb_point(40.0, 160.0), steps).prop_map(|(c, v)| {
let total: f64 = v.iter().map(|(a, _)| a).sum();
let mut ang = 0.0;
v.iter()