Skip to content

Fix assert crash in crossingsRemoved for paths with duplicate intersections#142

Open
hfutrell wants to merge 3 commits into
0.16.4-releasefrom
worktree-crossing-augmented-crash-fix
Open

Fix assert crash in crossingsRemoved for paths with duplicate intersections#142
hfutrell wants to merge 3 commits into
0.16.4-releasefrom
worktree-crossing-augmented-crash-fix

Conversation

@hfutrell

Copy link
Copy Markdown
Owner

Summary

  • Fixes a crash (assert(self.neighborsContain(node) == false)) in AugmentedGraph when crossingsRemoved is called on a path with degenerate near-coincident curves (such as a tiny loop at the start of a path)
  • The root cause: when selfIntersections returns the same intersection twice (or two intersections at identical IndexedPathLocations), sortAndMergeDuplicates merges the duplicate node pairs in a way that creates mutual-neighbor conflicts
  • Fixed replaceNeighbor to remove rather than duplicate an entry when the replacement node is already present
  • Fixed mergeNeighbors to skip self-referential connections, guard against adding duplicate neighbors, and remove the stale reference to the merged-away node

Test plan

  • testCrossingsRemovedAugmentedGraphDuplicateNeighborAssert: reproduces the crash using the exact path from the bug report; also validates correctness by sampling ±normal offsets at t=0.05/0.5/0.95 on every element and asserting path.contains (winding) matches result.contains (evenOdd)
  • Full test suite (280 tests) passes

🤖 Generated with Claude Code

hfutrell and others added 3 commits May 9, 2026 21:11
…duce mutual neighbor nodes

When selfIntersections returns the same intersection twice (possible with degenerate
near-coincident curves), sortAndMergeDuplicates creates two node pairs at the same
IndexedPathLocation. Merging the second pair caused addNeighbor to encounter a
neighbor already added during the first merge, triggering the assert.

Fix replaceNeighbor to remove rather than duplicate when the replacement is already
present, and fix mergeNeighbors to skip self-referential connections, guard against
adding duplicates, and clean up the stale reference to the merged-away node.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A closed path whose first element is a degenerate cubic (start == end, with
control points off that point) caused the bezier clipping algorithm to detect
the same self-intersection twice. This produced duplicate nodes in
sortAndMergeDuplicates which triggered assert(self.neighborsContain(node) == false).

The test uses a 3-segment path (degenerate cubic + line + closing line) — the
smallest structure that reproduces the crash without the fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 260-line real-world path is replaced by a 3-segment path that
reliably demonstrates the same bug: a degenerate cubic (start == end)
followed by a line, closed back to the start point.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant