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:
@@ -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.
|
||||||
@@ -17,7 +17,9 @@ internal static partial class ElkEdgeRoutingScoring
|
|||||||
{
|
{
|
||||||
var edgesById = edges.ToDictionary(edge => edge.Id, StringComparer.Ordinal);
|
var edgesById = edges.ToDictionary(edge => edge.Id, StringComparer.Ordinal);
|
||||||
var nodesById = nodes.ToDictionary(node => node.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 coordinateTolerance = Math.Max(1d, Math.Min(6d, minClearance * 0.2d));
|
||||||
var entries = new List<(string EdgeId, ElkPositionedNode Node, string Side, double Coordinate, bool IsOutgoing)>();
|
var entries = new List<(string EdgeId, ElkPositionedNode Node, string Side, double Coordinate, bool IsOutgoing)>();
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ internal static partial class ElkEdgeRoutingScoring
|
|||||||
Dictionary<string, int>? severityByEdgeId,
|
Dictionary<string, int>? severityByEdgeId,
|
||||||
int severityWeight = 1)
|
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 nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
|
||||||
var count = 0;
|
var count = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -239,6 +239,16 @@ internal static partial class ElkEdgeRoutingScoring
|
|||||||
return overrideClearance;
|
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();
|
var serviceNodes = nodes.Where(n => n.Kind is not "Start" and not "End").ToArray();
|
||||||
return serviceNodes.Length > 0
|
return serviceNodes.Length > 0
|
||||||
? Math.Min(serviceNodes.Average(n => n.Width), serviceNodes.Average(n => n.Height)) / 2d
|
? Math.Min(serviceNodes.Average(n => n.Width), serviceNodes.Average(n => n.Height)) / 2d
|
||||||
|
|||||||
Reference in New Issue
Block a user