From fafcadbc9a183fbb3885ef527705e7300f836ffa Mon Sep 17 00:00:00 2001 From: master <> Date: Wed, 1 Apr 2026 17:15:24 +0300 Subject: [PATCH] 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) --- ...1_008_ElkSharp_wider_spacing_robustness.md | 48 +++++++++++++++++++ .../ElkEdgeRoutingScoring.BoundarySlots.cs | 4 +- .../ElkEdgeRoutingScoring.TargetJoin.cs | 4 +- .../ElkEdgeRoutingScoring.cs | 10 ++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 docs/implplan/SPRINT_20260401_008_ElkSharp_wider_spacing_robustness.md diff --git a/docs/implplan/SPRINT_20260401_008_ElkSharp_wider_spacing_robustness.md b/docs/implplan/SPRINT_20260401_008_ElkSharp_wider_spacing_robustness.md new file mode 100644 index 000000000..0b1e9663e --- /dev/null +++ b/docs/implplan/SPRINT_20260401_008_ElkSharp_wider_spacing_robustness.md @@ -0,0 +1,48 @@ +# Sprint 20260401-008 - ElkSharp Wider Spacing Robustness + +## Topic & Scope +- Make the ElkSharp document-processing artifact test pass at NodeSpacing=50 (and ideally 60) without per-spacing tuning. +- The routing pipeline currently passes all 44+ assertions only at NodeSpacing=40. Wider spacing creates different node arrangements with different edge convergence patterns (target-joins, shared lanes) that the pipeline doesn't fix. +- Working directory: `src/__Libraries/StellaOps.ElkSharp/`. +- Expected evidence: artifact test passing at NodeSpacing=50, regenerated PNG with visually clean routing. + +## Dependencies & Concurrency +- Depends on the ElkLayoutClearance infrastructure (already in place). +- Safe cross-module edits limited to: + - `src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/` + - `docs/workflow/` + +## Documentation Prerequisites +- `docs/code-of-conduct/CODE_OF_CONDUCT.md` +- `src/__Libraries/StellaOps.ElkSharp/AGENTS.md` + +## Delivery Tracker + +### TASK-001 - Make artifact test pass at NodeSpacing=50 +Status: TODO +Dependency: none +Owners: Implementer +Task description: +- Set NodeSpacing=50 in the artifact test and fix all violations that arise from the wider layout. +- Known violations at NodeSpacing=50: target-join edge/7+edge/12, shared-lane edge/7+edge/14, under-node edge/9, long-diagonal edge/11. +- The fixes should be generic (improve the pipeline for all spacings) not spacing-specific hacks. + +Completion criteria: +- [ ] Artifact test passes at NodeSpacing=50 +- [ ] Artifact test also passes at NodeSpacing=40 (no regression) +- [ ] HybridDeterministicMode 3/3 and StraightExit 2/2 still pass +- [ ] Regenerated PNG is visually reviewed + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-04-01 | Sprint created. NodeSpacing=50 violations identified: target-join edge/7+edge/12@body/5/left, shared-lane edge/7+edge/14, under-node edge/9, long-diagonal edge/11. ElkLayoutClearance infrastructure in place (clearance scales with spacing). | Implementer | +| 2026-04-01 | Split clearance into node-size-based (for face detections: target-join, boundary-slot) and spacing-scaled (for routing detections: under-node, proximity). Added `ResolveNodeSizeClearance` to scoring. Identified deeper issue: scoring's `CountTargetApproachJoinViolations` doesn't detect edge/7+edge/12 at NodeSpacing=50 but the test helper does. Likely side-resolution or detection mismatch between the two implementations. Needs investigation of `ResolveTargetApproachJoinSide` differences. | Implementer | + +## Decisions & Risks +- The routing optimizer finds different paths at different spacings. Fixes must be generic (improve pipeline robustness) not per-spacing patches. +- The ElkLayoutClearance holder ensures consistent clearance across scoring/post-processing/routing. +- Speed regression risk: wider spacing may need more iterations. + +## Next Checkpoints +- Fix violations one at a time, verifying both NodeSpacing=40 and 50 after each. diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.BoundarySlots.cs b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.BoundarySlots.cs index 6558412f5..adfed12ba 100644 --- a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.BoundarySlots.cs +++ b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.BoundarySlots.cs @@ -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)>(); diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.TargetJoin.cs b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.TargetJoin.cs index 6c1a31071..dfc7bfce5 100644 --- a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.TargetJoin.cs +++ b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.TargetJoin.cs @@ -15,7 +15,9 @@ internal static partial class ElkEdgeRoutingScoring Dictionary? 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; diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.cs b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.cs index 3f06e7940..00a1a38d3 100644 --- a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.cs +++ b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRoutingScoring.cs @@ -239,6 +239,16 @@ internal static partial class ElkEdgeRoutingScoring return overrideClearance; } + return ResolveNodeSizeClearance(nodes); + } + + /// + /// 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. + /// + private static double ResolveNodeSizeClearance(IReadOnlyCollection 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