diff --git a/src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/DocumentProcessingWorkflowRenderingTests.Artifacts.cs b/src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/DocumentProcessingWorkflowRenderingTests.Artifacts.cs index 4cbeb51e5..9c398aa4b 100644 --- a/src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/DocumentProcessingWorkflowRenderingTests.Artifacts.cs +++ b/src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/DocumentProcessingWorkflowRenderingTests.Artifacts.cs @@ -137,6 +137,23 @@ public partial class DocumentProcessingWorkflowRenderingTests .SelectMany(edge => GetBoundaryAngleViolations(edge, layout.Nodes)) .ToArray(); TestContext.Out.WriteLine($"Boundary angle offenders: {(boundaryAngleOffenders.Length == 0 ? "" : string.Join(", ", boundaryAngleOffenders))}"); + // Diagnostic: compare scoring vs test target-join detection + var scoringJoinSeverity = new Dictionary(StringComparer.Ordinal); + var elkNodesForDiag = layout.Nodes.Select(ToElkNode).ToArray(); + var elkEdgesForDiag = layout.Edges.Select(edge => new ElkRoutedEdge + { + Id = edge.Id, SourceNodeId = edge.SourceNodeId, TargetNodeId = edge.TargetNodeId, + Kind = edge.Kind, Label = edge.Label, + Sections = edge.Sections.Select(s => new ElkEdgeSection + { + StartPoint = new ElkPoint { X = s.StartPoint.X, Y = s.StartPoint.Y }, + EndPoint = new ElkPoint { X = s.EndPoint.X, Y = s.EndPoint.Y }, + BendPoints = s.BendPoints.Select(p => new ElkPoint { X = p.X, Y = p.Y }).ToArray(), + }).ToArray(), + }).ToArray(); + var scoringJoinCount = ElkEdgeRoutingScoring.CountTargetApproachJoinViolations( + elkEdgesForDiag, elkNodesForDiag, scoringJoinSeverity, 1); + TestContext.Out.WriteLine($"Scoring target-join count: {scoringJoinCount}, edges: [{string.Join(", ", scoringJoinSeverity.Keys.OrderBy(k => k))}]"); var targetJoinOffenders = GetTargetApproachJoinOffenders(layout.Edges, layout.Nodes).ToArray(); TestContext.Out.WriteLine($"Target join offenders: {(targetJoinOffenders.Length == 0 ? "" : string.Join(", ", targetJoinOffenders))}"); var elkNodes = layout.Nodes.Select(node => new ElkPositionedNode diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs index 25a2d57a5..3bc246d21 100644 --- a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs +++ b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs @@ -292,6 +292,29 @@ internal static partial class ElkEdgeRouterIterative current = RepairRemainingEdgeNodeCrossings(current, nodes); } + // Final target-join repair: the per-edge gateway fixes may create + // new target-join convergences that didn't exist before. Run the + // spread one more time to catch them. + var finalJoinSeverity = new Dictionary(StringComparer.Ordinal); + var finalJoinCount = ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(current.Edges, nodes, finalJoinSeverity, 1); + ElkLayoutDiagnostics.LogProgress( + $"Final target-join check: count={finalJoinCount} edges=[{string.Join(", ", finalJoinSeverity.Keys.OrderBy(k => k))}]"); + if (finalJoinSeverity.Count >= 2) + { + var joinFocus = finalJoinSeverity.Keys.ToArray(); + var joinCandidate = ElkEdgePostProcessor.SpreadTargetApproachJoins( + current.Edges, nodes, minLineClearance, joinFocus, forceOutwardAxisSpacing: true); + joinCandidate = ElkEdgePostProcessor.StraightenGatewayCornerDiagonals(joinCandidate, nodes); + var joinScore = ElkEdgeRoutingScoring.ComputeScore(joinCandidate, nodes); + var joinJoinCount = ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(joinCandidate, nodes); + if (joinJoinCount < finalJoinCount) + { + current = current with { Score = joinScore, Edges = joinCandidate }; + ElkLayoutDiagnostics.LogProgress( + $"Hybrid final target-join repair: joins={joinJoinCount}"); + } + } + return current; }