fix: classifyAndGroup pairs a hole with the wrong hull - #41
Conversation
It paired each hole with the first hull whose filled area intersected it, which is order-dependent and wrong when an island hull sits inside another hull's hole. Pair each hole with the innermost hull that actually contains it instead, and add PolygonHelperTest.
|
Can you show screenshot/video of the issue in action and then similar with the fix please? |
|
These screenshots are fine I think. |
|
Claude Code Opus 4.8, multiple review rounds. |
Thanks for the honesty. |
|
No hurries. I have implemented a workaround in my mod so it also works with if this PR does not get merged. |
I am fine with AI generated code, these tools are a huge help. But they need to be well formed PRs and not slop. This PR is clean and not sloppy. Thanks for that. I've had some that are really bad in the journeymap-legacy repo. I used fable and codex 5.6 to assist with the review as well. |
- fall back to the first intersecting hull for a hole that no hull contains, preserving classifyAndGroup's documented arbitrary-polygon behavior instead of silently dropping a partially overlapping hole - share one signed shoelace helper between isHole and ringArea - scope the order-independence note to normalized Area contours and document the hole-association contract on classifyAndGroup - initialize the per-hull hole lists with forEach - add a partial-overlap regression test
mysticdrew
left a comment
There was a problem hiding this comment.
Thanks for the fix and speedy responses and code review updates.
I'll get this merged when I have more time to cherry pick to all versions api.2.0.0, This will be in the next release for supported versions.
|
Thank you for the kind interaction and helpful comments. Really helps learning. |


Symptom
PolygonHelper.classifyAndGroupsometimes attaches a hole to the wrong hull. Thehull that truly owns the hole then renders with no hole carved, so its fill covers a
region that was supposed to be empty. When two mods draw adjacent filled tones (one
showing through the other's hole), the mis-paired hull's fill floods solid over the
neighboring tone.
Root cause
The grouping paired a hole with the first hull whose filled outline merely
intersected it, then removed the hole from the pool:
toArea(hull)fills the hull ignoring nesting, so "intersects" is far too weak atest. It breaks whenever a hull is nested inside another hull's hole (an island
sitting inside a hole): both the outer hull and the inner island have filled areas
that overlap the hole between them, so whichever hull the loop visits first claims it.
The pairing is therefore order-dependent, and when the inner island wins it is handed
a hole larger than itself (island minus hole renders as nothing) while the outer hull
is left with no hole and fills solid over the region that should have been carved out.
Reproduction (nested island-in-hole)
The contours are the square (hull), the big rectangle (a hole), and the small
rectangle (an island hull). Correct output: the big hole belongs to the square,
and the island has no holes. On the old code the hole is order-dependently grabbed by
the island, the square keeps no hole, and the square's fill covers the region that
should have stayed empty.
(The test builds this topology with rectangles rather than ellipses on purpose:
createPolygonFromAreaonly extractsSEG_MOVETO/SEG_LINETOsegments, so anellipse's curved contour would be dropped. Rectilinear contours also match JourneyMap's
real usage, which unions chunk squares.)
Fix
Pair each hole with the innermost hull that actually contains it:
(
OminusHis empty). An island's fill intersects a hole it does not own, sointersection is not enough.
the unsigned shoelace area of the hull ring and pick the smallest that contains the
hole. This also makes the result independent of contour order.
input) is dropped rather than forced onto an unrelated hull.
isHole,toArea, andcreatePolygonFromArea's contour extraction are unchanged;only the grouping logic changed.
Tests
Adds
common/src/test/java/journeymap/api/v2/client/util/PolygonHelperTest.java:nestedIslandDoesNotStealTheEnclosingHullsHolereproduces the bug (fails on the oldcode, passes on the fix).
holeIsPairedWithTheInnermostContainingHullcovers a hole enclosed by two nestedhulls.
holeWithNoContainingHullIsDroppedpins the drop path for an unenclosed hole.plainDonutKeepsItsHoleandseveralNonNestedHolesAllGroupWithTheirHullguard thecommon cases.
Each test also asserts the invariant that re-unioning every (hull minus its holes)
reproduces the input
Area, which catches both a stolen and a dropped hole../gradlew :common:testpasses, including the pre-existing tests.Branch
Based on
1.21.1_2.0.0, the repository's default branch. The same change applies to the sibling per-Minecraft-version branches; happy to retarget or port it if you would prefer a different base.