Skip to content

fix(html): trace hyperedge perimeter in convex-hull order, not member order - #2450

Open
ysys143 wants to merge 1 commit into
Graphify-Labs:v8from
ysys143:fix/hyperedge-hull-order
Open

fix(html): trace hyperedge perimeter in convex-hull order, not member order#2450
ysys143 wants to merge 1 commit into
Graphify-Labs:v8from
ysys143:fix/hyperedge-hull-order

Conversation

@ysys143

@ysys143 ysys143 commented Aug 4, 2026

Copy link
Copy Markdown

Fixes #2449.

What

_hyperedge_script traced the hyperedge ring by walking h.nodes in array
order. That self-intersects whenever the layout does not happen to place members
in angular order around the centroid, so fill() paints crossed wedges instead
of one region. The 1.15x expansion cannot mask it — a positive homothety
preserves segment intersections.

This computes Andrew's monotone chain over the member positions and expands the
hull instead of the raw array.

Why a hull rather than an angular sort

A one-line atan2 sort also removes the crossings, and I tried that first. I
went with the hull because it matches the existing // Centroid and expanded hull comment and #334's "convex-hull polygons" wording, and because it degrades
better on degenerate member sets: collinear and duplicate points collapse to the
extremes rather than producing a zero-area crossed path, and there is no
atan2(0, 0) tie when the centroid lands on a vertex.

Happy to swap it for the one-liner if you prefer the smaller diff.

Scale of the defect

Over 20,000 random 4-7 member point sets (the sizes real hyperedges carry — mine
were {4: 1, 5: 8, 6: 3, 7: 3}), 75.3% of arbitrary orderings self-intersect
after expansion. This was the common case, not a corner.

Tests

Two tests in tests/test_export.py:

  • test_hyperedge_perimeter_uses_convex_hull_not_member_order — guards against
    reverting to raw member order.
  • test_hyperedge_convex_hull_js_is_geometrically_sound — executes the emitted
    JS in node and asserts the ring is simple, convex, counter-clockwise, and
    encloses every member, across 2,000 random sets plus the degenerate cases and
    the bow-tie ordering this exists for. Skipped when node is unavailable.

Both fail on the unpatched exporter and pass with the change.

tests/ is otherwise unaffected: 3894 passed. The 15 failures in
test_ollama_retry_cap.py and test_skillgen.py reproduce identically on a
clean v8 checkout without this change.

Open question

#2449 raises it too: if h.nodes order is meant to be load-bearing — several
emitted arrays do follow their flow labels — then tracing in array order is the
intent and this should be closed. My reading is that the schema treats
hyperedges as sets (participate_in | implement | form, no sequence relation)
and that a translucent fill with no direction cues cannot convey a sequence
anyway, but that call is yours.

… order (Graphify-Labs#2449)

_hyperedge_script mapped h.nodes straight to positions and traced that array
in order, so the ring self-intersected whenever the layout did not happen to
place members in angular order — fill() then painted crossed wedges instead of
one region. Over 20k random 4-7 member sets, 75.3% of arbitrary orderings
self-intersect, so this was the common case rather than a corner.

The 1.15x expansion could not mask it: a positive homothety preserves segment
intersections.

Compute Andrew's monotone chain over the member positions and expand the hull
instead of the raw array. This matches the existing "expanded hull" comment and
Graphify-Labs#334's "convex-hull polygons" wording, and degrades cleanly where an angular
sort would not: collinear and duplicate points collapse to the extremes rather
than producing a zero-area crossed path, with no atan2(0, 0) tie.

Tests execute the emitted JS in node (skipped when absent) and assert the ring
is simple, convex, counter-clockwise, and encloses every member.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

This PR modifies the HTML exporter's hyperedge rendering to compute a convex hull (via Andrew's monotone chain) over member node positions before tracing the surrounding polygon, replacing the prior approach that expanded points in raw member order. It adds two tests in tests/test_export.py: one asserting the emitted script contains the convexHull function and uses hull-ordered points, and another that extracts the emitted JavaScript and runs it under node (skipping if unavailable) to check geometric properties like non-self-intersection, convexity, and point enclosure across random and degenerate inputs. The large list of "changed symbols" appears to reflect the test module being touched; the substantive surface area is the _hyperedge_script function in graphify/exporters/html.py and the two new test functions.

No blocking issues surfaced. 2 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 320 functions depend on the 114 functions this change touches.

Health — grade A; 5 existing hotspot(s) in the area this change touches (pre-existing, not introduced here):

  • _rebuild_code() — 68 callers, 49 callees (high)
  • dispatch_command() — 2 callers, 110 callees (high)
  • to_html() — 16 callers, 7 callees (high)
  • run_pipeline() — 8 callers, 12 callees (high)
  • watch() — 4 callers, 6 callees (medium)

Verification — 320 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 190 function(s) in the blast radius were not formally verified this run

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.

Hyperedge polygons are traced in h.nodes array order, so the filled region can self-intersect

1 participant