The stator's last big union goes through api::mesh_fallback(.., refine = true). Same binary, same input:
- most runs: 8118.6 mm³, 0 open edges
- ~1 run in 3: 8150.6 mm³, 12 open edges (12 raw boundary edges)
It only flips when no VCAD_* debug env var is set: 0 of 26 runs with VCAD_BOOLEAN_WARN, VCAD_UNION_TRACE or a log-path var present, 5 of 14 without. None of those vars affects control flow (they only print), so the size of the environment block is what matters → address/alignment-dependent behaviour, uninitialised state, pointer-ordered containers, or float summation whose order depends on slice alignment. In-process repeats of mesh_csg + remove_interior_membranes + repair_watertightness on fixed operand meshes are bit-identical (8/8), so plain HashMap order does not explain it; QuadricCtx::project_mesh and the operand tessellation were not part of that check. Final triangle counts of the part also vary run to run (99 985 … 105 493).
Why it matters: fidelity and volume of a cached, content-addressed result depend on the run that populated the cache. #890 made the union referee tolerant (accepts a fallback ≥ 20× cleaner than the analytic result) so the verdict no longer flips, but the volume still wobbles.
Method that should work: unconditional (not env-gated — env vars mask it) stage hashes of operand meshes, raw CSG, each projection, membranes, repair; run ~10× under env -i with a padding variable of varying length; find the first stage that differs.
Found while getting the rana-60-cnc stator to solve (#890). Helpers ring(), post(), fillet_block(side) live in crates/vcad-kernel-booleans/tests/coplanar_cap_union.rs; assert volume against a closed form in any new test — every failure in this family was a plausible mesh of the wrong solid.
🤖 Filed by Claude Code
The stator's last big union goes through
api::mesh_fallback(.., refine = true). Same binary, same input:It only flips when no
VCAD_*debug env var is set: 0 of 26 runs withVCAD_BOOLEAN_WARN,VCAD_UNION_TRACEor a log-path var present, 5 of 14 without. None of those vars affects control flow (they only print), so the size of the environment block is what matters → address/alignment-dependent behaviour, uninitialised state, pointer-ordered containers, or float summation whose order depends on slice alignment. In-process repeats ofmesh_csg+remove_interior_membranes+repair_watertightnesson fixed operand meshes are bit-identical (8/8), so plainHashMaporder does not explain it;QuadricCtx::project_meshand the operand tessellation were not part of that check. Final triangle counts of the part also vary run to run (99 985 … 105 493).Why it matters: fidelity and volume of a cached, content-addressed result depend on the run that populated the cache. #890 made the union referee tolerant (accepts a fallback ≥ 20× cleaner than the analytic result) so the verdict no longer flips, but the volume still wobbles.
Method that should work: unconditional (not env-gated — env vars mask it) stage hashes of operand meshes, raw CSG, each projection, membranes, repair; run ~10× under
env -iwith a padding variable of varying length; find the first stage that differs.Found while getting the rana-60-cnc stator to solve (#890). Helpers
ring(),post(),fillet_block(side)live incrates/vcad-kernel-booleans/tests/coplanar_cap_union.rs; assert volume against a closed form in any new test — every failure in this family was a plausible mesh of the wrong solid.🤖 Filed by Claude Code