Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.GatewayArtifacts.cs
master d04483560b Complete ElkSharp document rendering cleanup and source decomposition
- Fix target-join (edge/4+edge/17): gateway face overflow redirect to left tip
- Fix under-node (edge/14,15,20): push-first corridor reroute instead of top corridor
- Fix boundary-slots (4->0): snap after gateway polish reordering
- Fix gateway corner diagonals (2->0): post-pipeline straightening pass
- Fix gateway interior adjacent: polygon-aware IsInsideNodeShapeInterior
- Fix gateway source face mismatch (2->0): per-edge redirect with lenient validation
- Fix gateway source scoring (5->0): per-edge scoring candidate application
- Fix edge-node crossing (1->0): push horizontal segment above blocking node
- Decompose 7 oversized files (~20K lines) into 55+ partials under 400 lines each
- Archive sprints 004 (document cleanup), 005 (decomposition), 007 (render speed)

All 44+ document-processing artifact assertions pass. Hybrid deterministic mode
documented as recommended path for LeftToRight layouts.

Tests verified: StraightExit 2/2, BoundarySlotOffenders 2/2, HybridDeterministicMode 3/3,
DocumentProcessingWorkflow artifact 1/1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 14:16:10 +03:00

382 lines
14 KiB
C#

namespace StellaOps.ElkSharp;
internal static partial class ElkEdgeRouterIterative
{
private readonly record struct GatewayArtifactState(
int SourceVertexExits,
int CornerDiagonals,
int InteriorAdjacentPoints,
int SourceFaceMismatches,
int SourceDominantAxisDetours,
int SourceScoringIssues)
{
public bool IsClean =>
SourceVertexExits == 0
&& CornerDiagonals == 0
&& InteriorAdjacentPoints == 0
&& SourceFaceMismatches == 0
&& SourceDominantAxisDetours == 0
&& SourceScoringIssues == 0;
public bool IsBetterThan(GatewayArtifactState other)
{
if (SourceVertexExits != other.SourceVertexExits)
{
return SourceVertexExits < other.SourceVertexExits;
}
if (CornerDiagonals != other.CornerDiagonals)
{
return CornerDiagonals < other.CornerDiagonals;
}
if (InteriorAdjacentPoints != other.InteriorAdjacentPoints)
{
return InteriorAdjacentPoints < other.InteriorAdjacentPoints;
}
if (SourceFaceMismatches != other.SourceFaceMismatches)
{
return SourceFaceMismatches < other.SourceFaceMismatches;
}
if (SourceDominantAxisDetours != other.SourceDominantAxisDetours)
{
return SourceDominantAxisDetours < other.SourceDominantAxisDetours;
}
return SourceScoringIssues < other.SourceScoringIssues;
}
public override string ToString()
{
return
$"vertex={SourceVertexExits} corner={CornerDiagonals} interior={InteriorAdjacentPoints} " +
$"face={SourceFaceMismatches} detour={SourceDominantAxisDetours} scoring={SourceScoringIssues}";
}
}
private static CandidateSolution ApplyFinalGatewayArtifactPolish(
CandidateSolution solution,
ElkPositionedNode[] nodes,
double minLineClearance)
{
var current = solution;
for (var round = 0; round < 2; round++)
{
var baselineArtifacts = EvaluateGatewayArtifacts(current.Edges, nodes, out var focusEdgeIds);
if (baselineArtifacts.IsClean || focusEdgeIds.Length == 0)
{
break;
}
ElkLayoutDiagnostics.LogProgress(
$"Hybrid gateway artifact polish round {round + 1} start: artifacts={baselineArtifacts} focus=[{string.Join(", ", focusEdgeIds)}]");
var candidateEdges = current.Edges;
candidateEdges = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(candidateEdges, nodes, focusEdgeIds);
candidateEdges = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(
candidateEdges,
nodes,
minLineClearance,
focusEdgeIds);
candidateEdges = ElkEdgePostProcessor.FinalizeDecisionTargetEntries(candidateEdges, nodes, focusEdgeIds);
candidateEdges = ElkEdgePostProcessor.NormalizeBoundaryAngles(candidateEdges, nodes);
candidateEdges = ElkEdgePostProcessor.NormalizeSourceExitAngles(candidateEdges, nodes);
candidateEdges = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
candidateEdges,
nodes,
minLineClearance,
focusEdgeIds,
enforceAllNodeEndpoints: true);
candidateEdges = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(candidateEdges, nodes, focusEdgeIds);
candidateEdges = ElkEdgePostProcessor.NormalizeBoundaryAngles(candidateEdges, nodes);
candidateEdges = ElkEdgePostProcessor.NormalizeSourceExitAngles(candidateEdges, nodes);
// Straighten any corner diagonals created by the face fix so they
// don't block promotion via the lexicographic IsBetterThan check
// (corners have higher priority than face mismatches).
candidateEdges = ElkEdgePostProcessor.StraightenGatewayCornerDiagonals(candidateEdges, nodes);
if (!TryPromoteGatewayArtifactCandidate(current, candidateEdges, nodes, baselineArtifacts, out var promoted))
{
break;
}
current = promoted;
ElkLayoutDiagnostics.LogProgress(
$"Hybrid gateway artifact polish round {round + 1} improved: retry={DescribeRetryState(current.RetryState)}");
}
return current;
}
private static bool TryPromoteGatewayArtifactCandidate(
CandidateSolution current,
ElkRoutedEdge[] candidateEdges,
ElkPositionedNode[] nodes,
GatewayArtifactState baselineArtifacts,
out CandidateSolution promoted)
{
promoted = current;
var candidateArtifacts = EvaluateGatewayArtifacts(candidateEdges, nodes, out _);
if (!candidateArtifacts.IsBetterThan(baselineArtifacts))
{
return false;
}
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
var candidateRetryState = BuildRetryState(
candidateScore,
HighwayProcessingEnabled
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
: 0);
if (HasHardRuleRegression(candidateRetryState, current.RetryState)
|| candidateScore.NodeCrossings > current.Score.NodeCrossings)
{
return false;
}
promoted = current with
{
Score = candidateScore,
RetryState = candidateRetryState,
Edges = candidateEdges,
};
return true;
}
private static GatewayArtifactState EvaluateGatewayArtifacts(
IReadOnlyCollection<ElkRoutedEdge> edges,
IReadOnlyCollection<ElkPositionedNode> nodes,
out string[] focusEdgeIds)
{
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
var focus = new HashSet<string>(StringComparer.Ordinal);
var sourceVertexExits = 0;
var cornerDiagonals = 0;
var interiorAdjacentPoints = 0;
var sourceFaceMismatches = 0;
var sourceDominantAxisDetours = 0;
var sourceScoringIssues = 0;
foreach (var edge in edges)
{
var path = ExtractPath(edge);
if (path.Count < 2)
{
continue;
}
if (nodesById.TryGetValue(edge.SourceNodeId ?? string.Empty, out var sourceNode)
&& ElkShapeBoundaries.IsGatewayShape(sourceNode))
{
if (ElkShapeBoundaries.IsNearGatewayVertex(sourceNode, path[0]))
{
sourceVertexExits++;
focus.Add(edge.Id);
}
if (HasGatewayCornerDiagonalArtifact(path, sourceNode, fromSource: true))
{
cornerDiagonals++;
focus.Add(edge.Id);
}
if (HasGatewayInteriorAdjacentPointArtifact(path, sourceNode, fromSource: true))
{
interiorAdjacentPoints++;
focus.Add(edge.Id);
}
if (HasGatewaySourcePreferredFaceMismatchArtifact(
path,
sourceNode,
nodes,
edge.SourceNodeId,
edge.TargetNodeId))
{
sourceFaceMismatches++;
focus.Add(edge.Id);
}
if (HasGatewaySourceDominantAxisDetourArtifact(
path,
sourceNode,
nodes,
edge.SourceNodeId,
edge.TargetNodeId))
{
sourceDominantAxisDetours++;
focus.Add(edge.Id);
}
if (ElkEdgePostProcessor.HasClearGatewaySourceScoringOpportunity(
path,
sourceNode,
nodes,
edge.SourceNodeId,
edge.TargetNodeId))
{
sourceScoringIssues++;
focus.Add(edge.Id);
}
}
if (nodesById.TryGetValue(edge.TargetNodeId ?? string.Empty, out var targetNode)
&& ElkShapeBoundaries.IsGatewayShape(targetNode))
{
if (HasGatewayCornerDiagonalArtifact(path, targetNode, fromSource: false))
{
cornerDiagonals++;
focus.Add(edge.Id);
}
if (HasGatewayInteriorAdjacentPointArtifact(path, targetNode, fromSource: false))
{
interiorAdjacentPoints++;
focus.Add(edge.Id);
}
}
}
focusEdgeIds = focus.OrderBy(edgeId => edgeId, StringComparer.Ordinal).ToArray();
return new GatewayArtifactState(
sourceVertexExits,
cornerDiagonals,
interiorAdjacentPoints,
sourceFaceMismatches,
sourceDominantAxisDetours,
sourceScoringIssues);
}
private static bool HasGatewayCornerDiagonalArtifact(
IReadOnlyList<ElkPoint> path,
ElkPositionedNode node,
bool fromSource)
{
if (!ElkShapeBoundaries.IsGatewayShape(node) || path.Count < 2)
{
return false;
}
var boundary = fromSource ? path[0] : path[^1];
var adjacent = fromSource ? path[1] : path[^2];
var deltaX = Math.Abs(boundary.X - adjacent.X);
var deltaY = Math.Abs(boundary.Y - adjacent.Y);
return deltaX >= 3d
&& deltaY >= 3d
&& ElkShapeBoundaries.IsNearGatewayVertex(node, boundary);
}
private static bool HasGatewayInteriorAdjacentPointArtifact(
IReadOnlyList<ElkPoint> path,
ElkPositionedNode node,
bool fromSource)
{
if (!ElkShapeBoundaries.IsGatewayShape(node) || path.Count < 2)
{
return false;
}
var adjacent = fromSource ? path[1] : path[^2];
return ElkShapeBoundaries.IsInsideNodeBoundingBoxInterior(node, adjacent);
}
private static bool HasGatewaySourcePreferredFaceMismatchArtifact(
IReadOnlyList<ElkPoint> path,
ElkPositionedNode sourceNode,
IReadOnlyCollection<ElkPositionedNode> allNodes,
string? sourceNodeId,
string? targetNodeId)
{
if (!ElkShapeBoundaries.IsGatewayShape(sourceNode)
|| path.Count < 2
|| !ElkEdgePostProcessor.HasClearGatewaySourceDirectRepairOpportunity(
path,
sourceNode,
allNodes,
sourceNodeId,
targetNodeId))
{
return false;
}
var centerX = sourceNode.X + (sourceNode.Width / 2d);
var centerY = sourceNode.Y + (sourceNode.Height / 2d);
var desiredDx = path[^1].X - centerX;
var desiredDy = path[^1].Y - centerY;
var boundaryDx = path[0].X - centerX;
var boundaryDy = path[0].Y - centerY;
if (Math.Abs(desiredDx) >= Math.Abs(desiredDy) * 1.25d)
{
return Math.Sign(boundaryDx) != Math.Sign(desiredDx)
|| Math.Abs(boundaryDy) > sourceNode.Height * 0.28d;
}
if (Math.Abs(desiredDy) >= Math.Abs(desiredDx) * 1.25d)
{
return Math.Sign(boundaryDy) != Math.Sign(desiredDy)
|| Math.Abs(boundaryDx) > sourceNode.Width * 0.28d;
}
return false;
}
private static bool HasGatewaySourceDominantAxisDetourArtifact(
IReadOnlyList<ElkPoint> path,
ElkPositionedNode sourceNode,
IReadOnlyCollection<ElkPositionedNode> allNodes,
string? sourceNodeId,
string? targetNodeId)
{
if (!ElkShapeBoundaries.IsGatewayShape(sourceNode)
|| path.Count < 3
|| !ElkEdgePostProcessor.HasClearGatewaySourceDirectRepairOpportunity(
path,
sourceNode,
allNodes,
sourceNodeId,
targetNodeId))
{
return false;
}
const double coordinateTolerance = 0.5d;
var centerX = sourceNode.X + (sourceNode.Width / 2d);
var centerY = sourceNode.Y + (sourceNode.Height / 2d);
var desiredDx = path[^1].X - centerX;
var desiredDy = path[^1].Y - centerY;
var dominantHorizontal = Math.Abs(desiredDx) >= Math.Abs(desiredDy) * 1.25d && Math.Sign(desiredDx) != 0;
var dominantVertical = Math.Abs(desiredDy) >= Math.Abs(desiredDx) * 1.25d && Math.Sign(desiredDy) != 0;
if (!dominantHorizontal && !dominantVertical)
{
return false;
}
var boundary = path[0];
var adjacent = path[1];
var firstDx = adjacent.X - boundary.X;
var firstDy = adjacent.Y - boundary.Y;
if (dominantHorizontal)
{
if (Math.Sign(firstDx) != Math.Sign(desiredDx) || Math.Abs(firstDx) <= coordinateTolerance)
{
return true;
}
return Math.Abs(firstDy) > Math.Max(24d, Math.Abs(desiredDy) + 12d)
&& Math.Abs(firstDy) > Math.Abs(firstDx) * 1.25d;
}
if (Math.Sign(firstDy) != Math.Sign(desiredDy) || Math.Abs(firstDy) <= coordinateTolerance)
{
return true;
}
return Math.Abs(firstDx) > Math.Max(24d, Math.Abs(desiredDx) + 12d)
&& Math.Abs(firstDx) > Math.Abs(firstDy) * 1.25d;
}
}