Fidelity
- Commit
- 2a743bf · 2026-10-19
- Generated
- 2026-10-19 13:58:24 UTC
- Source
- Measured: gv-diff e2e run under Apple and portable math on Darwin arm64, matching the committed baselines
- Reference
- C Graphviz 16.1.0, no Pango plugin
The port is checked against C Graphviz 16.1.0 in four ways, from the strictest to the broadest. All the corpus checks run in cargo test --workspace as ratchets: each has a committed baseline listing the files that match, and a file that stops matching fails the build. The numbers on this page are read from those runs and baselines by scripts/site-data.py; none are typed in by hand, except where marked.
Two math profiles are reported. macOS uses Apple’s libm, as C does on the reference machine. Linux and WASM use a portable math library, because Apple’s pow, sin, cos and atan2 are closed source and not reproduced bit for bit there; Linux on aarch64, Linux on x86_64 and the WASM build give the same bytes as each other (why).
Full pipeline
Section titled “Full pipeline”gv-diff e2e runs the whole port, parse to layout to render, on every input of nine corpora and compares its -Tsvg output with C’s. Identical means equal bytes, after dropping the one line that starts with <!-- Generated by , which names the program and its version. Nothing else is normalised.
Only hashes of C’s output are committed (tests/golden/e2e.json, 64-bit FNV-1a of the normalised SVG), so the check runs without C installed. Each C reference was produced twice; inputs whose C output differed between the two runs are recorded as unstable and skipped.
| Corpus | Inputs | Files | macOS | Linux, WASM | C unstable, skipped |
|---|---|---|---|---|---|
| main | tests/wild, tests/fixtures | 565 | 565 | 564 | · |
| neato | tests/neato | 442 | 442 | 442 | · |
| sfdp | tests/sfdp | 297 | 297 | 183 | · |
| circo | tests/circo | 280 | 280 | 279 | · |
| twopi | tests/twopi | 281 | 281 | 281 | 8 |
| fdp | tests/fdp | 382 | 382 | 379 | 4 |
| osage | tests/osage | 180 | 180 | 180 | · |
| patchwork | tests/patchwork | 141 | 141 | 141 | · |
| render | tests/render | 21 | 21 | 21 | · |
| Total | 2,589 | 2,589 | 2,470 | 12 |
Each patch below is one file. The macOS sheet is solid; switch to the portable profile to see where Apple’s libm matters: almost all of it in sfdp, whose multilevel layout amplifies a last-bit difference in pow into a different drawing.
The 119 files that differ under portable math
- circo/wild_gv16_undirected_ngk10_4_793340
- fdp/wild_gv16_graphs_b15_8c212e
- fdp/wild_gv16_graphs_b51_33bcb0
- fdp/wild_gv16_graphs_ldbxtried_9e83ae
- main/gallery_beautify_1_1cfb90
- sfdp/attr_beautify_tree
- sfdp/attr_label_scheme_1
- sfdp/attr_label_scheme_2
- sfdp/attr_label_scheme_3
- sfdp/attr_label_scheme_4
- sfdp/attr_label_scheme_4_overlap
- sfdp/attr_levels_0
- sfdp/attr_levels_1
- sfdp/attr_levels_2
- sfdp/attr_normalize
- sfdp/attr_overlap_false_grid
- sfdp/attr_overlap_false_labels
- sfdp/attr_quadtree_none
- sfdp/attr_quadtree_normal
- sfdp/attr_ratio_fill
- sfdp/attr_repulsiveforce_2
- sfdp/attr_repulsiveforce_2_grid
- sfdp/attr_repulsiveforce_3
- sfdp/attr_rotation_-45
- sfdp/attr_rotation_30
- sfdp/attr_rotation_90
- sfdp/attr_size
- sfdp/attr_start_7
- sfdp/attr_start_random42
- sfdp/fx_ER
- sfdp/fx_Heawood
- sfdp/fx_NaN
- sfdp/fx_Petersen
- sfdp/fx_abstract
- sfdp/fx_alf
- sfdp/fx_arrows
- sfdp/fx_clust3
- sfdp/fx_pack
- sfdp/fx_record
- sfdp/fx_unix
- sfdp/fx_world
- sfdp/gen_grid10x10
- sfdp/gen_grid20x20
- sfdp/gen_grid8x40
- sfdp/gen_powerlaw1000
- sfdp/gen_powerlaw300
- sfdp/gen_rnd100
- sfdp/gen_rnd2000
- sfdp/gen_rnd300
- sfdp/gen_rnd800
- sfdp/gen_tree4_ternary
- sfdp/gen_tree7
- sfdp/gen_tree9
- sfdp/hw_C60
- sfdp/hw_K5
- sfdp/hw_K8_box
- sfdp/hw_disconnected
- sfdp/hw_disconnected_pack20
- sfdp/hw_disconnected_packarray
- sfdp/hw_labels
- sfdp/hw_loops
- sfdp/hw_petersen
- sfdp/hw_strict_multi
- sfdp/hw_weights
- sfdp/wild_gv16_directed_arrows_54d1cf
- sfdp/wild_gv16_directed_crazy_982d4d
- sfdp/wild_gv16_directed_oldarrows_f70da3
- sfdp/wild_gv16_graphs_ER_1359e3
- sfdp/wild_gv16_graphs_Heawood_771643
- sfdp/wild_gv16_graphs_Petersen_8a008f
- sfdp/wild_gv16_graphs_arrows_148b6b
- sfdp/wild_gv16_graphs_arrowsize_46ccd8
- sfdp/wild_gv16_graphs_b34_ca01c7
- sfdp/wild_gv16_graphs_b56_36f937
- sfdp/wild_gv16_graphs_b69_4b5b48
- sfdp/wild_gv16_graphs_badvoro_2e8cb8
- sfdp/wild_gv16_graphs_clover_ae8650
- sfdp/wild_gv16_graphs_crazy_cfdc7a
- sfdp/wild_gv16_graphs_d_3a4a40
- sfdp/wild_gv16_graphs_dpd_223cb3
- sfdp/wild_gv16_graphs_ldbxtried_9e83ae
- sfdp/wild_gv16_graphs_newarrows_769825
- sfdp/wild_gv16_graphs_ngk10_4_6aab96
- sfdp/wild_gv16_graphs_p2_99819c
- sfdp/wild_gv16_graphs_p4_849964
- sfdp/wild_gv16_graphs_pack_1c2410
- sfdp/wild_gv16_graphs_pm2way_85dfc3
- sfdp/wild_gv16_graphs_pmpipe_2cd2b2
- sfdp/wild_gv16_graphs_proc3d_a79c48
- sfdp/wild_gv16_graphs_triedds_2395ab
- sfdp/wild_gv16_graphs_viewport_651d5b
- sfdp/wild_gv16_linux_x86_arrows_dot_1ce23f
- sfdp/wild_gv16_linux_x86_edgeclip_dot_17e57b
- sfdp/wild_gv16_linux_x86_html2_dot_3ee3fc
- sfdp/wild_gv16_linux_x86_pack_neato1_0327de
- sfdp/wild_gv16_linux_x86_pack_neato2_dc0918
- sfdp/wild_gv16_linux_x86_pack_neato_c0395a
- sfdp/wild_gv16_linux_x86_ports_dot_99eb47
- sfdp/wild_gv16_linux_x86_rankdir_dot1_8f7d7d
- sfdp/wild_gv16_linux_x86_rankdir_dot2_810f01
- sfdp/wild_gv16_linux_x86_rankdir_dot_f06bd6
- sfdp/wild_gv16_linux_x86_record_dot_dd3ea1
- sfdp/wild_gv16_linux_x86_rowcolsep_dot1_2c201c
- sfdp/wild_gv16_linux_x86_rowcolsep_dot_0ad7dd
- sfdp/wild_gv16_nshare_html2_dot_7bea59
- sfdp/wild_gv16_nshare_neatosplines_neato1_9c01b5
- sfdp/wild_gv16_nshare_neatosplines_neato_7ac8ee
- sfdp/wild_gv16_nshare_overlap_neato_afd9ce
- sfdp/wild_gv16_nshare_pack_neato1_014679
- sfdp/wild_gv16_nshare_pack_neato2_10690f
- sfdp/wild_gv16_nshare_pack_neato_e0a2bb
- sfdp/wild_gv16_nshare_ports_dot_25dc96
- sfdp/wild_gv16_nshare_rankdir_dot1_228ae0
- sfdp/wild_gv16_nshare_rankdir_dot2_920cb4
- sfdp/wild_gv16_nshare_rankdir_dot_0e4e43
- sfdp/wild_gv16_nshare_record_dot_f557a8
- sfdp/wild_wild_graphs_arrows_a4826e
- sfdp/wild_wild_graphs_arrowsize_ccef6e
- sfdp/wild_wild_graphs_complex_edges_71191c
The 12 inputs skipped because C's output changes between two runs
- fdp/wild_gv16_graphs_badvoro_2e8cb8
- fdp/wild_gv16_graphs_ngk10_4_6aab96
- fdp/wild_gv16_infosrc_plugins_1a797f
- fdp/wild_gv16_undirected_ngk10_4_793340
- twopi/fx_arrows
- twopi/wild_gv16_directed_oldarrows_f70da3
- twopi/wild_gv16_graphs_arrows_148b6b
- twopi/wild_gv16_graphs_arrowsize_46ccd8
- twopi/wild_gv16_graphs_newarrows_769825
- twopi/wild_gv16_linux_x86_arrows_dot_1ce23f
- twopi/wild_wild_graphs_arrows_a4826e
- twopi/wild_wild_graphs_arrowsize_ccef6e
Is it only the math library?
Section titled “Is it only the math library?”Yes. Running C itself with the port’s portable math functions (scripts/dot-portable-libm.sh loads them into the unmodified C dot) and comparing again:
| vs stock C on macOS | vs C with the port’s math functions | |
|---|---|---|
| Linux and WASM build | 2,470 / 2,589 | 2,590 / 2,590 |
| C Graphviz built on Linux, for comparison | 2,005 / 2,586 | — |
With the same math functions on both sides, the browser build is byte-identical to C on every stable input; a ratchet in CI keeps it that way. C itself is less consistent across platforms than the port: built on Linux the way Homebrew builds it on macOS, it matches macOS C on 77.5% of the corpus, against 95.4% for the port in the browser. The details, and what makes Linux C differ (glibc’s math, qsort and rand), are in Math off macOS.
Layout
Section titled “Layout”gv-diff (without e2e) compares geometry rather than bytes: it lays out each input with the port, reads C’s -Tdot output for the same input, and checks the bounding box, every node’s centre and size, every edge’s spline, arrow endpoints and label positions, and every cluster box. A file is exact when every element is within tolerance: 1 pt for node positions, sizes and bounding boxes, 2 pt for edge splines (sampled Hausdorff distance) and labels. This is looser than byte identity, and it covers the per-engine corpora with C goldens kept as .dot files (tests/golden/<engine>/).
| Corpus | Files | macOS exact | Linux, WASM exact | Not exact (macOS) |
|---|---|---|---|---|
| main | 565 | 565 | 564 | · |
| neato | 442 | 442 | 442 | · |
| sfdp | 297 | 297 | 208 | · |
| circo | 280 | 280 | 279 | · |
| twopi | 289 | 282 | 281 | fx_arrowswild_gv16_directed_oldarrows_f70da3wild_gv16_graphs_arrowsize_46ccd8wild_gv16_graphs_newarrows_769825wild_gv16_linux_x86_arrows_dot_1ce23fwild_wild_graphs_arrows_a4826ewild_wild_graphs_arrowsize_ccef6e |
| fdp | 386 | 386 | 382 | · |
| osage | 180 | 180 | 180 | · |
| patchwork | 141 | 141 | 141 | · |
The 7 twopi files are a floating-point tie in edge routing that C itself breaks differently from run to run, so no fixed reference can match every C. The full-pipeline check skips the same inputs as unstable.
Renderers
Section titled “Renderers”gv-diff render takes C’s own layouts (tests/golden/render/laid/*.dot) and renders them with the port in no-layout mode (-Kneato -n2), next to C’s renderings of the same layouts. Any difference comes from the output code (or node sizing, which -n2 recomputes). Identity is byte for byte, generator comment aside.
| Format | Files | macOS identical | Linux, WASM identical | Skipped | Differ |
|---|---|---|---|---|---|
-Tsvg | 547 | 547 | 547 | · | · |
-Txdot | 547 | 547 | 547 | · | · |
-Tdot | 547 | 547 | 547 | · | · |
-Tjson | 511 | 511 | 511 | · | · |
-Tjson0 | 547 | 547 | 547 | · | · |
-Tplain | 537 | 535 | 535 | 10 | gv16_graphs_Latin1_cd0c3dgv16_graphs_b60_bf5b3a |
-Tplain-ext | 537 | 535 | 535 | 10 | gv16_graphs_Latin1_cd0c3dgv16_graphs_b60_bf5b3a |
The skipped -Tplain and -Tplain-ext references are ones C does not reproduce from run to run: for some converted labels C prints a string after lib/common/output.c has freed it, so the field holds whatever is left in that memory. The two that differ hit the same path; on the reference machine C prints an empty label there, while the port prints the label.
C test graphs
Section titled “C test graphs”scripts/dot-broad-validate.sh lays out every graph in the Graphviz 16.1.0 source tree (graphs/, tests/graphs/, tests/regression_tests/) with C and with the port, in five variants, and gv-diff pairs compares the layouts as above.
| Variant | Flags | Exact | Compared | Note |
|---|---|---|---|---|
| base | · | 379 | 379 | |
| LR | -Grankdir=LR | 379 | 379 | |
| ortho | -Gsplines=ortho | 379 | 379 | C needs over 60 s on 3 inputs; given longer, they match too |
| concentrate | -Gconcentrate=true | 379 | 379 | |
| newrank | -Gnewrank=true | 379 | 379 |
These figures are carried over from the README: they need the Graphviz source tree and the C reference on the machine, so scripts/site-data.py does not regenerate them.
Fuzzing
Section titled “Fuzzing”The corpora only contain inputs someone thought of. scripts/fuzz-gen.py generates random graphs from a seed (engine, size, topology, clusters, rank sets, attributes from the whole Graphviz table, and about 8% malformed input), and scripts/fuzz-run.py runs C and the port on each and compares stdout (generator comment aside) and exit status. Every difference was minimised and fixed against the C source, round by round; one reproducer per bug went into the corpus.
Identity is the share of comparable cases whose output and exit status both match C. Cases where C’s output changes between two runs, C times out, or C crashes are excluded.
| Round | Seeds | Cases | dot | neato | fdp | sfdp | twopi | circo | osage | patchwork | All |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 (pilot) | 1-200 | 200 | 50.8 | 69.2 | 54.2 | 40.0 | 71.4 | 62.5 | 85.7 | 26.3 | 55.2 |
| 1 | 1-2000 | 2,000 | 88.5 | 92.0 | 81.9 | 94.3 | 91.3 | 95.5 | 91.2 | 99.4 | 90.9 |
| 2 | 10001-13000 | 3,000 | 96.3 | 98.9 | 95.2 | 99.2 | 99.7 | 98.6 | 100.0 | 100.0 | 98.1 |
| 3 | 20001-25000 | 5,000 | 96.8 | 98.6 | 97.3 | 99.2 | 99.6 | 99.2 | 99.8 | 100.0 | 98.4 |
| 4 | 30001-36000 | 6,000 | 98.5 | 98.8 | 96.1 | 99.8 | 99.5 | 99.5 | 99.8 | 100.0 | 98.8 |
Read from the rounds table in the fuzzing notes, which also break down the remaining differences: mostly C nondeterminism (layouts that depend on heap addresses), C undefined behaviour on inputs it reports as invalid, and a few open differences, which are tracked in What’s left.
The reference
Section titled “The reference”C Graphviz 16.1.0, run with GVBINDIR pointing at a plugin directory that holds only its core, dot and neato plugins. With no Pango text-layout plugin available, C measures text with its built-in font metric tables, as the port does. A default installation, which measures text with Pango and cairo, will not match. Set it up yourself.