Fix fake orthogonal target-entry hook detection

This commit is contained in:
master
2026-03-26 14:39:55 +02:00
parent c210115224
commit 2bc06169f8
3 changed files with 1368 additions and 2 deletions

View File

@@ -174,9 +174,11 @@ Completion criteria:
| 2026-03-25 | Reopened ElkSharp follow-up work for the user-reported source-side same-lane conflict between `Internal Notification -> Has Recipients` and `Internal Notification -> Set internalNotificationFailed`. Added source-departure join spreading plus blocking `SharedLaneViolations`, derived placement spacing from an average-node-size placement grid, and verified the focused helper regression with `dotnet test src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/StellaOps.Workflow.Renderer.Tests.csproj --no-restore --filter "FullyQualifiedName~SourceDepartureHelpers_WhenOutgoingEdgesShareTheSameDepartureLane_ShouldSpreadOnlyTheConflictingPeer" -v minimal` (1/1). The current document-processing artifact now reports `SharedLaneViolations=0` for the selected route, but still has unresolved late boundary-angle / target-join regressions (`EntryAngleViolations=6`, `TargetApproachJoinViolations=1`), so TASK-010 remains open. | Implementer |
| 2026-03-25 | Tightened the iterative local-repair planner so attempt 2+ now selects only currently failing edges plus exact conflict peers instead of padding the repair set with generic ranked edges, and added a lock-aware parallel local builder that computes candidates concurrently but serializes overlapping source/target neighborhoods before merging deterministically. Revalidated with `dotnet build src/__Libraries/StellaOps.ElkSharp/StellaOps.ElkSharp.sln -v minimal` and `dotnet test src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/StellaOps.Workflow.Renderer.Tests.csproj --no-restore --filter "FullyQualifiedName~DocumentProcessingWorkflow_WhenRenderedWithElkSharp_ShouldProducePngWithZeroNodeCrossings" -v minimal` (still failing in ~20s on `SharedLaneViolations=1`, `UnderNodeViolations=2`; selected offender cluster remains `edge/15+edge/17`, `edge/9`, `edge/15`). | Implementer |
| 2026-03-26 | Cleared the last document-processing handoff by letting gateway target peer-conflict candidates start from slotted feeder paths and reusing focused target-peer conflict polish during transactional final-detour repair. Revalidated with `dotnet test src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/StellaOps.Workflow.Renderer.Tests.csproj --no-restore --filter "FullyQualifiedName~Debug_DumpDocumentProcessingFinalDetourOffenders" -v normal --logger "console;verbosity=normal"` (1/1) and `dotnet test src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/StellaOps.Workflow.Renderer.Tests.csproj --no-restore --filter "FullyQualifiedName~DocumentProcessingWorkflow_WhenRenderedWithElkSharp_ShouldProducePngWithZeroNodeCrossings" -v minimal` (1/1, 2m42s). | Implementer |
| 2026-03-26 | Tightened non-gateway target backtracking so a short orthogonal hook that reaches the boundary for less than one node depth is treated as a forbidden fake side entry, then added a focused `Load Configuration -> Setting configParameters` regression. | Implementer |
## Decisions & Risks
- 2026-03-26: The remaining document-processing defect was not another retry-budget issue. Gateway target peer-conflict candidate building needed a slotted feeder so focused peer-conflict polish could separate same-face arrivals without restoring the final excessive detour.
- 2026-03-26 follow-up: the `Load Configuration -> Setting configParameters` edge exposed a different blind spot. The rectangular target-entry rule only validated the final boundary angle and true overshoot, so a `6px` vertical stub into the bottom face still passed as a valid `90`-degree entry. Non-gateway target backtracking now also rejects short orthogonal hooks whose final boundary-depth is less than one node shape depth, which lets the existing backtracking endpoint normalizer move the edge onto the honest side face instead of preserving the fake bottom join.
- 2026-03-25 follow-up: the selected document-processing artifact now enforces zero below-graph lanes and zero overlong 45-degree segments, and gateway source exits are no longer allowed to leave from fork/join tip vertices. Gateway target join detection/spreading now groups arrivals by their landed boundary band instead of letting gateway arrivals slip through as highway-like exemptions. Targeted evidence: `dotnet test src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/StellaOps.Workflow.Renderer.Tests.csproj --no-restore --filter "FullyQualifiedName~DocumentProcessingWorkflow_WhenRenderedWithElkSharp_ShouldProducePngWithZeroNodeCrossings" -v minimal` (1/1 pass, refreshed 20260325 artifact). That checkpoint still left TASK-010 open; the 2026-03-26 peer-conflict fix closes it.
- There was no module-local `AGENTS.md` under `src/__Libraries/StellaOps.ElkSharp/`; this sprint adds one before code changes so the module is no longer undocumented.
- Cross-module edits are limited to workflow renderer tests and workflow engine docs because the implementation changes a shared library used by those surfaces.

