Split clearance: node-size for face detections, spacing-scaled for routing

Target-join and boundary-slot detection now use ResolveNodeSizeClearance
(node dimensions only), while under-node/proximity use
ResolveMinLineClearance (scales with NodeSpacing via ElkLayoutClearance).

Face slot gaps depend on node face geometry, not inter-node spacing.
Routing corridors should scale with spacing for visual breathing room.

Created sprint 008 for wider spacing robustness. NodeSpacing=50 still
fails on target-join (scoring/test detection mismatch needs investigation).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-01 17:15:24 +03:00
parent 1ad77a4f8e
commit fafcadbc9a
4 changed files with 64 additions and 2 deletions

View File

@@ -17,7 +17,9 @@ internal static partial class ElkEdgeRoutingScoring
{
var edgesById = edges.ToDictionary(edge => edge.Id, StringComparer.Ordinal);
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
var minClearance = ResolveMinLineClearance(nodes);
// Use node-size clearance for boundary-slot gaps: slot spacing depends
// on node face geometry, not inter-node spacing.
var minClearance = ResolveNodeSizeClearance(nodes);
var coordinateTolerance = Math.Max(1d, Math.Min(6d, minClearance * 0.2d));
var entries = new List<(string EdgeId, ElkPositionedNode Node, string Side, double Coordinate, bool IsOutgoing)>();

View File

@@ -15,7 +15,9 @@ internal static partial class ElkEdgeRoutingScoring
Dictionary<string, int>? severityByEdgeId,
int severityWeight = 1)
{
var minClearance = ResolveMinLineClearance(nodes);
// Use node-size clearance for target-join gaps: face slot spacing
// depends on node face geometry, not inter-node spacing.
var minClearance = ResolveNodeSizeClearance(nodes);
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
var count = 0;

View File

@@ -239,6 +239,16 @@ internal static partial class ElkEdgeRoutingScoring
return overrideClearance;
}
return ResolveNodeSizeClearance(nodes);
}
/// <summary>
/// Node-size-based clearance (independent of NodeSpacing). Used for
/// face-related detections (target-join, boundary-slot) where the gap
/// depends on node face geometry, not inter-node routing corridors.
/// </summary>
private static double ResolveNodeSizeClearance(IReadOnlyCollection<ElkPositionedNode> nodes)
{
var serviceNodes = nodes.Where(n => n.Kind is not "Start" and not "End").ToArray();
return serviceNodes.Length > 0
? Math.Min(serviceNodes.Average(n => n.Width), serviceNodes.Average(n => n.Height)) / 2d