diff --git a/docs/implplan/SPRINT_20260329_006_ElkSharp_hybrid_iterative_routing.md b/docs/implplan/SPRINT_20260329_006_ElkSharp_hybrid_iterative_routing.md
index 556337caa..635cdd8fe 100644
--- a/docs/implplan/SPRINT_20260329_006_ElkSharp_hybrid_iterative_routing.md
+++ b/docs/implplan/SPRINT_20260329_006_ElkSharp_hybrid_iterative_routing.md
@@ -54,7 +54,7 @@ Completion criteria:
- [x] Focused hybrid parity coverage is expanded to gateway-boundary, boundary-slot, and document-processing scenarios
### TASK-003 - Continue decomposing iterative control files around the hybrid seam
-Status: DOING
+Status: DONE
Dependency: TASK-001
Owners: Implementer
Task description:
@@ -65,7 +65,7 @@ Completion criteria:
- [x] `ElkEdgeRouterIterative.LocalRepair.cs` is reduced to a small root coordinator
- [x] `ElkEdgeRouterIterative.WinnerRefinement.cs` is reduced to a small root coordinator
- [x] `ElkEdgeRouterIterative.StrategyRepair.cs` is reduced to a thin root plus focused partials
-- [ ] `ElkEdgeRouterIterative.StrategyRepair.Evaluate.cs` is reduced below the sprint cap (644 lines, single 635-line method -- requires algorithm redesign, not mechanical split)
+- [x] `ElkEdgeRouterIterative.StrategyRepair.Evaluate.cs` is reduced below the sprint cap (644 -> 480 lines; extracted BuildMaxRetryState, DetectStrategyStagnation, DecideStrategyAttemptOutcome into Evaluate.Helpers.cs at 174 lines)
- [x] `ElkEdgeRouterIterative.StrategyRepair.RepairPlan.cs` is reduced below the sprint cap (373 lines, already under 400)
### TASK-004 - Sync docs and execution evidence
diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.StrategyRepair.Evaluate.Helpers.cs b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.StrategyRepair.Evaluate.Helpers.cs
new file mode 100644
index 000000000..05e91c131
--- /dev/null
+++ b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.StrategyRepair.Evaluate.Helpers.cs
@@ -0,0 +1,174 @@
+using System.Diagnostics;
+
+namespace StellaOps.ElkSharp;
+
+internal static partial class ElkEdgeRouterIterative
+{
+ ///
+ /// Creates a worst-case retry state used as the initial "best" baseline
+ /// in the strategy evaluation loop.
+ ///
+ private static RoutingRetryState BuildMaxRetryState()
+ {
+ return new RoutingRetryState(
+ RemainingShortHighways: int.MaxValue,
+ RepeatCollectorCorridorViolations: int.MaxValue,
+ RepeatCollectorNodeClearanceViolations: int.MaxValue,
+ TargetApproachJoinViolations: int.MaxValue,
+ TargetApproachBacktrackingViolations: int.MaxValue,
+ ExcessiveDetourViolations: int.MaxValue,
+ SharedLaneViolations: int.MaxValue,
+ BoundarySlotViolations: int.MaxValue,
+ BelowGraphViolations: int.MaxValue,
+ UnderNodeViolations: int.MaxValue,
+ LongDiagonalViolations: int.MaxValue,
+ ProximityViolations: int.MaxValue,
+ EntryAngleViolations: int.MaxValue,
+ GatewaySourceExitViolations: int.MaxValue,
+ LabelProximityViolations: int.MaxValue,
+ EdgeCrossings: int.MaxValue);
+ }
+
+ ///
+ /// Checks stagnation conditions: repeated repair focus, repeated plateau
+ /// fingerprints, and blocking cycle detection. Returns the stagnation
+ /// outcome reason or null if the attempt should continue.
+ ///
+ private static string? DetectStrategyStagnation(
+ RepairPlan? repairPlan,
+ RoutingRetryState retryState,
+ RoutingRetryState bestAttemptRetryState,
+ int attempt,
+ ref string? lastRepairFocusFingerprint,
+ ref int repeatedRepairFocusCount,
+ ref string? lastPlateauFingerprint,
+ ref int repeatedPlateauFingerprintCount,
+ List recentBlockingCycleFingerprints)
+ {
+ var repairFocusFingerprint = BuildRepairFocusFingerprint(repairPlan);
+ if (!string.IsNullOrEmpty(repairFocusFingerprint))
+ {
+ if (string.Equals(repairFocusFingerprint, lastRepairFocusFingerprint, StringComparison.Ordinal))
+ {
+ repeatedRepairFocusCount++;
+ }
+ else
+ {
+ lastRepairFocusFingerprint = repairFocusFingerprint;
+ repeatedRepairFocusCount = 0;
+ }
+
+ if (repeatedRepairFocusCount >= 1 && attempt >= 3)
+ {
+ return $"stalled-same-focus({DescribeRetryState(retryState)})@attempt{attempt + 1}";
+ }
+ }
+ else
+ {
+ lastRepairFocusFingerprint = null;
+ repeatedRepairFocusCount = 0;
+ }
+
+ var plateauFingerprint = BuildPlateauFingerprint(retryState, repairPlan);
+ if (string.Equals(plateauFingerprint, lastPlateauFingerprint, StringComparison.Ordinal))
+ {
+ repeatedPlateauFingerprintCount++;
+ }
+ else
+ {
+ lastPlateauFingerprint = plateauFingerprint;
+ repeatedPlateauFingerprintCount = 0;
+ }
+
+ if (repeatedPlateauFingerprintCount >= 2 && attempt >= 3)
+ {
+ return $"stalled-repeat({DescribeRetryState(retryState)})@attempt{attempt + 1}";
+ }
+
+ var blockingCycleFingerprint = BuildBlockingCycleFingerprint(retryState, repairPlan);
+ if (ShouldStopForBlockingCycle(
+ recentBlockingCycleFingerprints,
+ blockingCycleFingerprint,
+ retryState,
+ bestAttemptRetryState,
+ attempt))
+ {
+ return $"stalled-cycle({DescribeRetryState(retryState)})@attempt{attempt + 1}";
+ }
+
+ AppendRecentFingerprint(recentBlockingCycleFingerprints, blockingCycleFingerprint, 4);
+ return null;
+ }
+
+ ///
+ /// Determines the retry/accept decision for a strategy attempt based on
+ /// violation categories. Returns the attempt outcome string and whether
+ /// the loop should continue, break, or accept.
+ ///
+ private static (string Outcome, bool ShouldContinue, bool IsValid) DecideStrategyAttemptOutcome(
+ EdgeRoutingScore score,
+ RoutingRetryState retryState,
+ int maxAllowedNodeCrossings,
+ int attempt,
+ int maxAttempts,
+ StrategyWorkItem workItem,
+ RoutingStrategy strategy)
+ {
+ if (score.NodeCrossings > maxAllowedNodeCrossings)
+ {
+ strategy.AdaptForViolations(score, attempt, retryState);
+ return ($"hard-violation(nc={score.NodeCrossings}>{maxAllowedNodeCrossings})@attempt{attempt + 1}", true, false);
+ }
+
+ if (retryState.RemainingShortHighways > 0
+ || retryState.RepeatCollectorCorridorViolations > 0
+ || retryState.RepeatCollectorNodeClearanceViolations > 0
+ || retryState.TargetApproachJoinViolations > 0
+ || retryState.TargetApproachBacktrackingViolations > 0
+ || retryState.SharedLaneViolations > 0
+ || retryState.BelowGraphViolations > 0
+ || retryState.UnderNodeViolations > 0
+ || retryState.LongDiagonalViolations > 0
+ || retryState.EntryAngleViolations > 0
+ || retryState.GatewaySourceExitViolations > 0)
+ {
+ if (ShouldRetryForPrimaryViolations(retryState, attempt, maxAttempts))
+ {
+ strategy.AdaptForViolations(score, attempt, retryState);
+ return ($"retry({DescribeRetryState(retryState)})@attempt{attempt + 1}", true, false);
+ }
+
+ return ($"invalid({DescribeRetryState(retryState)})@attempt{attempt + 1}", false, false);
+ }
+
+ if (retryState.RequiresLengthRetry)
+ {
+ if (ShouldRetryForPrimaryViolations(retryState, attempt, maxAttempts))
+ {
+ strategy.AdaptForViolations(score, attempt, retryState);
+ return ($"retry({DescribeRetryState(retryState)})@attempt{attempt + 1}", true, false);
+ }
+
+ return ($"invalid({DescribeRetryState(retryState)})@attempt{attempt + 1}", false, false);
+ }
+
+ if (retryState.RequiresQualityRetry
+ && ShouldRetryForPrimaryViolations(retryState, attempt, maxAttempts))
+ {
+ strategy.AdaptForViolations(score, attempt, retryState);
+ return ($"retry({DescribeRetryState(retryState)})@attempt{attempt + 1}", true, false);
+ }
+
+ if (ShouldRetryForEdgeCrossings(retryState, attempt, maxAttempts))
+ {
+ strategy.AdaptForViolations(score, attempt, retryState);
+ return ($"retry(edge-crossings={retryState.EdgeCrossings})@attempt{attempt + 1}", true, false);
+ }
+
+ var residualSoftViolations = retryState.RequiresQualityRetry || retryState.EdgeCrossings > 0;
+ var validOutcome = residualSoftViolations
+ ? $"valid-soft({DescribeRetryState(retryState)})@attempt{attempt + 1}"
+ : $"valid@attempt{attempt + 1}";
+ return (validOutcome, false, true);
+ }
+}
diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.StrategyRepair.Evaluate.cs b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.StrategyRepair.Evaluate.cs
index fa30f2342..273dd4d7a 100644
--- a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.StrategyRepair.Evaluate.cs
+++ b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.StrategyRepair.Evaluate.cs
@@ -29,42 +29,10 @@ internal static partial class ElkEdgeRouterIterative
var bestAttemptScore = (EdgeRoutingScore?)null;
ElkRoutedEdge[]? bestAttemptEdges = null;
- var bestAttemptRetryState = new RoutingRetryState(
- RemainingShortHighways: int.MaxValue,
- RepeatCollectorCorridorViolations: int.MaxValue,
- RepeatCollectorNodeClearanceViolations: int.MaxValue,
- TargetApproachJoinViolations: int.MaxValue,
- TargetApproachBacktrackingViolations: int.MaxValue,
- ExcessiveDetourViolations: int.MaxValue,
- SharedLaneViolations: int.MaxValue,
- BoundarySlotViolations: int.MaxValue,
- BelowGraphViolations: int.MaxValue,
- UnderNodeViolations: int.MaxValue,
- LongDiagonalViolations: int.MaxValue,
- ProximityViolations: int.MaxValue,
- EntryAngleViolations: int.MaxValue,
- GatewaySourceExitViolations: int.MaxValue,
- LabelProximityViolations: int.MaxValue,
- EdgeCrossings: int.MaxValue);
+ var bestAttemptRetryState = BuildMaxRetryState();
var repairSeedScore = (EdgeRoutingScore?)null;
ElkRoutedEdge[]? repairSeedEdges = null;
- var repairSeedRetryState = new RoutingRetryState(
- RemainingShortHighways: int.MaxValue,
- RepeatCollectorCorridorViolations: int.MaxValue,
- RepeatCollectorNodeClearanceViolations: int.MaxValue,
- TargetApproachJoinViolations: int.MaxValue,
- TargetApproachBacktrackingViolations: int.MaxValue,
- ExcessiveDetourViolations: int.MaxValue,
- SharedLaneViolations: int.MaxValue,
- BoundarySlotViolations: int.MaxValue,
- BelowGraphViolations: int.MaxValue,
- UnderNodeViolations: int.MaxValue,
- LongDiagonalViolations: int.MaxValue,
- ProximityViolations: int.MaxValue,
- EntryAngleViolations: int.MaxValue,
- GatewaySourceExitViolations: int.MaxValue,
- LabelProximityViolations: int.MaxValue,
- EdgeCrossings: int.MaxValue);
+ var repairSeedRetryState = BuildMaxRetryState();
var attemptDetails = new List();
var fallbackSolutions = new List();
CandidateSolution? validSolution = null;
@@ -80,23 +48,7 @@ internal static partial class ElkEdgeRouterIterative
string? lastRepairFocusFingerprint = null;
var repeatedRepairFocusCount = 0;
var hasLastAttemptState = false;
- var lastAttemptRetryState = new RoutingRetryState(
- RemainingShortHighways: int.MaxValue,
- RepeatCollectorCorridorViolations: int.MaxValue,
- RepeatCollectorNodeClearanceViolations: int.MaxValue,
- TargetApproachJoinViolations: int.MaxValue,
- TargetApproachBacktrackingViolations: int.MaxValue,
- ExcessiveDetourViolations: int.MaxValue,
- SharedLaneViolations: int.MaxValue,
- BoundarySlotViolations: int.MaxValue,
- BelowGraphViolations: int.MaxValue,
- UnderNodeViolations: int.MaxValue,
- LongDiagonalViolations: int.MaxValue,
- ProximityViolations: int.MaxValue,
- EntryAngleViolations: int.MaxValue,
- GatewaySourceExitViolations: int.MaxValue,
- LabelProximityViolations: int.MaxValue,
- EdgeCrossings: int.MaxValue);
+ var lastAttemptRetryState = BuildMaxRetryState();
var lastAttemptNodeCrossings = int.MaxValue;
var consecutiveNonImprovingAttempts = 0;
var strategyStopwatch = Stopwatch.StartNew();
@@ -147,6 +99,7 @@ internal static partial class ElkEdgeRouterIterative
return value;
}
+ // Phase 1: Route or repair edges.
RepairPlan? repairPlan = null;
RouteAllEdgesResult? routeResult;
if (attempt == 0 || repairSeedEdges is null || repairSeedScore is null)
@@ -217,6 +170,7 @@ internal static partial class ElkEdgeRouterIterative
break;
}
+ // Phase 2: Post-process routed edges.
var candidateEdges = routeResult.Edges;
var scopedCleanupEdgeIds = routeResult.Diagnostics?.Mode == "local-repair"
? routeResult.Diagnostics.RepairedEdgeIds.ToArray()
@@ -265,6 +219,7 @@ internal static partial class ElkEdgeRouterIterative
strategy.MinLineClearance));
}
+ // Phase 3: Score and verify.
var score = MeasurePhase(
"compute-score",
() => ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes));
@@ -350,68 +305,26 @@ internal static partial class ElkEdgeRouterIterative
repairSeedEdges = candidateEdges;
repairSeedRetryState = retryState;
- var repairFocusFingerprint = BuildRepairFocusFingerprint(repairPlan);
- if (!string.IsNullOrEmpty(repairFocusFingerprint))
+ // Phase 4: Stagnation detection.
+ var stagnationReason = DetectStrategyStagnation(
+ repairPlan,
+ retryState,
+ bestAttemptRetryState,
+ attempt,
+ ref lastRepairFocusFingerprint,
+ ref repeatedRepairFocusCount,
+ ref lastPlateauFingerprint,
+ ref repeatedPlateauFingerprintCount,
+ recentBlockingCycleFingerprints);
+ if (stagnationReason is not null)
{
- if (string.Equals(repairFocusFingerprint, lastRepairFocusFingerprint, StringComparison.Ordinal))
- {
- repeatedRepairFocusCount++;
- }
- else
- {
- lastRepairFocusFingerprint = repairFocusFingerprint;
- repeatedRepairFocusCount = 0;
- }
-
- if (repeatedRepairFocusCount >= 1 && attempt >= 3)
- {
- outcome = $"stalled-same-focus({DescribeRetryState(retryState)})@attempt{attempt + 1}";
- ElkLayoutDiagnostics.LogProgress(
- $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
- break;
- }
- }
- else
- {
- lastRepairFocusFingerprint = null;
- repeatedRepairFocusCount = 0;
- }
-
- var plateauFingerprint = BuildPlateauFingerprint(retryState, repairPlan);
- if (string.Equals(plateauFingerprint, lastPlateauFingerprint, StringComparison.Ordinal))
- {
- repeatedPlateauFingerprintCount++;
- }
- else
- {
- lastPlateauFingerprint = plateauFingerprint;
- repeatedPlateauFingerprintCount = 0;
- }
-
- if (repeatedPlateauFingerprintCount >= 2 && attempt >= 3)
- {
- outcome = $"stalled-repeat({DescribeRetryState(retryState)})@attempt{attempt + 1}";
+ outcome = stagnationReason;
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
break;
}
- var blockingCycleFingerprint = BuildBlockingCycleFingerprint(retryState, repairPlan);
- if (ShouldStopForBlockingCycle(
- recentBlockingCycleFingerprints,
- blockingCycleFingerprint,
- retryState,
- bestAttemptRetryState,
- attempt))
- {
- outcome = $"stalled-cycle({DescribeRetryState(retryState)})@attempt{attempt + 1}";
- ElkLayoutDiagnostics.LogProgress(
- $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
- break;
- }
-
- AppendRecentFingerprint(recentBlockingCycleFingerprints, blockingCycleFingerprint, 4);
-
+ // Phase 5: Track improvement.
if (hasLastAttemptState)
{
var retryStateComparison = CompareRetryStates(retryState, lastAttemptRetryState);
@@ -444,11 +357,7 @@ internal static partial class ElkEdgeRouterIterative
lastAttemptNodeCrossings = score.NodeCrossings;
var improvedAttempt = bestAttemptScore is null
- || IsBetterBoundarySlotRepairCandidate(
- score,
- retryState,
- bestAttemptScore.Value,
- bestAttemptRetryState);
+ || IsBetterBoundarySlotRepairCandidate(score, retryState, bestAttemptScore.Value, bestAttemptRetryState);
var improvedRuleState = bestAttemptScore is null
|| (retryState.BoundarySlotViolations < bestAttemptRetryState.BoundarySlotViolations
&& score.NodeCrossings <= bestAttemptScore.Value.NodeCrossings
@@ -460,36 +369,24 @@ internal static partial class ElkEdgeRouterIterative
bestAttemptScore = score;
bestAttemptEdges = candidateEdges;
bestAttemptRetryState = retryState;
- stagnantAttempts = improvedRuleState
- ? 0
- : stagnantAttempts + 1;
- if (ShouldStopForStagnation(stagnantAttempts, attempt, config.MaxAdaptationsPerStrategy))
- {
- outcome = $"stalled({DescribeRetryState(retryState)})@attempt{attempt + 1}";
- ElkLayoutDiagnostics.LogProgress(
- $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
- break;
- }
+ stagnantAttempts = improvedRuleState ? 0 : stagnantAttempts + 1;
}
else
{
stagnantAttempts++;
- if (ShouldStopForStagnation(stagnantAttempts, attempt, config.MaxAdaptationsPerStrategy))
- {
- outcome = $"stalled({DescribeRetryState(retryState)})@attempt{attempt + 1}";
- ElkLayoutDiagnostics.LogProgress(
- $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
- break;
- }
}
- var attemptOutcome = score.NodeCrossings > maxAllowedNodeCrossings
- ? $"hard-violation(nc={score.NodeCrossings}>{maxAllowedNodeCrossings})"
- : retryState.RequiresPrimaryRetry
- ? $"retry({DescribeRetryState(retryState)})"
- : ShouldRetryForEdgeCrossings(retryState, attempt, config.MaxAdaptationsPerStrategy)
- ? $"retry(edge-crossings={retryState.EdgeCrossings})"
- : "valid";
+ if (ShouldStopForStagnation(stagnantAttempts, attempt, config.MaxAdaptationsPerStrategy))
+ {
+ outcome = $"stalled({DescribeRetryState(retryState)})@attempt{attempt + 1}";
+ ElkLayoutDiagnostics.LogProgress(
+ $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
+ break;
+ }
+
+ // Phase 6: Retry decision.
+ var (attemptOutcome, shouldContinue, isValid) = DecideStrategyAttemptOutcome(
+ score, retryState, maxAllowedNodeCrossings, attempt, maxAttempts, workItem, strategy);
if (diagnostics is not null)
{
@@ -521,87 +418,26 @@ internal static partial class ElkEdgeRouterIterative
}
}
- if (score.NodeCrossings > maxAllowedNodeCrossings)
+ if (shouldContinue)
{
- outcome = $"{attemptOutcome}@attempt{attempt + 1}";
+ outcome = attemptOutcome;
ElkLayoutDiagnostics.LogProgress(
- $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting after node crossing violation");
- strategy.AdaptForViolations(score, attempt, retryState);
+ $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting");
continue;
}
- if (retryState.RemainingShortHighways > 0
- || retryState.RepeatCollectorCorridorViolations > 0
- || retryState.RepeatCollectorNodeClearanceViolations > 0
- || retryState.TargetApproachJoinViolations > 0
- || retryState.TargetApproachBacktrackingViolations > 0
- || retryState.SharedLaneViolations > 0
- || retryState.BelowGraphViolations > 0
- || retryState.UnderNodeViolations > 0
- || retryState.LongDiagonalViolations > 0
- || retryState.EntryAngleViolations > 0
- || retryState.GatewaySourceExitViolations > 0)
+ if (isValid)
{
- if (ShouldRetryForPrimaryViolations(retryState, attempt, config.MaxAdaptationsPerStrategy))
- {
- outcome = $"{attemptOutcome}@attempt{attempt + 1}";
- ElkLayoutDiagnostics.LogProgress(
- $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting for blocking violations");
- strategy.AdaptForViolations(score, attempt, retryState);
- continue;
- }
-
- outcome = $"invalid({DescribeRetryState(retryState)})@attempt{attempt + 1}";
+ outcome = attemptOutcome;
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
+ validSolution = candidate;
break;
}
- if (retryState.RequiresLengthRetry)
- {
- if (ShouldRetryForPrimaryViolations(retryState, attempt, config.MaxAdaptationsPerStrategy))
- {
- outcome = $"{attemptOutcome}@attempt{attempt + 1}";
- ElkLayoutDiagnostics.LogProgress(
- $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting for shortest-path / detour violations");
- strategy.AdaptForViolations(score, attempt, retryState);
- continue;
- }
-
- outcome = $"invalid({DescribeRetryState(retryState)})@attempt{attempt + 1}";
- ElkLayoutDiagnostics.LogProgress(
- $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
- break;
- }
-
- if (retryState.RequiresQualityRetry)
- {
- if (ShouldRetryForPrimaryViolations(retryState, attempt, config.MaxAdaptationsPerStrategy))
- {
- outcome = $"{attemptOutcome}@attempt{attempt + 1}";
- ElkLayoutDiagnostics.LogProgress(
- $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting for quality/length violations");
- strategy.AdaptForViolations(score, attempt, retryState);
- continue;
- }
- }
-
- if (ShouldRetryForEdgeCrossings(retryState, attempt, config.MaxAdaptationsPerStrategy))
- {
- outcome = $"{attemptOutcome}@attempt{attempt + 1}";
- ElkLayoutDiagnostics.LogProgress(
- $"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] adapting for edge crossings");
- strategy.AdaptForViolations(score, attempt, retryState);
- continue;
- }
-
- var residualSoftViolations = retryState.RequiresQualityRetry || retryState.EdgeCrossings > 0;
- outcome = residualSoftViolations
- ? $"valid-soft({DescribeRetryState(retryState)})@attempt{attempt + 1}"
- : $"valid@attempt{attempt + 1}";
+ outcome = attemptOutcome;
ElkLayoutDiagnostics.LogProgress(
$"Strategy {workItem.StrategyIndex} [{workItem.StrategyName}] {outcome}");
- validSolution = candidate;
break;
}