Plan-graph round 12 (owner): docking gap + straight shaft stub

Two edge-drawing refinements (owner), both pure post-processing in
edgePathD on dagre's waypoints:

- Docking gap (4 units ≈ half a head length): the head's tip floats
  short of the target face instead of touching it — the mouth pulls
  back by tip overhang + gap, so the head kisses nothing and tilted
  entries stop biting into the box.
- Shaft stub (12 units ≈ 1.5 head lengths): from Q, every control
  point sits exactly on the approach line, so the basis spline renders
  that stretch perfectly straight and the head lands on a straight
  shaft; the corner where the route squares up is rounded by the
  curve, and the repeated mouth point makes the end tangent exactly
  the approach direction. Collinear routes render identically
  (pinned: on-axis start to mouth).

Angle alignment/perpendicular entry deliberately NOT touched yet —
owner wants to evaluate this first (round 13 candidate).
This commit is contained in:
amosblomqvist
2026-09-14 07:53:54 +03:00
parent 0f6b2d5c1e
commit 847caf48d3
2 changed files with 35 additions and 9 deletions
+9 -4
View File
@@ -61,12 +61,17 @@ describe("plan block layout (pure)", () => {
it("edgePathD draws dagre's route through curveBasis — mouth pulled back from the border", () => {
// Straight 3-point route (source border → mid → target border): the
// basis curve starts exactly at the border point and the END pulls
// back by HEAD_TIP_OVERHANG so the arrowhead's TIP lands on the
// border while its mouth sits at the path's end (owner, round 11).
// basis curve starts exactly at the border point; the END pulls
// back by HEAD_TIP_OVERHANG + the docking gap (92 − 3.538 − 4) so the
// head's TIP floats 4 short of the border with its mouth at the
// path's end, on a straight shaft (owner, rounds 11–12).
const d = edgePathD([{ x: 80, y: 44 }, { x: 80, y: 68 }, { x: 80, y: 92 }]);
assert.ok(d.startsWith("M80,44"), "starts at the source border point");
assert.ok(d.endsWith("L80,88.462"), "ends pulled back by the tip overhang");
assert.ok(d.endsWith("L80,84.462"), "mouth pulled back; tip floats 4 off the border");
// The whole run stays on-axis (every x is 80): a collinear route
// renders straight — the shaft stub never bends a straight edge.
const xs = [...d.matchAll(/(?:[MLC]|,)(-?[\d.]+),/g)].map((m) => m[1]);
assert.ok(xs.length > 0 && xs.every((v) => v === "80"), "collinear route renders on-axis");
});
it("wrapTitle wraps greedily and NEVER drops content (boxes grow instead)", () => {