Skip to content

Coverage: corner passes and near-coincident edges (#25); exact approx centre ties - #36

Merged
mdsumner merged 2 commits into
mainfrom
fix/corner-pass
Oct 28, 2026
Merged

mdsumner merged 2 commits into
mainfrom
fix/corner-pass

Conversation

@mdsumner

@mdsumner mdsumner commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Fixes #25, the C++ side of hypertidy/controlledburn-rs#6. Same change as hypertidy/controlledburn-rs#18.

Before: a cell was overcounted where a polygon edge passes exactly through a cell corner, or where two edges nearly coincide. #25's ring covered 6.6016 for an area of 6.1578 (cell (12, 18) burned 1.0, exact 0.0943). The other rings from controlledburn-rs#6 burned 0.739 for 0.455 and 0.839 or 1.0 (depending on the start vertex) for 0.339. A sliver of area 4e-13 covered 11 whole cells at 2x resolution. In approx mode, an empty cell burned when two edges both passed 1e-13 right of its centre.

Causes:

  • An edge through a corner gave a crossing an ulp off the corner, and the next cell a sliver traversal along its wall where the ring only touches the corner.
  • exactextract's left_hand_area depends on the order of the traversal ends around a cell. Rounding can swap two nearly coincident ends (that sliver, or a ring doubling back along itself), and it then returns the complement.
  • A single traversal lying along a cell wall doesn't split the cell, but the single-traversal formula treats it as if it did. A ring tracing the grid's bottom edge out and back put one direction in the bottom row and the other in the padding row, and the bottom-row cells came out fully covered.
  • A zero-width spike out to a corner vertex and back left a coverage of 1e-16, and that record blanked a fully covered cell.
  • Approx compared the rounded intercept with the centre.

After:

  • The walker exits through the corner when the crossing is within the lattice tolerance of it (1e-9 of a cell).
  • Cells crossed by several traversals, and a single traversal along the walls, get their coverage from Green's theorem, the identity behind controlledburn-rs's CoverageWindow. Each traversal adds its integral of (x - xmax) dy, plus the cell width times the covered length of the left wall, read from the ring's traversals to its left in the row. No ordering is involved.
  • A coverage below the sweep's 1e-6 tolerance gets a record only when the ring passes through the cell.
  • Approx decides the side of a centre with an exact orientation test: FMA-split products summed as a Shewchuk expansion, with a float filter first.

Golden tables (controlledburn-golden.json lives in controlledburn-rs; dumped from this build): the 28 existing cases match bit for bit, except bowtie coverage. Its closing edge lies on a column wall, and it gains the six cells it was missing: 26 cells before, the exact 32 now. lines_multi's line records come out in a different order from the golden file, as they already do on main. Three cases are added: corner_pass, sliver_coincident_edges and approx_centre_tie. corner_pass is also a new shared fixture (14 now); the CSVs are byte-identical between the repos.

How checked:

  • ctest 2/2. test_burn adds five tests, which fail 124 checks on main.
  • R: 384 expectations passed, 0 failed (test-lattice-snap.R adds four tests).
  • Python: pytest 133 passed (test_regressions.py adds four).
  • The Rust port of the same change passes its property suite: 20 of 20 runs at 10,000 cases and 20 of 20 at 100,000. Unfixed main failed 20 of 20 at 100,000.

Status of the other ports (wxylib m20): the hole fix (controlledburn-rs#11, here #27/#31) and the wall-cell fix (#29 and 1a597eb, via #32) are both already on main, and Rust and C++ agree on their golden tables. CoverageWindow/RowPrefix don't exist in the C++ core yet; this PR doesn't port them.

Also noticed: NEWS.md doesn't mention the hole or wall-cell fixes, though 0.3.2 contains both. Not changed here. DESCRIPTION goes to 0.3.2.9000 for the development NEWS entry.

🤖 Generated with Claude Code

… centre ties

An edge exactly through a cell corner, or two nearly coincident edges,
overcounted a cell by up to a whole cell. Three fixes in walk_polyline and
walk_ring, plus one in the approx sweep:

- Corner guard. An edge through a corner gave a crossing an ulp off the
  corner and a sliver traversal along the next cell's wall where the ring
  only touches the corner. The walker now snaps an exit crossing within
  the lattice tolerance (1e-9 of a cell) of a corner onto the corner.
- Green's theorem for cells crossed by several traversals, and for a
  single traversal lying along the cell walls. exactextract's chain chaser
  depends on the order of the traversal ends around the cell; rounding can
  swap two nearly coincident ends (a sliver, a ring doubling back along
  itself) and it then returned the complement. Each traversal now adds the
  integral of (x - xmax) dy, plus the cell width times the covered length
  of the left wall, which is minus the net height of the ring's traversals
  left of the cell in its row. Same identity as controlledburn-rs's
  CoverageWindow.
- A coverage below the sweep's 1e-6 tolerance gets a record only when the
  ring passes through the cell: a spike to a corner vertex and back left
  1e-16, and that record blanked a fully covered cell.
- Approx: the side of a cell centre an edge passes is an exact orientation
  test (FMA-split products summed as a Shewchuk expansion) on the edge's
  endpoints taken bottom to top. Intercepts sort by column, side, then x.

Before: the issue's ring covered 6.6016 for 6.1578 (cell (12, 18) burned
1.0 for 0.0943); a 4e-13 sliver covered 11 cells at 2x resolution; the
bowtie probe covered 26 cells of 32. After: all exact. Golden tables
dumped from this build match controlledburn-rs's (bit for bit) except
bowtie coverage, which gains the six missing cells, and the record order
of lines_multi, which already differed on main.

Same change as controlledburn-rs (fix/corner-pass). Tests: C++
test_corner_pass and four more (124 failed checks before the fix), R
test-lattice-snap.R, Python test_regressions.py, and the shared fixture
corner_pass (14 fixtures, synced with controlledburn-rs). ctest 2/2, R 384
expectations passed, pytest 133 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R parses 17-digit decimal literals with its own strtod, which is an ulp
off where long double is double (macOS arm64). This test's edges pass
1e-13 from a centre, so the ulp moved them across it and all 16 start
variants failed there. Hex literals parse exactly on every platform.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mdsumner
mdsumner merged commit ed5281b into main Oct 28, 2026
12 checks passed
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.

Coverage: boundary cell filled as interior when an edge passes exactly through a cell corner (lattice edge case)

1 participant