The v1.6 roadmap in five tracks: 154 -> 247 registered tools in 28 groups. Evidence re-measured for the release: - correctness A/B against v1.5.1: 26/48 -> 48/48, 22 fixed, 0 regressed (ab-v1.5.1-vs-v1.6.0.json, replacing the -dev report); - the reference adapter on matrix v7: 21/21 (autocad-mcp-pro.json, which was the v3 run of 1.5); the published-report test follows it to v7; - the 100-point rubric re-scored per category, every withheld point named: 93 (the competitor rows are their July snapshots, not re-reviewed); - the perf lane as medians of five alternating runs, beside a v1.4.0 comparison on one interpreter (perf-v1.4.0-vs-v1.6.0.json). Two roadmap criteria are not green, and the changelog, the README and the roadmap say so: criterion 5 is withdrawn (track D, Plant 3D, dropped) and criterion 7 is not met (creation 1.25x / 1.38x v1.4.0's time, still 1.17x / 1.20x with the timeout off). Carried to 1.7. README rewritten for 1.6: what is new, the mechanical and architectural sheets, the rubric and task-matrix charts, the measured limits (including the four feature tables that ship empty), and the 1.7 roadmap. server.json describes the 1.6 surface and declares TOOL_PACKS. CLAUDE.md corrects the ISO 286 letter set and the empty feature tables. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
443 lines
26 KiB
Markdown
443 lines
26 KiB
Markdown
# Benchmarks
|
||
|
||

|
||
|
||
## Two evidence layers
|
||
|
||
This repository publishes two deliberately separate forms of evidence:
|
||
|
||
1. **Source-reviewed capability benchmark.** Nine named AutoCAD MCP projects are
|
||
scored with a fixed 100-point rubric covering CAD breadth, correctness and
|
||
delivery, backend reach, engineering production, tests, and security. The
|
||
dated data lives in [`source_review.json`](source_review.json), and the README
|
||
graphic is generated—not hand-edited—from that file.
|
||
2. **Fixed-task runtime benchmark.** Adapters execute the same task matrix and
|
||
return `pass`, `partial`, `unsupported`, `fail`, `timeout`, or `not_run`.
|
||
AutoCAD MCP Pro currently has the reference adapter; other projects do not
|
||
receive runtime scores until an adapter actually runs their public interface.
|
||
|
||
Regenerate the source-review chart:
|
||
|
||
```bash
|
||
pip install -e ".[pdf]"
|
||
python -m benchmarks.render_chart
|
||
```
|
||
|
||
The source-review chart is not presented as a shared live AutoCAD run. Review
|
||
dates, evidence grades, project URLs, and the boundary are stored with the data.
|
||
|
||
## Fixed-task runtime runner
|
||
|
||
The runner separates runtime evidence from the source-review rubric. Every
|
||
adapter receives the same tasks and produces the same closed result enum:
|
||
`pass`, `partial`, `unsupported`, `fail`, `timeout`, or `not_run`. A timeout is
|
||
reported per task, and a capability refusal is recorded `unsupported` rather
|
||
than `fail` — "cannot reach this" and "got this wrong" are different findings.
|
||
Execution is in-process, so adapters that delegate blocking work must provide
|
||
cancellation-safe task implementations or be wrapped by an external process
|
||
supervisor. Reports include commit SHA, Python/platform details, backend
|
||
capability claims, durations, coverage, and hashes for returned artifacts.
|
||
Unsupported tasks remain in the fixed-matrix denominator with score zero,
|
||
preventing partial implementations from receiving an inflated score.
|
||
|
||
```bash
|
||
python -m benchmarks.run_competitors --list --matrix v7
|
||
python -m benchmarks.run_competitors --server autocad-mcp-pro --backend ezdxf --matrix v7 --json
|
||
python -m benchmarks.run_competitors --task table_mleader --task hatch_islands --json
|
||
```
|
||
|
||
The release-machine ezdxf self-check is **21/21 (100.0)** on the v7 matrix
|
||
(`--matrix v6` … `--matrix v2` reproduce the earlier sets); the published
|
||
report is [`results/published/autocad-mcp-pro.json`](results/published/autocad-mcp-pro.json).
|
||
Repository stars and raw tool counts do not contribute to the score. Adapter
|
||
registration lives in `competitors.yaml`.
|
||
|
||
### Matrix v3 (v1.5.0) — five tasks that can fail
|
||
|
||
v2 scored this server 10/10, and that number carried no information: every task
|
||
in it exercised something the server was designed around. `tasks_v3.py` keeps
|
||
the v2 ten unchanged — their ids and weights key the published v1.4 reports —
|
||
and adds five chosen because they *can* fail. Three of them did, while being
|
||
written:
|
||
|
||
| Task | Category | Verified against |
|
||
|---|---|---|
|
||
| `tool_discovery` | discovery | six AutoCAD command names, each found in the top 3 of the 154-tool registry the harness ranks — all six came back #1 in the published run |
|
||
| `token_budget` | efficiency | 40,305 → 356 tokens advertised, against a 2,000 ceiling fixed in advance |
|
||
| `hatch_islands` | hatch | 300 filled with the island, 400 ignoring it |
|
||
| `selection_filter` | selection | window 1, crossing 2, bounding box 3, polygon 1 |
|
||
| `measure_from_handle` | measurement | 139.2699 against the 100.0 a vertex shoelace gives |
|
||
|
||
Token counts come from the offline ratio estimator and the report labels them
|
||
`ratio/v1 (estimate)`; `token_suite.py --tokenizer anthropic` counts for real.
|
||
|
||
**Two renames from the release plan, and they are the point.**
|
||
`tool_discovery_bilingual` → `tool_discovery`: a Turkish query normalizer was
|
||
written and then removed, because this repository is English throughout and the
|
||
Turkish was inferred from the author's chat language rather than required by the
|
||
product. `measurement_massprops` → `measure_from_handle`: no centroid or
|
||
moment-of-inertia tool ships.
|
||
|
||
**The competitor reports are not re-scored.** Those servers are pinned at v1.4
|
||
commits and were run against the v2 matrix; they have never been asked these
|
||
five questions. Their scores stand over the ten they ran, and the matrix chart
|
||
leaves the five new rows blank (`not run`) instead of scoring them zero — a
|
||
zero we invented would be indistinguishable from a zero we measured.
|
||
|
||
`--matrix v2` stays available so a v1.4 report can be reproduced rather than
|
||
only described, and `--publish` writes the artifact-path-free file that
|
||
`results/published/` holds.
|
||
|
||
### Matrix v4 (v1.6) — the P&ID round trip and the page-setup truth
|
||
|
||
`tasks_v4.py` keeps the v3 fifteen unchanged and adds two tasks that can each
|
||
fail on their own:
|
||
|
||
| Task | Category | Verified against |
|
||
|---|---|---|
|
||
| `pid_roundtrip` | pid | the example spec drawn through `pid_from_spec`'s code, then read back by the graph builder, which never sees the spec: 5 nodes, 4 edges, 0 dangling ends, `confidence_min` 1.0, zero critique issues, an instrument index of exactly `FIC-101` wired to `FCV-101`, and the two authored line numbers back on the two process lines while the two unnumbered lines carry sequence-built numbers from XDATA |
|
||
| `page_setup_truth` | pagesetup | `page_setup_apply("Layout1", ANSI_B landscape 1:1)` then `batch_plot` — the PDF's own `/MediaBox` must parse to 432 × 279 mm (±0.5), a size no fresh document has; then `page_setup_apply("Layout1", ISO_A3 landscape 1:1)` and `batch_plot` again — 420 × 297, the setter's `changed.size_mm` must read `[[432, 279], [420, 297]]`, the page-setup list must say `ISO_A3` / `landscape` / `[420, 297]`, and the ctb must be a known one. A setter that writes nothing fails at the first PDF (`tests/test_benchmark_v4.py` stubs it to prove that) |
|
||
|
||
The competitor reports carry no result for either, for the same reason they
|
||
carry none for the v3 five. `--matrix v3` reproduces the v1.5 set exactly.
|
||
|
||
### Matrix v5 (v1.6) — the mechanical sheet
|
||
|
||
`tasks_v5.py` keeps the v4 seventeen unchanged and adds roadmap criterion 3:
|
||
|
||
| Task | Category | Verified against |
|
||
|---|---|---|
|
||
| `mech_assembly` | mech | a flange-coupling A3 sheet built through the code the tools call — `apply_titleblock` (ISO 5457 frame with zones and marks, ISO 7200 title block), a hub drawn by `draw_part` with a full section by `add_view` and chain dimensions by `dimension_part`, a flange with its bolt-circle end view, two ISO 4014 M12x60 bolts and two ISO 4032 M12 nuts by `insert_std_part`, the ISO 7573 parts list *read off the drawing* by `extract_records` and drawn by `draw_bom_table`, and one balloon per row linked to that row's inserts by `add_balloon`. The sheet is saved, then gated on two numbers that cannot be negotiated: `run_critique(focus=None)` returns **zero** issues and `combine(validator, critique)` scores at least **90**. The focuses that fired come back in the metrics, and `tests/test_benchmark_v5.py` drops one balloon to prove the gate fails and names `mech_bom_balloon_mismatch` |
|
||
|
||
Nothing in the task tidies up after the tools: the view engine draws the ISO
|
||
128-23 centre lines of every circle it emits, so the sheet is clean because it
|
||
was drawn clean. Building it found four defects that every headless test had
|
||
missed — the headless diameter text on the wrong side of the chord, the
|
||
validator reading a fastener designation (`M12x60`) as a fake dimension, and on
|
||
the live seat a missing `MECH` layer and a parts list that could not see a
|
||
`BLOCKREFERENCE`; each is fixed with a test. The competitor reports carry no
|
||
result for it.
|
||
|
||
### Matrix v6 (v1.6) — the architectural plan
|
||
|
||
`tasks_v6.py` keeps the v5 eighteen unchanged and adds track F's evidence:
|
||
|
||
| Task | Category | Verified against |
|
||
|---|---|---|
|
||
| `arch_roundtrip` | arch | the plan of `engineering/arch/spec.py::EXAMPLE_SPEC` — a 9 × 6 m brick ring at 250 mm split by a 100 mm AAC wall, an entrance door, an interior door, two windows, a straight stair, two labelled rooms, the exterior chains and the door / window / room schedules — drawn in one transaction by `draw_plan_from_spec`, then read back without the spec: `rooms_detect` on the wall layer must find each labelled room within 0.1 % of the area its label carries **and** of the net floor computed by hand (4825 × 5750 = 27 743 750 and 3825 × 5750 = 21 993 750 mm²), `schedule_rows` must list exactly D1, D2 and W1, W2, and the saved plan is gated like `mech_assembly`: `run_critique(focus=None)` returns **zero** issues and `combine(validator, critique)` scores at least **90**. `tests/test_benchmark_v6.py` removes one room label to prove the gate fails and names `arch_room_unlabelled` |
|
||
|
||
The competitor reports carry no result for it.
|
||
|
||
### Matrix v7 (v1.6) — understanding foreign drawings
|
||
|
||
`tasks_v7.py` keeps the v6 nineteen unchanged and adds track H's evidence, both on the synthetic plant pair of `tests/fixtures/plant_pair.py` (invented names; its truth is computed from the positions it placed, never typed):
|
||
|
||
| Task | Category | Verified against |
|
||
|---|---|---|
|
||
| `takeoff_roundtrip` | plant | `scale_check(pid, layout)` must say **schematic** (room 1 is a 1.3× stretch, room 2 is rearranged: 21.8 % of the 55 tag pairs within ±10 % of the median ratio); `pipe_rows(..., length_source="auto")` must report all nine runs with the truth's status, each routable run's layout length equal to the rectilinear MST of its tags to a micron (PR1 5500, PR2 13200, CS1 20700 mm, …), its size and service as drawn — the CIP return drawn on the supply layer included — and both sizes across the reducer; `cable_rows` must give M11 / T102 / M12 / T202 their panels, 6900 / 14100 / 15800 / 8000 mm and 9 / 17 / 19 / 10 m after the 20 % allowance, the heater's power empty. `tests/test_benchmark_v7.py` deletes the wiring callouts and the gate names all four loads |
|
||
| `understand_foreign` | understand | `describe(layout)` must infer millimetres and warn about `INSUNITS` (the file says inches), name the stray line's handle in its extents and report two clusters (the plan is drawn twice, 100 m apart); `describe(pid)` must classify `P_product piping`, `P_cipsupplyline`, `P_cipreturnline`, `P_ijswater` and `E_power` as product, CIP supply, CIP return, ice water and electrical. `tests/test_benchmark_v7.py` makes the layout declare millimetres and the units gate fails alone |
|
||
|
||
The competitor reports carry no result for them.
|
||
|
||
## Live competitor lane (v1.4)
|
||
|
||
Two competitor adapters now execute the exact same task matrix, black-box over
|
||
MCP stdio, against commits pinned in `competitors.yaml`:
|
||
|
||
| Server | Matrix | Pinned | Score | Pass | Coverage |
|
||
|---|---|---|---:|---:|---:|
|
||
| autocad-mcp-pro (reference) | v7 (21) | 1.6.0 tree | 100.0 | 21/21 | 100% |
|
||
| beiming183-cloud/AutoCAD-MCP | v2 (10) | `11f7c47e` | 50.0 | 5/10 | 50% |
|
||
| puran-water/autocad-mcp | v2 (10) | `95476a33` | 45.0 | 4/10 | 50% |
|
||
|
||
The competitor rows are the v1.4 runs, unchanged. Read the matrix column
|
||
before the score column: the denominators differ because the eleven tasks
|
||
added since v2 came after those runs and have not been put to them.
|
||
|
||
Method and boundaries:
|
||
|
||
- `benchmarks/competitors_env.py` clones the pinned SHA into
|
||
`benchmarks/.competitors/<id>/` (gitignored), builds an isolated venv, and
|
||
installs the competitor from its own `pyproject.toml`.
|
||
- `benchmarks/adapters/mcp_stdio.py` drives the competitor exactly like an MCP
|
||
host (fastmcp `Client` over stdio). No competitor code is imported.
|
||
- Task playbooks call each server's **own documented tool contract**, read from
|
||
the pinned source (consolidated `operation` + payload tools; beiming's
|
||
`doc_id`/`expected_revision` discipline is honored via
|
||
`transaction(operation="context")`).
|
||
- **Verification never trusts the competitor's response**: every geometry claim
|
||
must survive `save`/`save_as_dxf` and re-opening the DXF with ezdxf inside
|
||
the harness.
|
||
- Tasks with no documented equivalent are reported `unsupported` with the
|
||
reason string; they stay in the fixed-matrix denominator at score zero.
|
||
- This lane is headless (ezdxf backends only). File-IPC / live-AutoCAD lanes
|
||
(including COM-only servers such as best-cad-mcp or daobataotie/CAD-MCP)
|
||
require a local AutoCAD session and are out of CI scope by design.
|
||
|
||
Published machine-readable reports (artifact paths sanitized to filenames)
|
||
live under [`results/published/`](results/published/); the README charts are
|
||
regenerated from them:
|
||
|
||
```bash
|
||
python -m benchmarks.run_competitors --server puran-water-autocad-mcp --backend ezdxf --matrix v2
|
||
python -m benchmarks.run_competitors --server beiming183-autocad-mcp --backend ezdxf --matrix v2
|
||
python -m benchmarks.render_live_chart # score bars
|
||
python -m benchmarks.render_matrix_chart # tasks x servers status heatmap
|
||
```
|
||
|
||
`--publish <path>` writes the same report with artifact paths reduced to
|
||
filenames, which is how `results/published/` is produced; the sha256 stays,
|
||
because that is what makes an artifact checkable.
|
||
|
||
## Headless performance lane
|
||
|
||
`perf_suite.py` measures wall time on fixed workloads through the same backend
|
||
methods the MCP tools call (server-side overhead included): 2,000 individual
|
||
line creates, a 10,000-entity build → DXF export → reopen roundtrip, a region
|
||
query over 10,000 entities, and a full premium quality pass (ISO layers,
|
||
geometry, dimensions, complete critique). The report records the machine
|
||
fingerprint; numbers move with hardware, workload definitions do not.
|
||
**Self-measurement only** — competitor servers would pay an extra stdio
|
||
serialization cost that in-process runs do not, so no cross-server timing
|
||
claims are made.
|
||
|
||
```bash
|
||
python -m benchmarks.perf_suite --out benchmarks/results/published/perf-ezdxf.json
|
||
python -m benchmarks.render_perf_chart
|
||
```
|
||
|
||
### v1.4.0 against v1.6.0, and what the call-timeout guard costs
|
||
|
||
The published v1.4 report was recorded on CPython 3.14 and the later ones on
|
||
3.11.15, so comparing those files directly shows an apparent speedup that is
|
||
almost entirely the interpreter. The comparison that means something runs one
|
||
interpreter and one ezdxf over each tree's own code: 1.6.0's `perf_suite.py`
|
||
(v1.4.0 had none) against a v1.4.0 worktree, against 1.6.0, and against 1.6.0
|
||
with the guard off — five rounds, the three configurations alternating within
|
||
a round. Medians, from
|
||
[`results/published/perf-v1.4.0-vs-v1.6.0.json`](results/published/perf-v1.4.0-vs-v1.6.0.json),
|
||
which keeps every run:
|
||
|
||
| Workload | v1.4.0 | v1.6.0 | v1.6.0, `EZDXF_CALL_TIMEOUT=0` |
|
||
|---|---:|---:|---:|
|
||
| `create_lines_2k` | 244.5 ms | 306.1 ms (1.25×) | 284.9 ms (1.17×) |
|
||
| `roundtrip_10k` | 1,847.9 ms | 2,553.5 ms (1.38×) | 2,216.4 ms (1.20×) |
|
||
| `region_query_10k` | 665.2 ms | 712.2 ms (1.07×) | 783.9 ms (1.18×) |
|
||
| `premium_pass` | 150.6 ms | 65.3 ms (0.43×) | 69.9 ms (0.46×) |
|
||
|
||
The guard explains part of the creation cost, not all of it. v1.5.1 rebuilt
|
||
the wrapper around one `asyncio.timeout_at` instead of two `asyncio.wait_for`s
|
||
(`wait_for` wraps its awaitable in a Task, so the old shape created two extra
|
||
Tasks and two timer handles per call), which recovered about a third of its
|
||
overhead; the rest is the Task the abandon-on-timeout design needs. With the
|
||
guard off, creation is still 1.17× and 1.20× v1.4.0's time. A cProfile of the
|
||
event-loop thread over the 2,000 creates counts **491,071** function calls on
|
||
1.6.0 against **319,038** on v1.4.0: per-call work added since 1.4 that 1.6
|
||
has not removed. The region-query row, whose guard-off median is the slower
|
||
one, is this machine's noise floor (about ±10 %).
|
||
|
||
Roadmap criterion 7 — headless creation throughput back to v1.4.0's numbers —
|
||
is therefore **not met** and moves to 1.7. Earlier versions of this page said
|
||
that `EZDXF_CALL_TIMEOUT=0` returned creation to v1.4.0's numbers; measured
|
||
this way, it does not. The guard stays: it is what stops one hung ezdxf call
|
||
wedging a server whose document lock is a single `asyncio.Lock`.
|
||
|
||
`perf-ezdxf.json`, the chart's input, is the median of the five
|
||
default-configuration runs, with every run kept in `runs_ms`.
|
||
|
||
Reproduce (the v1.4.0 tree needs 1.6.0's `benchmarks/perf_suite.py` copied in):
|
||
|
||
```bash
|
||
git worktree add ../v140 v1.4.0 && cp benchmarks/perf_suite.py ../v140/benchmarks/
|
||
for i in 1 2 3 4 5; do
|
||
python -m benchmarks.perf_suite --json > main-$i.json
|
||
EZDXF_CALL_TIMEOUT=0 python -m benchmarks.perf_suite --json > off-$i.json
|
||
(cd ../v140 && python -m benchmarks.perf_suite --json) > v140-$i.json
|
||
done
|
||
```
|
||
|
||
The profile, run from each tree's root:
|
||
|
||
```python
|
||
import asyncio, cProfile, pstats
|
||
from backends.ezdxf_backend import EzdxfBackend
|
||
|
||
|
||
async def work():
|
||
b = EzdxfBackend()
|
||
await b.connect()
|
||
await b.drawing_new()
|
||
for i in range(2000):
|
||
await b.entity_create_line(0, i, 100, i)
|
||
await b.disconnect()
|
||
|
||
|
||
pr = cProfile.Profile()
|
||
pr.enable()
|
||
asyncio.run(work())
|
||
pr.disable()
|
||
print(pstats.Stats(pr).total_calls)
|
||
```
|
||
|
||
## Correctness A/B suite
|
||
|
||
`correctness_suite.py` is a set of deterministic, headless (ezdxf-backend) checks
|
||
that exercise real drawing operations. `compare_versions.py` runs the **same**
|
||
suite against an older git ref and the current checkout, each check in its own
|
||
subprocess so a hard crash (e.g. a matplotlib-in-thread `SIGSEGV`) is recorded as
|
||
a miss rather than taking down the run.
|
||
|
||
Reproduce:
|
||
|
||
```bash
|
||
python benchmarks/compare_versions.py # current tree vs origin/main
|
||
python benchmarks/compare_versions.py v1.0.0 # vs a tag/ref
|
||
python benchmarks/compare_versions.py --json results.json
|
||
```
|
||
|
||
### Result — v1.6.0 vs v1.5.1 (release gate)
|
||
|
||
48 checks, ezdxf backend, one subprocess per check. Machine-readable report:
|
||
[`results/published/ab-v1.5.1-vs-v1.6.0.json`](results/published/ab-v1.5.1-vs-v1.6.0.json).
|
||
|
||
| Version | Checks passing | Pass rate | Fixed | Regressed |
|
||
|---------|----------------|-----------|-------|-----------|
|
||
| **v1.5.1** (baseline) | 26 / 48 | 54.2 % | — | — |
|
||
| **v1.6.0** (this release) | 48 / 48 | 100 % | 22 | **0** |
|
||
|
||
The twenty-two are all `miss → pass` — v1.5.1 has none of the methods. Five are track H: `scale_check_detects_schematic` (the synthetic P&ID is schematic against its layout; a uniform 2× copy is to scale with factor 2), `pipe_takeoff_rmst_exact` (eight routable runs, each exactly the rectilinear MST of its tags), `cable_takeoff_roundup` (9 / 17 / 19 / 10 m, the unstated power left empty), `diff_detects_known_edits` (five known edits and nothing else) and `topology_known_defects` (one near miss, one crossing, a clean T). Four are track F: `arch_junction_l_t_x` (L, T and X junction outlines against hand-computed corners and wall areas), `arch_room_area_net` (a 4 × 5 m room between 200 mm walls is labelled 20.00 m², not its 21.84 m² axis area), `arch_opening_cuts_wall` (both faces interrupted across a door, two jambs) and `arch_rooms_detect_foreign` (plain lines on a WALLS layer read as 20 and 15 m² at confidence 0.6, nothing written). Seven
|
||
are tracks B and G: `mech_part_roundtrip` (a part read back from its own
|
||
`ACADMCP_MECH` XDATA equals the part drawn), `mech_section_hatch_area` (a
|
||
sleeve 60 long, ⌀40 outside, ⌀20 bore: a full section cuts two 60 × 10 faces,
|
||
1200 mm² exactly, measured out of the drawing by `entity_measure` — never from
|
||
the code that drew it), `mech_iso286_on_dimension` (ISO 286-1: 40 H7 is
|
||
+0.025 / 0, and the fit survives `layout_dimensions`),
|
||
`mech_thread_unrepresented_is_caught` (the ISO 6410 focus fires on a thread
|
||
drawn as a plain circle and stops firing once the thin ¾ arc is there — the
|
||
mechanical twin of `dim_overlap_critique_fires`), `std_part_iso4014_m12` (ISO
|
||
4014:2011 table 1, M12: s = 18, k = 7.5, matched by value; the inserted block
|
||
carries its designation back), `sheet_frame_iso5457_a3` (the frame runs 20,10
|
||
to 410,287 inside the 420 × 297 trimmed sheet) and `bom_balloon_link` (two
|
||
identical bolts collapse into one row of quantity 2 with a stable item number,
|
||
and that row's balloon carries it). The other six are the three
|
||
P&ID checks track A added (`pid_block_define_attdef_roundtrip`,
|
||
`pid_tag_parse_fic`, `pid_graph_edge_count`) and the three settings checks of
|
||
track E (`settings_dimstyle_iso25_values`: the ISO-25 preset reaches the
|
||
DIMSTYLE table with ISO 129-1's numbers, read back through ezdxf and not
|
||
through the lister; `settings_layer_state_roundtrip`: save → change → restore
|
||
puts the layer table back and the state survives save/reopen as an XRECORD in
|
||
the file; `settings_pdf_mediabox_a3`: the plotted PDF's own `/MediaBox` reads
|
||
420 × 297 mm). Every one of the 26 checks it was released on still passes, so
|
||
the five tracks added capability without moving a number it had already earned.
|
||
|
||
### Result — v1.5.1 vs v1.5.0
|
||
|
||
26 checks at the time, ezdxf backend, one subprocess per check. Machine-readable report:
|
||
[`results/published/ab-v1.5.0-vs-v1.5.1.json`](results/published/ab-v1.5.0-vs-v1.5.1.json).
|
||
|
||
| Version | Checks passing | Pass rate | Fixed | Regressed |
|
||
|---------|----------------|-----------|-------|-----------|
|
||
| v1.5.0 (baseline) | 24 / 26 | 92.3 % | — | — |
|
||
| v1.5.1 (that release) | 26 / 26 | 100 % | 2 | **0** |
|
||
|
||
v1.5.0 fails `diameter_dim_measures_the_diameter` and
|
||
`radius_dim_ignores_the_leader_length`: it has both methods and gets both
|
||
wrong. That is the defect 1.5.1 exists to fix and the reason 1.5.0 is yanked.
|
||
|
||
### Result — v1.5.1 vs v1.4.0
|
||
|
||
Same 26-check suite, older baseline:
|
||
[`results/published/ab-v1.4.0-vs-v1.5.1.json`](results/published/ab-v1.4.0-vs-v1.5.1.json).
|
||
|
||
| Version | Checks passing | Pass rate | Fixed | Regressed |
|
||
|---------|----------------|-----------|-------|-----------|
|
||
| v1.4.0 (baseline) | 21 / 26 | 80.8 % | — | — |
|
||
| v1.5.1 (that release) | 26 / 26 | 100 % | 5 | **0** |
|
||
|
||
> This row used to read `v1.5.0 | 24 / 26 | 92.3 % | 5`, which was impossible on
|
||
> its face — 21 passing plus 5 fixed is 26, not 24 — and it contradicted the
|
||
> report it linked. The cause is worth recording: the file named
|
||
> `ab-v1.4.0-vs-v1.5.0.json` was regenerated from a tree that already carried
|
||
> the dimension fixes, so it never measured the v1.5.0 *tag*. The v1.5.0 tag
|
||
> really does score 24/26, which is what the v1.5.0-baseline table above
|
||
> reports. This table now names the tree it actually measured.
|
||
|
||
The pass rate is against the 26-check current suite; v1.4.0 passed 21/21 of the
|
||
checks that existed when it shipped. The five splits into two kinds, and the
|
||
difference is the whole reason this lane distinguishes `miss` from `fail`:
|
||
|
||
* **Three are new capability.** v1.4.0 has no `entity_measure` and no boundary
|
||
tracing, so it misses `hatch_area_subtracts_its_island`,
|
||
`boundary_area_agrees_with_measure` and `two_vertex_circle_has_area` by not
|
||
having the method (`miss → pass`). They are in the suite because all three
|
||
broke during v1.5.0 development and nothing outside their own unit tests
|
||
would have caught it.
|
||
* **Two are repaired defects.** v1.4.0 *and v1.5.0* both have
|
||
`dimension_diameter` and `dimension_radius` and both get them wrong
|
||
(`fail → pass`): `leader_length`, a text placement, was being measured as
|
||
geometry, so at default settings every diameter callout came out
|
||
2 × `leader_length` too large. Only 1.5.1 passes them.
|
||
|
||
The 21 pre-existing checks are unchanged and all still pass: the discovery
|
||
layer, `cad_batch`, the 19-module contract split, the layout/viewport family
|
||
and Wave A landed with **zero correctness regressions**.
|
||
|
||
### Result — v1.4.0 vs v1.3.0
|
||
|
||
21 checks. Report:
|
||
[`results/published/ab-v1.3.0-vs-v1.4.0.json`](results/published/ab-v1.3.0-vs-v1.4.0.json).
|
||
|
||
| Version | Checks passing | Pass rate | Fixed | Regressed |
|
||
|---------|----------------|-----------|-------|-----------|
|
||
| **v1.3.0** (baseline) | 21 / 21 | 100 % | — | — |
|
||
| **v1.4.0** | 21 / 21 | 100 % | 0 | **0** |
|
||
|
||
### Result — v1.1.0 vs v1.0.0 (`origin/main`, commit 15fa2bc)
|
||
|
||
21 checks, ezdxf backend, CPython 3.14, one subprocess per check.
|
||
|
||
| Version | Checks passing | Pass rate |
|
||
|---------|----------------|-----------|
|
||
| **v1.0.0** (public release) | 8 / 21 | **38.1 %** |
|
||
| **v1.1.0** (this release) | 21 / 21 | **100 %** |
|
||
|
||
**13 defects fixed, 0 regressions** — a 2.6× higher correctness pass-rate (+61.9 pts).
|
||
|
||
Fixed (failed on v1.0.0, pass on v1.1.0):
|
||
|
||
| Check | Category | What it proves |
|
||
|-------|----------|----------------|
|
||
| `dim_aligned_no_error` | Dimensions | aligned dim no longer raises `TypeError` |
|
||
| `dim_angular_no_error` | Dimensions | angular dim no longer raises `TypeError` |
|
||
| `array_polar_360_distinct` | Modify | full-circle array no longer duplicates the original |
|
||
| `point_intersection_line_line` | Geometry | deterministic line/line intersection exists & is correct |
|
||
| `point_tangent_external` | Geometry | external-point tangent is perpendicular & on-circle |
|
||
| `arc_has_length` | Query | ARC carries a `length` property |
|
||
| `arc_select_by_length` | Query | `entity_select_smart` length_range selects arcs |
|
||
| `ezdxf_bounding_box` | Query | `bounding_box` populated (COM/ezdxf parity) |
|
||
| `mtext_rotation_roundtrip` | Entities | MTEXT honors caller rotation |
|
||
| `screenshot_png` | Render | headless render returns a valid PNG (no GUI-thread crash) |
|
||
| `dim_overlap_critique_fires` | Quality gate | `dim_overlap` critique is live (was a no-op) |
|
||
| `iso13567_dim_layer` | Quality gate | `dimension_auto` lands dims on the active set's layer |
|
||
| `gear_no_self_overlap` | Engineering | gear outline never dips inside the root circle (z ≥ 42) |
|
||
|
||
Passing on **both** versions (6 core ops + 2 others) keep the suite honest — it is
|
||
not a cherry-picked list of failures; the baseline genuinely does basic CAD work.
|
||
|
||
### Caveats / honesty notes
|
||
|
||
- The baseline is `origin/main` (the public v1.0.0). All four audited fix sprints
|
||
land between it and v1.1.0 — see `docs/analysis/`.
|
||
- `screenshot_png` on the baseline is host-dependent: it is recorded as a miss
|
||
here because the default matplotlib backend is a GUI backend and the render runs
|
||
off the main thread. On a host where matplotlib defaults to `Agg`, the baseline
|
||
may not crash. v1.1.0 forces `Agg` and is deterministic.
|
||
- `center_linetype_applied` passes on both because it only checks the linetype
|
||
**attribute** string (set on both); the v1.0.0 "renders as Continuous because the
|
||
linetype was never loaded" defect is not observable through `EntityInfo`.
|
||
- COM-backend behaviour is not covered here (no live AutoCAD); see the mocked-COM
|
||
suites in `tests/`.
|
||
|
||
Model-by-model tool-call success rates remain future work; scale/throughput
|
||
numbers live in the headless performance lane above.
|