fix(parse): report draw operations that use an undefined aperture - #21
Merged
Merged
Conversation
A flash or stroke whose D-code selects an aperture that was never defined -- or that draws before any Dnn selection at all -- carried an aperture index with no definition behind it. Every consumer read that as nothing to draw: both geometry emit helpers returned early on `ap is None` and the raster renderer drew nothing. No diagnostic was emitted at any severity. diff and geomdiff therefore reported 0 changes at exit 0, and the JSON report was byte-identical to a comparison of two identical boards, so a real fabrication change could pass a --fail-on-diff gate invisibly. Check at the point of use rather than at selection. That is exactly coextensive with the harm: it also catches drawing before any selection (index 0), and it does not fail a file that selects a stray D-code but never draws with it, which renders identically. Offending codes are reported once each, so one bad aperture used by thousands of flashes yields one diagnostic. D02 moves and G36/G37 region contours consume no aperture and are skipped. The existing promotion path turns the Error into exit 2 for parse, render, diff and geomdiff alike, so no downstream change is needed. test_parse_minimal was itself an instance of this: it defined D10 but never selected it, so its stroke expanded to zero geometry ops while the test asserted no errors. Its D10* selection is now load-bearing. Closes #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A flash or stroke whose D-code selects an aperture that was never defined --
or that draws before any
Dnnselection at all -- carried an aperture indexwith no definition behind it. Every consumer read that as nothing to draw:
both geometry emit helpers returned early on
ap is None(
layer_geometry.py:280,:330) and the raster renderer drew nothing. Nodiagnostic was emitted at any severity.
diffandgeomdifftherefore reported0 changesat exit0, and the JSONreport was byte-identical to a comparison of two identical boards, so a real
fabrication change could pass a
--fail-on-diffgate invisibly.Where the check goes
At the point of use, not at selection -- a deliberate deviation from the
"cheapest: at selection" suggestion in the issue. Use is exactly coextensive
with the harm:
_current_apertureisstill
0andapertures.get(0)isNone-- the same silent drop, which aselection-time check would miss entirely.
That file renders identically, so breaking a CI gate on it would be a false
alarm.
parse,render,diffand
geomdiff. Both CLI paths already promoteErrorcorrectly; they justnever received one. No downstream change is needed.
Offending codes are reported once each, so one bad aperture used by thousands
of flashes yields one diagnostic rather than thousands. D02 moves and G36/G37
region contours consume no aperture and are skipped.
test_parse_minimalwas itself an instance of thisThe fix tripped exactly one pre-existing test, and it turned out to be a real
finding rather than a false positive.
test_parse_minimaldefinedD10butnever selected it, so its stroke carried aperture
0and expanded to zerogeometry ops -- while the test asserted no error diagnostics. Its
len(img.draw_ops) > 0assertion passed only because the parse IR holds theop; the geometry was empty. A test named "minimal" was asserting silence on a
file that draws nothing. Its
D10*selection is now load-bearing.Every other test, including the real KiCad board fixtures, stayed green --
which is the evidence that the check does not false-positive on real files.
Verification
Before, on
main:After:
diffandparselikewise now exit2on the same input; the control pair ofgenuinely identical boards still exits
0with0 changes.10 tests added. All 7 positive cases were confirmed to go red with the source
fix reverted and the tests kept; the 3 negative guards (D02 move, region fill,
valid flash) stay green either way, which is the shape that proves they guard
against the check firing on everything rather than merely passing.
Full CI-equivalent gate locally: ruff check, ruff format --check, mypy, the
ASCII scan, and 473 passed at 95.82% coverage (baseline was 463).
Closes #17