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 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)>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user