Skip to content

Kernel: make boolean results reproducible across processes - #903

Merged
ecto merged 2 commits into
mainfrom
claude/deterministic-weld-order
Sep 17, 2026
Merged

ecto merged 2 commits into
mainfrom
claude/deterministic-weld-order

Conversation

@ecto

@ecto ecto commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Closes #893.

The same document evaluated to a different (equally valid) mesh from run to run — triangle counts wandered 99 985…105 998 on the rana-60 stator, and its mesh-fallback union returned 8118.6 mm³ closed or 8150.6 mm³ with 12 open edges. In-process repeats were always bit-identical, and the flip only ever showed when no debug env var was set, which pointed everywhere except the actual cause: HashMap iteration order (seeded per process) leaking into geometry in three places.

where what depended on hash order
sew::merge_nearby_vertices, repair::weld_boundary_vertices merged vertices were removed in merge_map.keys() order; a slotmap recycles freed slots LIFO, so removal order decides which keys later vertices get, and with them every id-based tie-break downstream (one 378-face union: 4067 / 4075 / 4083 triangles)
tessellate::snap_boundary_rails kept the FIRST nearest open edge (strict <) from a hash-ordered list; a slit's two rails routinely offer exactly equidistant candidates
tessellate::heal_t_junctions_pass candidates built from counts.iter(); ties survived in that order into dedup_by_key

All three now iterate sorted — three small hunks, no behaviour change beyond which of two equally valid tie-break winners is chosen (now always the same one).

Verification — across processes, since in-process repeats never showed it: 10/10 stator evaluations identical (volume, triangle count, defect count) under env -i with the environment block padded 37→370 bytes; an instrumented run gave byte-identical FNV hashes at all 7 fallback stages across 5 processes. Whole workspace the way CI runs it (cargo nextest run --workspace --exclude vcad-desktop --profile ci): 4378 passed; the only 2 failures are the local-GPU wavefront tests in #897, which pass on CI. Workspace clippy (1.98.1) and fmt clean.

Worth a look in CI: the torture-track baseline is platform-specific and these tie-breaks now resolve one fixed way, so a case could legitimately move.

🤖 Generated with Claude Code

Three places let HashMap iteration order — seeded per process — leak into
geometry, so the same document tessellated differently from run to run
(in-process repeats were always bit-identical, which is what made it look
like anything but hash order):

- sew::merge_nearby_vertices and repair::weld_boundary_vertices removed
  merged vertices in merge_map.keys() order. A slotmap recycles freed slots
  LIFO, so removal order decides which keys later vertices receive, and
  with them every id-based tie-break downstream. Measured on the rana-60
  stator: one 378-face union came back with 4067, 4075 or 4083 triangles.
- tessellate::snap_boundary_rails kept the FIRST nearest open edge from a
  HashMap-ordered list; a slit's two rails routinely offer exactly
  equidistant candidates.
- tessellate::heal_t_junctions_pass built its candidates from
  counts.iter(); ties survived in that order into dedup_by_key.

All three now iterate sorted. Verified across processes, not within one:
10/10 stator evaluations identical under env -i with the environment block
padded from 37 to 370 bytes (the flip only ever showed with no debug env
var set, i.e. it tracked the environment's size); before, triangle counts
wandered 99 985..105 998 and the mesh fallback returned 8118.6 or
8150.6 mm3 from "the same" input.

Closes #893

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
vcad-mcp Building Building Preview Sep 17, 2026 7:10pm UTC
3 Skipped Deployments
Project Deployment Actions Updated
mecheval Ignored Ignored Sep 17, 2026 7:10pm UTC
vcad Ignored Ignored Sep 17, 2026 7:10pm UTC
vcad-docs Ignored Ignored Sep 17, 2026 7:10pm UTC

Request Review

@ecto

ecto commented Sep 17, 2026

Copy link
Copy Markdown
Owner Author

Merged current main (which now includes #901) into this branch, since both change boolean tie-breaks and the first CI run predated #901. Verified the combination locally the way CI runs it: 4382 of 4384 pass (the 2 are the local-GPU tests in #897). The stator, evaluated three times under env -i with the environment padded to three different sizes — the condition that used to flip it — is now identical every run: 7852.96 mm³, Analytic, 11 708 triangles, 102 unpaired edges (before this PR the triangle count wobbled 11 708 / 11 709).

🤖 Posted by Claude Code

@ecto
ecto merged commit 2a9a5e5 into main Sep 17, 2026
14 checks passed
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.

kernel: mesh boolean fallback is not reproducible across processes (0 vs 12 open edges, 0.4% volume wobble)

1 participant