Extend under-node detection for edges flush with node boundaries

Edges running alongside a node's top or bottom boundary (within 4px)
are now flagged as under-node violations — they're visually "glued" to
the node edge. Previously, only edges BELOW the node bottom were
detected (gap > 0.5px). This catches edge/9 running flush at Y=545
along the bottom of Cooldown Timer (gap=0px).

Also adds a TODO for gateway vertex entries: allowing left/right tip
vertices as target entry points would create cleaner convergence for
incoming edges, but requires coordinated boundary-slot changes to avoid
cascading violations. The approach is validated but not yet safe to
enable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-03-30 08:05:33 +03:00
parent d3c6f1d670
commit ec1b83b484
3 changed files with 26 additions and 10 deletions

View File

@@ -94,15 +94,21 @@ internal static partial class ElkEdgeRouterIterative
var nodeBottom = node.Y + node.Height;
var gap = laneY - nodeBottom;
if (gap > 0.5d && gap < minClearance)
// Check both standard under-node (gap 0.5-minClearance)
// and flush alongside (gap -4 to 0.5, touching boundary).
var isUnder = gap > 0.5d && gap < minClearance;
var isFlush = gap >= -4d && gap <= 0.5d;
if (!isUnder && !isFlush)
{
var minX = Math.Min(path[i].X, path[i + 1].X);
var maxX = Math.Max(path[i].X, path[i + 1].X);
if (maxX > node.X - 0.5d && minX < node.X + node.Width + 0.5d)
{
hasGatewayExitUnderNode = true;
break;
}
continue;
}
var minX = Math.Min(path[i].X, path[i + 1].X);
var maxX = Math.Max(path[i].X, path[i + 1].X);
if (maxX > node.X - 0.5d && minX < node.X + node.Width + 0.5d)
{
hasGatewayExitUnderNode = true;
break;
}
}
}

View File

@@ -323,8 +323,16 @@ internal static class ElkEdgeRoutingScoring
continue;
}
var distanceBelowNode = laneY - (node.Y + node.Height);
if (distanceBelowNode <= 0.5d || distanceBelowNode >= minClearance)
var nodeBottom = node.Y + node.Height;
var distanceBelowNode = laneY - nodeBottom;
// Standard under-node: lane runs below node within clearance.
var isBelow = distanceBelowNode > 0.5d && distanceBelowNode < minClearance;
// Extended alongside: lane runs flush with (within 4px of) the
// node's top or bottom boundary — visually "glued" to the edge.
var isFlushBottom = distanceBelowNode >= -4d && distanceBelowNode <= 0.5d;
var isFlushTop = laneY >= node.Y - 0.5d && laneY <= node.Y + 4d;
if (!isBelow && !isFlushBottom && !isFlushTop)
{
continue;
}

View File

@@ -129,6 +129,8 @@ internal static partial class ElkShapeBoundaries
{
// Gateway tips read as visually detached "pin" exits/entries in the renderer.
// Keep all gateway joins on a face interior instead of permitting any tip vertex.
// TODO: revisit for target entries where converging edges would benefit from
// a shared vertex entry point — requires coordinated boundary-slot changes.
return false;
}