Skip to content

Fix issue #88: seven volumetric corpus mismatches, root-caused individually - #106

Merged
particlesector merged 1 commit into
mainfrom
claude/issue-88-6x1bgc
Aug 12, 2026
Merged

Fix issue #88: seven volumetric corpus mismatches, root-caused individually#106
particlesector merged 1 commit into
mainfrom
claude/issue-88-6x1bgc

Conversation

@particlesector

Copy link
Copy Markdown
Owner

Summary

Closes #88. Rebuilt the v3.9 volumetric-comparison toolchain (real OpenSCAD 2021.01 oracle, Manifold v3.5.2, headless chiselcad_core, scad_to_stl/stl_diff — see tests/tools/README.md) and bisected each of the issue's seven untriaged corpus files individually. All seven turned out to be real, distinct bugs, and all seven are now fixed — each file's sym_diff_volume against the live oracle is at floating-point-noise level:

  • ifelse-tests.scad (175% → exact): cube()/square()'s positional center argument (cube(2, true)) was silently dropped by the parser — never an if/else bug at all.
  • primitive-inf-tests.scad (83% → exact): non-finite cube/sphere/cylinder dimensions were folded to 0.0 instead of rejected, so e.g. cylinder(r2=1/0) silently became a valid zero-radius cone instead of empty geometry; a non-finite $fn also escaped its minimum-segment clamp via undefined int cast behavior (empirically INT_MAX on this platform).
  • surface-simple.scad (~82% → exact): surface()'s base was clamped to 0 instead of real OpenSCAD's actual minHeight - 1, center= incorrectly re-centered Z (real OpenSCAD only offsets X/Y), and the row-to-Y mapping was flipped relative to real OpenSCAD's own convention.
  • module-recursion.scad (31% → exact): rotate([x,y,z]) composed its three axis rotations in the reverse order from real OpenSCAD's X-then-Y-then-Z convention.
  • resize-tests.scad (1.5% → exact): resize()'s negative-newsize and auto= handling was a generalization that didn't match real OpenSCAD's actual getResizeTransform() algorithm — rewritten as a verbatim port.
  • intersection-tests.scad (6.4% → exact): a child that itself produced no geometry (e.g. bare render();) was silently dropped instead of nullifying the whole intersection(); carved out non-geometric statements (echo()/assert()) and %-backgrounded children so they're still just skipped, not treated as empty operands.
  • cylinder-tests.scad (13% → exact): when both r and r1/r2 were given, r unconditionally overwrote both instead of only filling in the unspecified slot.

Full root-cause narrative and verification details for each are in docs/roadmap.md's new v3.14 section.

Regression safety net

Re-verified against the live OpenSCAD oracle across the entire tests/data/scad/3D/features corpus subdirectory (72 of 73 files — see below), not just the seven files this issue named. That wider sweep caught a real regression the intersection() fix introduced in background-modifier2.scad (a %-backgrounded child inside an intersection() was being treated as an empty operand instead of simply excluded from the main tree) before it landed — fixed in the same commit.

Filed #105 for a pre-existing, unrelated crash found during that sweep (linear_extrude-parameter-tests.scad — heap corruption, confirmed present before this PR's changes too via a clean worktree build of the base commit).

Test plan


Generated by Claude Code

…dually

Rebuilt the v3.9 volumetric-comparison toolchain (real OpenSCAD oracle,
Manifold, headless chiselcad_core) and bisected each of the seven files
issue #88 flagged. All seven are real, distinct bugs, now fixed:

- ifelse-tests.scad: cube()/square()'s positional `center` argument
  (`cube(2, true)`) was silently dropped by the parser.
- primitive-inf-tests.scad: non-finite cube/sphere/cylinder dimensions
  were folded to 0 instead of rejected, sometimes producing a valid but
  wrong shape instead of empty geometry; a non-finite $fn also escaped
  its minimum-segment clamp via undefined int-cast behavior.
- surface-simple.scad: surface()'s base was clamped to 0 instead of
  real OpenSCAD's `minHeight - 1`, `center` incorrectly re-centered Z,
  and the row-to-Y mapping was flipped relative to real OpenSCAD.
- module-recursion.scad: rotate([x,y,z]) composed its three axis
  rotations in the reverse order from real OpenSCAD's X,Y,Z convention.
- resize-tests.scad: resize()'s negative-newsize and auto= handling
  didn't match real OpenSCAD's actual algorithm.
- intersection-tests.scad: an empty-geometry operand was silently
  dropped instead of nullifying the whole intersection(); excluded
  non-geometric statements (echo/assert) and '%'-backgrounded children
  from that rule so they're still just skipped.
- cylinder-tests.scad: when both r and r1/r2 were given, r
  unconditionally overwrote both instead of only filling the
  unspecified slot.

Re-verified against the live OpenSCAD oracle across the entire
tests/data/scad/3D/features corpus (not just these seven files), which
caught and fixed a regression the intersection() fix introduced in
background-modifier2.scad. Filed issue #105 for a pre-existing,
unrelated crash found during that sweep (linear_extrude-parameter-tests.scad).

9 new regression tests added; full chiselcad_tests suite passes
(3679 assertions, 664 test cases).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PTiTkTcWUPE4ucANory5WA

@particlesector particlesector left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff (excluding docs/roadmap.md and fixtures). Each of the seven fixes is narrowly scoped, well-justified in comments, and backed by a targeted regression test:

  • Parser.cpp's positional true/false handling is correctly confined to parseParamList (cube/sphere/cylinder/square/circle + discarded-params call sites), not the generic user-module-call path, so it can't misinterpret a custom module's positional bool arg as center.
  • evalBoolean's new intersection-emptiness check correctly re-inspects astModifiers(*child) directly to distinguish "genuinely empty child" from "disabled/backgrounded child," since evalNode already consumes those modifiers before returning nullptr — no double-counting.
  • MeshEvaluator::checkStatus now discarding the manifold on error (not just the 7 named files) is the broadest behavioral change here, but it's covered by the described 72/73-file oracle resweep with no regressions, so it looks safe.

One minor note (non-blocking): the checkStatus change affects every call site (booleans, resize, primitives), not just the primitives touched by the issue's seven files — worth keeping an eye on if any downstream corpus file relied on a "degenerate but non-empty" manifold surviving a non-NoError status.

Nothing here blocks merging as far as I can tell.


Generated by Claude Code

@particlesector
particlesector merged commit 912c583 into main Aug 12, 2026
6 checks passed
@particlesector
particlesector deleted the claude/issue-88-6x1bgc branch August 12, 2026 06:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Medium] Untriaged geometry corpus mismatches: intersection(), cylinder() edge case, primitive-inf, ifelse, module-recursion, resize(), surface()

2 participants