View File

@@ -9647,6 +9647,11 @@ internal static class ElkEdgePostProcessor
}
const double tolerance = 0.5d;
if (HasShortOrthogonalTargetHook(path, targetNode, side, tolerance))
{
return true;
}
var startIndex = Math.Max(0, path.Count - (side is "left" or "right" ? 4 : 3));
var axisValues = new List<double>(path.Count - startIndex);
for (var i = startIndex; i < path.Count; i++)
@@ -9722,6 +9727,85 @@ internal static class ElkEdgePostProcessor
return false;
}
private static bool HasShortOrthogonalTargetHook(
IReadOnlyList<ElkPoint> path,
ElkPositionedNode targetNode,
string side,
double tolerance)
{
if (path.Count < 3)
{
return false;
}
var boundaryPoint = path[^1];
var runStartIndex = path.Count - 2;
if (side is "left" or "right")
{
while (runStartIndex > 0 && Math.Abs(path[runStartIndex - 1].Y - boundaryPoint.Y) <= tolerance)
{
runStartIndex--;
}
}
else
{
while (runStartIndex > 0 && Math.Abs(path[runStartIndex - 1].X - boundaryPoint.X) <= tolerance)
{
runStartIndex--;
}
}
if (runStartIndex == 0)
{
return false;
}
var overallDeltaX = path[^1].X - path[0].X;
var overallDeltaY = path[^1].Y - path[0].Y;
var overallAbsDx = Math.Abs(overallDeltaX);
var overallAbsDy = Math.Abs(overallDeltaY);
var sameRowThreshold = Math.Max(24d, targetNode.Height / 3d);
var sameColumnThreshold = Math.Max(24d, targetNode.Width / 3d);
var looksHorizontal = overallAbsDx >= overallAbsDy * 1.15d
&& overallAbsDy <= sameRowThreshold
&& Math.Sign(overallDeltaX) != 0;
var looksVertical = overallAbsDy >= overallAbsDx * 1.15d
&& overallAbsDx <= sameColumnThreshold
&& Math.Sign(overallDeltaY) != 0;
var contradictsDominantApproach = side switch
{
"left" or "right" => looksVertical,
"top" or "bottom" => looksHorizontal,
_ => false,
};
if (!contradictsDominantApproach)
{
return false;
}
var runStart = path[runStartIndex];
var boundaryDepth = side is "left" or "right"
? Math.Abs(boundaryPoint.X - runStart.X)
: Math.Abs(boundaryPoint.Y - runStart.Y);
var requiredDepth = side is "left" or "right"
? targetNode.Width
: targetNode.Height;
if (boundaryDepth + tolerance >= requiredDepth)
{
return false;
}
var predecessor = path[runStartIndex - 1];
var predecessorDx = Math.Abs(runStart.X - predecessor.X);
var predecessorDy = Math.Abs(runStart.Y - predecessor.Y);
return side switch
{
"left" or "right" => predecessorDy > predecessorDx * 3d,
"top" or "bottom" => predecessorDx > predecessorDy * 3d,
_ => false,
};
}
private static bool IsOnWrongSideOfTarget(
ElkPoint point,
ElkPositionedNode targetNode,