diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.BoundaryFirst.GatewayApproachAdjustment.cs b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.BoundaryFirst.GatewayApproachAdjustment.cs index 3d2ead72d..1edcac3e6 100644 --- a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.BoundaryFirst.GatewayApproachAdjustment.cs +++ b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.BoundaryFirst.GatewayApproachAdjustment.cs @@ -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; } } } diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.cs b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.cs index 42c8656cd..3d57dd259 100644 --- a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.cs +++ b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.cs @@ -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; } diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkShapeBoundaries.GatewayInterior.cs b/src/__Libraries/StellaOps.ElkSharp/ElkShapeBoundaries.GatewayInterior.cs index 7b87406f8..fbb27b440 100644 --- a/src/__Libraries/StellaOps.ElkSharp/ElkShapeBoundaries.GatewayInterior.cs +++ b/src/__Libraries/StellaOps.ElkSharp/ElkShapeBoundaries.GatewayInterior.cs @@ -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; }