Compare commits

...
Author SHA1 Message Date
bherbruck 2bb9c81069 Release 0.14.0 2026-10-16 09:19:14 +00:00
bherbruckandClaude Opus 5.5 cedb51b8ea Release 0.14.0
Version 0.14.0; the pending design-format changes are folded into format 25.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 09:19:10 +00:00
bherbruckandClaude Opus 5.5 9d82b94469 Robustness goldens: record seeds 31 and 83 after #1581 and #1495 (#1657)
Seed 31: #1581 refuses a fillet selection with an earlier round's smooth seam instead of re-rounding another nearby edge. Seed 83: #1316 was recorded before its branch took in #1495, whose shell keeps the wall beside a concave edge.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 04:55:20 -04:00
bherbruckandClaude Opus 5.5 a9beb437b3 Boolean property tests: nightly only, and the cases they found as ignored tests (#1650)
Pushes to main skip boolean_props (random inputs); the nightly property-tests job runs it. The three shrunk cases are ignored tests with their issues (#1647, #1648, #1649), and proptest's failure file is named outright.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 04:35:53 -04:00
bherbruck 66d13059dd Release 0.13.1 2026-10-15 21:56:43 +00:00
bherbruckandClaude Opus 5.5 800ea40c0d Release 0.13.1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 21:56:35 +00:00
bherbruck 20442eecd3 Agent chat on Windows: start npm agents through their .cmd shim
npm puts a Unix shell script with the bare name (codex-acp,
claude-code-acp, gemini) beside the .cmd shim. The lookup tried the bare
name first, so every npm-installed agent failed with "not a valid Win32
application (os error 193)". Windows now only considers .exe/.cmd/.bat,
and a command saved with the bare path runs the shim beside it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 60ea8c69d2)
2026-10-15 21:51:01 +00:00
bherbruck 22d1cd7032 Release 0.13.0 2026-10-15 20:34:13 +00:00
bherbruck 6a0dc18655 Fix the Windows 32-bit and macOS release builds
On 32-bit Windows SetWindowLongPtrW is SetWindowLongW, which takes and
returns i32, so the window-procedure hook didn't compile for i686.
The macOS app bundle now signs the bundled solidcraft-cli before the
bundle itself; codesign refuses a bundle with an unsigned helper.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 94e76cd97e)
2026-10-15 20:34:12 +00:00
bherbruck e006dbf1af Release 0.13.0
The tree is the 0.13.0 version commit (8241a750fa) on main exactly; the previous release is kept as a parent so release only moves forward.
2026-10-15 19:59:50 +00:00
bherbruckandClaude Opus 5.5 8241a750fa Release 0.13.0
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 19:59:42 +00:00
bherbruck 41779cf7bb Release 0.12.0: release branch follows main again after the 0.11.1 patch
The tree is main at c36a6f9f exactly; the 0.11.1 patch commit is kept as a parent so release only moves forward.
2026-10-15 13:32:01 +00:00
bherbruckandClaude Opus 5.5 b01b9689c8 Release 0.11.1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 20:41:41 +00:00
bherbruck 4426da6f5d Windows: start Direct3D 12 first, and Vulkan only without a D3D12 GPU (#1190)
* Windows: start Direct3D 12 first, and Vulkan only without a D3D12 GPU

wgpu started Vulkan, D3D12 and OpenGL together and preferred Vulkan, so
every Windows start loaded all installed Vulkan drivers. An old Intel
Vulkan driver (igvk64.dll) crashed 0.10.1 at startup inside the driver.

The app now probes D3D12, then Vulkan, then OpenGL, one backend at a
time, and starts only the first with a hardware adapter (WARP when none
has one). WGPU_BACKEND still wins; graphics.json's "backend" sets the
order by hand; the software renderer (safe mode, the retry) uses D3D12
alone. A new Windows CI job starts the real app under several settings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* CI: a test filter YAML parses

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* CI: the startup smoke test writes graphics.json where the app reads it

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* CI: run the WARP shader tests one at a time

WARP's shader JIT (d3d10warp.dll) crashed twice in 124 runs of the gpu
tests with four test threads, with a null read inside its own optimizer
while several devices compiled compute pipelines at once. Serial runs
did not crash.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 95ea05e7c8)
2026-10-14 20:41:37 +00:00
bherbruck 5ead82e627 Release 0.11.0: release branch follows main again after the 0.10.1 patch
The tree is main at cccbe125 exactly; c29b0d2f (0.10.1) is kept as a parent so release only moves forward.
2026-10-14 20:07:41 +00:00
bherbruck c29b0d2f13 Release 0.10.1 2026-10-14 14:41:19 +00:00
bherbruckandClaude Opus 5.5 480fd8d517 Fix the startup crash on D3D12 with FXC: CPU fallback for the tessellation pipeline, FXC-friendly shader (#1098) (#1099)
* GPU tessellation: a pipeline the device cannot build falls back to CPU meshing instead of crashing (#1098)

Build the tessellation compute pipeline inside error scopes; TessGpu::new returns an error,
and the viewport logs it and meshes bodies on the CPU. A Windows CI job builds the pipeline
with D3D12's FXC on the WARP adapter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Tessellation shader: basis functions in one-dimensional local arrays, so D3D12's FXC compiles it (#1098)

FXC has to unroll loops that write an array inside a struct (Basis.n0/n1) or a 2-D array
(ndu) by a counter, and cannot with run-time trip counts (X3511). A2.2 basis functions in a
local 1-D array returned whole; derivatives from the degree p-1 functions; the pole step
offsets computed rather than indexed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-14 14:41:16 +00:00
bherbruckandClaude Opus 5.5 4c09fa5355 Hovered and selected faces light solidly again over GPU-drawn bodies (#1064)
Since the model's mesh went back to the reference mesher (#940), the GPU viewport drew
opaque bodies from their display plans while hover and selection lit faces with the
reference mesh's triangles. Two triangulations of the same face at equal depth fought, so
a hovered face shimmered in wedges instead of lighting whole.

Faces lit over a body drawn from its plan now use the plan's own triangles
(ModelBody::plan_mesh, evaluated once on the CPU), and the highlight pass gets a small
depth bias toward the eye so f32 rounding between the GPU and CPU evaluations of the same
triangles cannot speckle it either.

Test: every face of a box with a pocket and a round boss, hovered and rendered offscreen
through the viewport's real pipelines on the GPU path, lights the same pixels as on the
CPU path (skipped without a GPU adapter).

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-14 12:56:47 +00:00
16 changed files with 89 additions and 28 deletions

No files matched your search

+2 -1
View File
@@ -3,7 +3,8 @@
[profile.ci]
# Heavy tests (one to four minutes each on a CI runner) that pull requests skip. They run on
# every push to main and nightly (`--ignore-default-filter`), and in `cargo xtask test`.
# every push to main and nightly (`--ignore-default-filter`), and in `cargo xtask test`. The
# boolean_props property tests run only nightly (ci.yml excludes them from pushes to main).
# Keep this list in sync with docs/book/src/dev/testing.md and CONTRIBUTING.md.
default-filter = """
not (
+7 -2
View File
@@ -229,7 +229,11 @@ jobs:
run: |
# Pull requests skip the heavy tests (default-filter in .config/nextest.toml).
all=()
if [ "$EVENT_NAME" != pull_request ]; then all=(--ignore-default-filter); fi
# The boolean property tests draw new random inputs every run; they run only in the
# nightly property-tests job, so a known kernel bug cannot turn main red at random.
if [ "$EVENT_NAME" != pull_request ]; then
all=(--ignore-default-filter -E 'not (package(solidcraft-kernel) & binary(boolean_props))')
fi
# Extract into ./target: tests that run a built binary (env!("CARGO_BIN_EXE_*")) have
# the build job's path, which is the same checkout path here.
cargo nextest run --archive-file tests.tar.zst --workspace-remap . --extract-to . --extract-overwrite \
@@ -319,7 +323,8 @@ jobs:
property-tests:
# The boolean property tests and the atomic command tests with many cases. Pull requests skip
# them (.config/nextest.toml); pushes to main run them with small defaults (8 cases, seeds 0..4).
# them (.config/nextest.toml); pushes to main run atomic_commands with small defaults (seeds
# 0..4) and skip boolean_props, whose random inputs would fail main at random.
name: Property tests (many cases)
needs: build-tests
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
+2 -1
View File
@@ -181,7 +181,8 @@ Pull requests skip a few heavy tests (one to four minutes each), which run on ev
`pattern_suppress_tests::enclosure_arc_extinction_keeps_a_real_sharp_inner_lip_after_resize_and_reopen`,
`pattern_suppress_tests::enclosure_datums_follow_wall_height_and_board_depth_with_material_proofs`,
`cmd::plastic::tests::enclosure_snap_fit_follows_resized_front_wall`, the `atomic_commands` contract tests
- `solidcraft-kernel`: `tests::boolean_identity_on_tangent_primitives`, the `boolean_props` property tests
- `solidcraft-kernel`: `tests::boolean_identity_on_tangent_primitives`, and the `boolean_props` property tests
(these run only in the nightly "Property tests" job: their inputs are random, so main skips them too)
- `solidcraft-ui-egui` scenarios: `acceptance_sensor_enclosure`
**The action matrix** tries every ordinary action on every face, edge, vertex and body of a
Generated
+15 -15
View File
@@ -4051,7 +4051,7 @@ dependencies = [
[[package]]
name = "solidcraft"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"block2 0.6.2",
"eframe",
@@ -4080,7 +4080,7 @@ dependencies = [
[[package]]
name = "solidcraft-cli"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"image",
"serde_json",
@@ -4091,7 +4091,7 @@ dependencies = [
[[package]]
name = "solidcraft-derive"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"proc-macro2",
"quote",
@@ -4100,7 +4100,7 @@ dependencies = [
[[package]]
name = "solidcraft-doc"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"log",
"serde",
@@ -4117,7 +4117,7 @@ dependencies = [
[[package]]
name = "solidcraft-engine"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"log",
"serde",
@@ -4133,7 +4133,7 @@ dependencies = [
[[package]]
name = "solidcraft-geom"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"serde",
"serde_json",
@@ -4142,7 +4142,7 @@ dependencies = [
[[package]]
name = "solidcraft-gpu"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"log",
"naga",
@@ -4159,7 +4159,7 @@ dependencies = [
[[package]]
name = "solidcraft-io"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"cadmpeg-core",
"cadmpeg-ir",
@@ -4178,7 +4178,7 @@ dependencies = [
[[package]]
name = "solidcraft-kernel"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"log",
"proptest",
@@ -4197,7 +4197,7 @@ dependencies = [
[[package]]
name = "solidcraft-mcp"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"serde_json",
"solidcraft-engine",
@@ -4206,7 +4206,7 @@ dependencies = [
[[package]]
name = "solidcraft-render"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"image",
"moxcms",
@@ -4219,7 +4219,7 @@ dependencies = [
[[package]]
name = "solidcraft-sketch"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"serde",
"serde_json",
@@ -4230,7 +4230,7 @@ dependencies = [
[[package]]
name = "solidcraft-ui-egui"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"egui",
"egui-wgpu",
@@ -4251,7 +4251,7 @@ dependencies = [
[[package]]
name = "solidcraft-web"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"eframe",
"egui",
@@ -5750,7 +5750,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xtask"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"serde_json",
"sha2 0.10.9",
+1 -1
View File
@@ -5,7 +5,7 @@ members = ["crates/*", "apps/*", "xtask"]
exclude = ["vendor"]
[workspace.package]
version = "0.13.1"
version = "0.14.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
+56 -4
View File
@@ -4,10 +4,13 @@
//! inputs, and when a case fails it shrinks the inputs to a small case that still fails. Paste
//! that case into a normal `#[test]` as a regression test.
//!
//! Case count: `PROPTEST_CASES` (default 8). Pull requests skip this file (.config/nextest.toml);
//! pushes to main run it, and the nightly `property-tests` job uses more cases. A long local run:
//! Case count: `PROPTEST_CASES` (default 8). Pull requests and pushes to main skip this file
//! (.config/nextest.toml, .github/workflows/ci.yml): its random inputs differ on every run, so a
//! known kernel bug would turn main red at random. The nightly `property-tests` job runs it with
//! more cases. A long local run:
//! `PROPTEST_CASES=2000 cargo test -p solidcraft-kernel --test boolean_props`.
//! proptest saves every failing seed in `tests/boolean_props.proptest-regressions`. Commit it.
//! proptest saves every failing seed in `tests/boolean_props.proptest-regressions`. Commit it, and
//! copy the shrunk case into a `#[test]` below (ignored, with its issue, until the kernel is fixed).
use proptest::prelude::*;
use solidcraft_geom::Vec3;
@@ -102,7 +105,15 @@ fn cfg(default: u32) -> ProptestConfig {
// The env var overrides the default; proptest reads PROPTEST_CASES itself only when the
// config does not set `cases`, so read it here.
let cases = std::env::var("PROPTEST_CASES").ok().and_then(|s| s.parse().ok()).unwrap_or(default);
ProptestConfig { cases, max_shrink_iters: 200, ..ProptestConfig::default() }
// Integration tests have no lib.rs or main.rs beside them, so proptest's default persistence
// cannot place its file; name it.
let file = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/boolean_props.proptest-regressions");
ProptestConfig {
cases,
max_shrink_iters: 200,
failure_persistence: Some(Box::new(prop::test_runner::FileFailurePersistence::Direct(file))),
..ProptestConfig::default()
}
}
proptest! {
@@ -218,3 +229,44 @@ proptest! {
prop_assert!(close(run(&plate, &boss, BoolOp::Union)?, vp + vb, vp + vb), "plate + boss lost or gained volume");
}
}
// Cases the property tests found, as plain tests. Ignored until the kernel bug is fixed: run them
// with `cargo test -p solidcraft-kernel --test boolean_props -- --ignored`.
fn check(r: Result<(), TestCaseError>) {
if let Err(e) = r {
panic!("{e}");
}
}
/// A box and a cylinder in general position: the intersect is refused as an implausible volume.
#[test]
#[ignore = "kernel bug, #1647"]
fn generic_box_cylinder_intersect() {
let a = Prim::Box { at: [11.483767761089219, 4.801612107504224, 3.6313476467611787], size: [4.789088988834864, 3.0, 10.419335961063835] };
let b = Prim::Cyl { at: [29.2480632732879, 8.3683289461485, 6.947922225984966], axis: 2, r: 13.502884497756853, h: 19.719218725477496 };
check(identity(&a, &b));
}
/// A sphere and a box on the grid: the union has a face that does not mesh.
#[test]
#[ignore = "kernel bug, #1648"]
fn grid_sphere_box_union_is_sound() {
let a = Prim::Sph { at: [2.5, 15.0, 27.5], r: 12.5 };
let b = Prim::Box { at: [0.0, 0.0, 17.5], size: [5.0, 7.5, 5.0] };
check(identity(&a, &b));
}
/// Two crossing cylinders: the union moved 1000 mm away loses about a tenth of its volume.
#[test]
#[ignore = "kernel bug, #1649"]
fn crossing_cylinders_union_moved_far() {
let a = Prim::Cyl { at: [17.5, 30.0, 7.5], axis: 0, r: 5.0, h: 10.0 }.body();
let b = Prim::Cyl { at: [17.5, 30.0, 15.0], axis: 1, r: 12.5, h: 5.0 }.body();
let t = Vec3::new(1000.0, -700.0, 300.0);
let mv = |x: &Body| transform(x, t, Vec3::ZERO, Vec3::Z, 0.0).expect("translate");
let s = vol(&a) + vol(&b);
let here = run(&a, &b, BoolOp::Union).unwrap();
let there = run(&mv(&a), &mv(&b), BoolOp::Union).unwrap();
assert!(close(here, there, s), "union at origin {here:.4}, moved by 1000 gives {there:.4}");
}
+2 -1
View File
@@ -81,7 +81,8 @@ Pull requests skip a few heavy tests (each takes one to four minutes); they run
`pattern_suppress_tests::enclosure_arc_extinction_keeps_a_real_sharp_inner_lip_after_resize_and_reopen`,
`pattern_suppress_tests::enclosure_datums_follow_wall_height_and_board_depth_with_material_proofs`,
`cmd::plastic::tests::enclosure_snap_fit_follows_resized_front_wall`, the `atomic_commands` contract tests
- `solidcraft-kernel`: `tests::boolean_identity_on_tangent_primitives`, the `boolean_props` property tests
- `solidcraft-kernel`: `tests::boolean_identity_on_tangent_primitives`, and the `boolean_props` property tests
(these run only in the nightly "Property tests" job: their inputs are random, so main skips them too)
- `solidcraft-ui-egui` scenarios: `acceptance_sensor_enclosure`
The **action matrix** (`apps/solidcraft-cli/tests/action_matrix.rs`) tries every ordinary action
+4 -3
View File
@@ -32,7 +32,7 @@
"28": {"area":2394.691384,"bodies":1,"edges":15,"error":null,"faces":9,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","join cylinder"],"unsupported":0,"volume":5902.121375},
"29": {"area":6965.599057,"bodies":1,"edges":74,"error":null,"faces":30,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","shell","join cylinder"],"unsupported":0,"volume":3977.885339},
"30": {"area":853.525389,"bodies":1,"edges":23,"error":null,"faces":10,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","chamfer"],"unsupported":0,"volume":1337.14821},
"31": {"area":10198.42294,"bodies":3,"edges":67,"error":null,"faces":29,"outcome":"ok","rejected":1,"shells":3,"steps":["box","cut box","fillet","join box","fillet","circular pattern","cut box","circular pattern"],"unsupported":0,"volume":56408.3612},
"31": {"area":10221.945046,"bodies":3,"edges":68,"error":null,"faces":29,"outcome":"ok","rejected":2,"shells":3,"steps":["box","cut box","fillet","join box","circular pattern","cut box","circular pattern","hole"],"unsupported":0,"volume":56397.581359},
"32": {"area":3220.802769,"bodies":1,"edges":51,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere","cut box","cut box","join box"],"unsupported":0,"volume":7565.955897},
"33": {"area":5286.736353,"bodies":2,"edges":59,"error":null,"faces":27,"outcome":"ok","rejected":0,"shells":3,"steps":["revolved section","fillet","join box","cut cylinder","chamfer","join box","chamfer","fillet"],"unsupported":2,"volume":13858.467983},
"34": {"area":1086.305562,"bodies":1,"edges":30,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere","hole"],"unsupported":0,"volume":2134.365275},
@@ -84,7 +84,7 @@
"80": {"area":8652.533342,"bodies":1,"edges":20,"error":null,"faces":9,"outcome":"ok","rejected":0,"shells":1,"steps":["box","fillet","fillet"],"unsupported":0,"volume":37387.449469},
"81": {"area":4001.847983,"bodies":2,"edges":75,"error":null,"faces":29,"outcome":"ok","rejected":1,"shells":2,"steps":["revolved section","cut sphere","circular pattern","rectangular pattern","hole","join cylinder","join box","cut sphere"],"unsupported":2,"volume":13139.658749},
"82": {"area":3195.487112,"bodies":1,"edges":30,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","hole","circular pattern","cut box"],"unsupported":0,"volume":5902.162954},
"83": {"area":8489.059183,"bodies":1,"edges":97,"error":null,"faces":35,"outcome":"ok","rejected":0,"shells":2,"steps":["box","join box","rectangular pattern","shell","fillet","cut sphere","join box"],"unsupported":1,"volume":6217.394627},
"83": {"area":8632.834978,"bodies":1,"edges":95,"error":null,"faces":35,"outcome":"ok","rejected":0,"shells":2,"steps":["box","join box","rectangular pattern","shell","cut sphere","join box","fillet"],"unsupported":2,"volume":6339.218896},
"84": {"area":4906.192237,"bodies":1,"edges":67,"error":null,"faces":25,"outcome":"ok","rejected":2,"shells":1,"steps":["extruded profile","fillet","hole","hole","cut cylinder","cut box","fillet"],"unsupported":0,"volume":14219.865875},
"85": {"area":3309.210936,"bodies":1,"edges":57,"error":null,"faces":24,"outcome":"ok","rejected":1,"shells":2,"steps":["revolved section","fillet","cut sphere","circular pattern","join cylinder","hole","join box"],"unsupported":0,"volume":6904.455565},
"86": {"area":7885.256491,"bodies":1,"edges":66,"error":"offset: a corner whose faces don't meet the same way after t","faces":28,"outcome":"failed","rejected":0,"shells":3,"steps":["extruded profile","join cylinder","join cylinder","circular pattern","rectangular pattern","shell"],"unsupported":2,"volume":21498.10695},
@@ -125,6 +125,7 @@
{"reason":"Seed 83 builds: a shell splits a saddle corner (convex and concave edges in turn) into two corners","seeds":[83]},
{"reason":"seed 15 now cuts around a sealed void","seeds":[15]},
{"reason":"#1490: when the last-resort nudge fails both ways, two more screw axes are tried; seed 63's final box join (its face 0.00005 mm off the half-ring's cut face) now builds instead of being refused with an implausible volume, and the design goes on to its fillet","seeds":[63]},
{"reason":"a prism cut in a column round the tool (prism_local): seed 65's rectangular pattern (step 4, a second half-cylinder groove overlapping the first, in a block with a rounded vertical edge) is now built by the exact 2D route in a column clear of the round, where the B-rep tries left it with split edges. The step's part has the 10 faces, 24 edges and 16 vertices it should (28 edges and 18 vertices before), and its volume is 45145.383 mm³ against 45145.214 (exactly 45145.103, both within the mesh measure); the generator then picks other edges for its step 7 fillet, so the run ends on another design (45143.48 mm³, 15 faces)","seeds":[65]}
{"reason":"a prism cut in a column round the tool (prism_local): seed 65's rectangular pattern (step 4, a second half-cylinder groove overlapping the first, in a block with a rounded vertical edge) is now built by the exact 2D route in a column clear of the round, where the B-rep tries left it with split edges. The step's part has the 10 faces, 24 edges and 16 vertices it should (28 edges and 18 vertices before), and its volume is 45145.383 mm³ against 45145.214 (exactly 45145.103, both within the mesh measure); the generator then picks other edges for its step 7 fillet, so the run ends on another design (45143.48 mm³, 15 faces)","seeds":[65]},
{"reason":"Seed 31: #1581 no longer maps a pick on an earlier round's seam to some other nearby edge; the second fillet's selection includes the smooth seam at [2, 27.5, 13.5] beside the first round, which is now refused (no corner to round) instead of silently re-rounding a different edge at 1 mm, so the run goes on to a hole. Seed 83: #1316 was recorded before its branch took in #1495; with #1495's shell (an open face next to a concave edge no longer cuts the wall past it) the shelled part keeps more wall (6217 to 6339 mm3), its edges differ, and the 2.5 mm fillet of the first run is not picked; a later fillet is refused as not supported yet (an end face parallel to the edge)","seeds":[31,83]}
]
}