neato splines=polyline: route to a compass port on the node boundary ends 0.1pt off from C's for some node widths
For an edge routed with splines=polyline (or spline) to a compass port on the head node's boundary ("10":n), the port and C produce different polylines for some node widths, even with identical node positions. In C the path from the tail is slightly bent (x drifts from 29.241 to 29.265) and in the port it is perfectly vertical; with other widths the head end point itself differs by 0.1pt in x (C at the node centre, the port 0.1pt right of it, or the reverse). splines=line agrees. The node positions agree exactly, so the difference is in routing.
Reproducer
digraph "fuzz 12614" {
layout=neato;
splines="polyline";
mode="hier";
"10" [label="Start"];
"9" -> "10":"n" [xlabel="\T", color="white"];
}
GVBINDIR=<pango-free plugins> dot -Kneato -Tsvg repro.gv # C Graphviz 16.1.0
graphviz-pure -Kneato -Tsvg repro.gv # port
C output vs port
--- c
+++ port
@@ -25,5 +25,5 @@
<title>9->10:n</title>
-<path fill="none" stroke="white" d="M29.24,-71.76C29.25,-63.74 29.26,-54.56 29.26,-47.55"/>
-<polygon fill="white" stroke="white" points="32.76,-47.81 29.27,-37.81 25.76,-47.81 32.76,-47.81"/>
-<text xml:space="preserve" text-anchor="middle" x="25.75" y="-47.06" font-family="Times,serif" font-size="14.00">9</text>
+<path fill="none" stroke="white" d="M29.28,-71.52C29.28,-63.63 29.28,-54.67 29.28,-47.76"/>
+<polygon fill="white" stroke="white" points="32.78,-47.81 29.28,-37.81 25.78,-47.81 32.78,-47.81"/>
+<text xml:space="preserve" text-anchor="middle" x="25.78" y="-47.04" font-family="Times,serif" font-size="14.00">9</text>
</g>
Root cause
Narrowed down with -n (fixed positions): head width=0.81 differs, while width=0.75, 0.8, 0.82, 0.83, 0.85, 0.9, 1.0 agree (at 0.83 C itself ends 0.1pt off-centre and the port agrees). Confirmed with lldb on Homebrew's dot (dumping the router_t triangle graph at the first Pshortestpath). The head port lies on the node boundary, so the edge goes through the GTS router (makeMultiSpline, lib/neatogen/multispline.c), not getPath/makePolyline. The two stacked octagon obstacles are mirror images, so the triangle shortest path (triPath) from the head can pass either side of the 9-bottom/10-top diagonal at the same length. C and the port build the same triangles, but GTS returns each face's vertices in a different rotation (gts_triangle_vertices, fixed by GTS's incremental construction behind tri/mkSurface, lib/neatogen/delaunay.c), and triCenter (multispline.c:391) sums them in that order. In C both head edges come out at exactly 20.005238055389295, the first inserted (left) one wins and the guiding polygon is the left sleeve. In the port the right head edge is 1 ulp shorter, so it takes the right sleeve, whose last bend is the obstacle's top corner, and tweakEnd (multispline.c:658) moves the head 0.1pt right. Matching C needs GTS's per-face vertex order (and, for other ties, its pointer-hash face order), which the port's own constrained Delaunay triangulation (mk_surface in crates/graphviz-pure-layout/src/neatogen/multispline.rs) does not reproduce. Regression test (ignored): neato_polyline_compass_port_routes_like_c in crates/graphviz-pure-cli/tests/cli.rs.
Minimal fixed-position reproducer (run with -Kneato -n -Tdot):
digraph G { splines="polyline"; "10" [width=0.81, pos="100,18"]; "9" [pos="100,90"]; "9" -> "10":"n"; }
C: pos="e,29.16,36.482 29.126,71.526 ...", port: pos="e,29.26,36.483 29.026,71.859 ...".
Cases
No fuzz case of its own. This difference appeared while minimising seed 12614. The full seed-12614 input matches C once the record-obstacle fix (neato-router-record-obstacle-height) is applied, but this minimised form still differs with that fix, so it is a separate bug.
Files
tests/fuzz-open/019-neato-polyline-compass-port-endpoint
C and the port
C onlyport onlyboth: in register
Rasterising…
C ink onlyport ink onlysame pixel, faded
Reproduce
digraph "fuzz 12614" {layout=neato;splines="polyline";mode="hier";"10" [label="Start"];"9" -> "10":"n" [xlabel="\T", color="white"];}C: GVBINDIR=<pango-free plugins> dot -Kneato -Tsvg repro.gvport: graphviz-pure -Kneato -Tsvg repro.gv(no fuzz case of its own: found while minimising r2/diff/s12614.gv seed=12614; the full case was fixed by neato-router-record-obstacle-height, #13)Files: c.svg, cases.txt, cmd.txt, repro.gv, rust.svg. Source: tests/fuzz-open/019-neato-polyline-compass-port-endpoint.