test: seeded goldens for the robustness seeds (#826)

tests/robustness/goldens.json records what each of seeds 0-95 builds: its steps, how it ended (ok, or the kind and the start of the error it was refused with) and, for the design it left, the bodies' total volume and area (within a relative 1e-6) and their face, edge and shell counts. tests::robustness_goldens fails on any change; ROBUSTNESS_GOLDENS_BLESS="why" records one, with the reason and the seeds it moved. Pull requests that touch crates/kernel run every 8th seed; pushes to main and the nightly run every seed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-13 16:23:27 -04:00
1 parent 200a83a629
commit 534393ed5a
9 files changed
+338 -5

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ not (
(package(solidcraft-engine) & (
test(=tests::hostile_params_never_panic)
| test(/^tests::robustness::/)
| test(/^tests::robustness_goldens::/)
| test(=pattern_suppress_tests::enclosure_arc_extinction_keeps_a_real_sharp_inner_lip_after_resize_and_reopen)
| test(=pattern_suppress_tests::enclosure_datums_follow_wall_height_and_board_depth_with_material_proofs)
| test(=cmd::plastic::tests::enclosure_snap_fit_follows_resized_front_wall)
+15 -1
View File
@@ -47,6 +47,7 @@ jobs:
runs-on: ubuntu-24.04
outputs:
native: ${{ steps.paths.outputs.native }}
kernel: ${{ steps.paths.outputs.kernel }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -59,12 +60,14 @@ jobs:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
native=false
kernel=false
if [ "$EVENT_NAME" != pull_request ]; then
native=true
else
# Compare both old and new paths of renames; unknown paths run native checks.
git diff --no-renames --name-only -z "$BASE_SHA...$HEAD_SHA" > "$RUNNER_TEMP/ci-paths"
while IFS= read -r -d '' path; do
case "$path" in crates/kernel/*|vendor/truck-*) kernel=true ;; esac
case "$path" in
.github/workflows/ci.yml) native=true ;;
*.rs|Cargo.toml|Cargo.lock|*/Cargo.toml|.cargo/*|rust-toolchain*) native=true ;;
@@ -74,6 +77,7 @@ jobs:
done < "$RUNNER_TEMP/ci-paths"
fi
echo "native=$native" >> "$GITHUB_OUTPUT"
echo "kernel=$kernel" >> "$GITHUB_OUTPUT"
checks:
name: Format and repository checks
@@ -150,7 +154,7 @@ jobs:
tests:
name: Native Linux tests (${{ matrix.shard }}/4)
needs: build-tests
needs: [changes, build-tests]
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
@@ -177,6 +181,16 @@ jobs:
# 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 \
--profile ci --partition "slice:$SHARD/4" "${all[@]}"
- name: Robustness goldens sample (pull requests that touch the kernel)
# Every 8th seed of tests/robustness/goldens.json, a quarter per shard; pushes to main and
# the nightly run every seed in the step above.
if: github.event_name == 'pull_request' && needs.changes.outputs.kernel == 'true'
env:
SHARD: ${{ matrix.shard }}
run: |
cargo nextest run --archive-file tests.tar.zst --workspace-remap . --extract-to . --extract-overwrite \
--profile ci --ignore-default-filter \
-E "package(solidcraft-engine) & test(=tests::robustness_goldens::sample_$((SHARD - 1)))"
- name: Upload test timings
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
+2 -1
View File
@@ -161,7 +161,8 @@ Pull requests skip a few heavy tests (one to four minutes each), which run on ev
`main` and nightly; check the `main` run after merging. They are listed in
`.config/nextest.toml` and the [testing page](docs/book/src/dev/testing.md):
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness`,
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness` and
`tests::robustness_goldens` (pull requests that touch `crates/kernel` run its `sample_*` part),
`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`
+43 -2
View File
@@ -289,6 +289,47 @@ pub fn run(seed: u64, max_steps: usize) -> Run {
/// [`run`], telling `on_step` about each step before it runs (to see where a hang happened).
pub fn run_with(seed: u64, max_steps: usize, on_step: &mut dyn FnMut(&Step)) -> Run {
run_session(seed, max_steps, on_step).0
}
/// What a seed builds, as recorded in the robustness goldens (`tests/robustness/goldens.json`):
/// the steps, how it ended (with the start of the message when it did not end ok) and, for the
/// design it left, its bodies' total volume and area and their face, edge and shell counts.
pub fn golden(seed: u64, max_steps: usize) -> Value {
let (run, s) = run_session(seed, max_steps, &mut |_| {});
let st = s.model.state();
let (mut volume, mut area, mut faces, mut edges, mut shells) = (0.0, 0.0, 0, 0, 0);
let mut measured = true;
for b in &st.bodies {
match solidcraft_kernel::measure(&b.body) {
Ok(m) => {
volume += m.volume;
area += m.area;
faces += m.faces;
edges += m.edges;
shells += m.shells;
}
Err(_) => measured = false,
}
}
let round = |x: f64| (x * 1e6).round() / 1e6;
json!({
"steps": run.steps.iter().map(|st| st.kind).collect::<Vec<_>>(),
"outcome": run.outcome.kind,
"error": (run.outcome.kind != "ok").then(|| gist(&run.outcome.detail)),
"unsupported": run.unsupported.len(),
"rejected": run.rejected,
"bodies": st.bodies.len(),
"volume": measured.then(|| round(volume)),
"area": measured.then(|| round(area)),
"faces": faces,
"edges": edges,
"shells": shells,
})
}
/// [`run_with`], and the session the run left.
fn run_session(seed: u64, max_steps: usize, on_step: &mut dyn FnMut(&Step)) -> (Run, Session) {
let mut r = Rng::new(seed);
let mut s = Session::default();
let mut run = Run {
@@ -333,13 +374,13 @@ pub fn run_with(seed: u64, max_steps: usize, on_step: &mut dyn FnMut(&Step)) ->
run.steps.push(st);
if out.kind != "ok" {
run.outcome = out;
return run;
return (run, s);
}
if matches!(kind, "join box" | "cut box" | "join cylinder" | "cut cylinder" | "cut sphere" | "hole") {
last_feature = s.doc.features.last().map(|f| f.name.clone());
}
}
run
(run, s)
}
/// Run fixed steps (a recorded or minimised design): the index and outcome of the first step
+1
View File
@@ -175,6 +175,7 @@ mod component_frames;
mod mirror_planes;
mod primitive_points;
mod robustness;
mod robustness_goldens;
mod xz_plane;
/// Plan area a round of radius `r` fills at the concave corner `x` where circles (c1, r1) and
@@ -0,0 +1,152 @@
//! Seeded goldens: what every robustness seed builds (`tests/robustness/goldens.json`).
//!
//! Each seed's random design (`fuzz::run`) is recorded: its steps, how it ended (ok, or the
//! kind and start of the error it was refused with) and, for what it left, the bodies' total
//! volume and area (within a relative 1e-6) and their face, edge and shell counts (exact). Any
//! change fails, the better and the worse alike: a seed that used to be refused and now builds
//! is recorded on purpose, with the reason. To record: `ROBUSTNESS_GOLDENS_BLESS="why" cargo
//! test --profile ci -p solidcraft-engine --lib robustness_goldens:: -- --include-ignored`
//! (docs/robustness.md). The reason goes into the file's `notes` with the seeds it changed.
//!
//! Pull requests that touch `crates/kernel` run a sample (`sample_0` … `sample_3`, every 8th
//! seed, split so the shards share them); pushes to main and the nightly run every seed
//! (`all_0` … `all_3`).
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Mutex;
use serde_json::{Value, json};
use crate::fuzz;
/// Seeds 0..SEEDS (about ten minutes for all of them on one core), each with up to this many steps (as `fixed_seeds_run_clean`).
const SEEDS: u64 = 96;
const MAX_STEPS: usize = 8;
const REL: f64 = 1e-6;
/// Tests share the file when they record.
static FILE: Mutex<()> = Mutex::new(());
fn path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/robustness/goldens.json")
}
fn load() -> Value {
std::fs::read(path()).ok().and_then(|b| serde_json::from_slice(&b).ok()).unwrap_or_else(|| json!({}))
}
/// How `got` differs from `want` (None when it matches within the tolerances).
fn differs(want: &Value, got: &Value) -> Option<String> {
let mut out = Vec::new();
for (k, g) in got.as_object().into_iter().flatten() {
let w = &want[k];
let same = match (w.as_f64(), g.as_f64()) {
(Some(a), Some(b)) if matches!(k.as_str(), "volume" | "area") => (a - b).abs() <= REL * a.abs().max(b.abs()).max(1.0),
_ => w == g,
};
if !same {
out.push(format!("{k}: {w} -> {g}"));
}
}
(!out.is_empty()).then(|| out.join("; "))
}
fn check(part: u64, parts: u64, every: u64) {
let seeds: Vec<u64> = (0..SEEDS).filter(|s| s % every == 0 && (s / every) % parts == part).collect();
let got: BTreeMap<u64, Value> = seeds.iter().map(|s| (*s, fuzz::golden(*s, MAX_STEPS))).collect();
let _lock = FILE.lock().unwrap_or_else(|e| e.into_inner());
let mut file = load();
let changed: Vec<(u64, String)> = got
.iter()
.filter_map(|(seed, g)| {
let w = &file["seeds"][seed.to_string()];
if w.is_null() { Some((*seed, "new".to_string())) } else { differs(w, g).map(|d| (*seed, d)) }
})
.collect();
if let Ok(reason) = std::env::var("ROBUSTNESS_GOLDENS_BLESS") {
assert!(!reason.trim().is_empty(), "ROBUSTNESS_GOLDENS_BLESS needs the reason for the change");
if changed.is_empty() {
return;
}
if file.get("about").is_none() {
file["about"] = json!(
"What each robustness seed builds (crates/engine/src/tests/robustness_goldens.rs, docs/robustness.md). Record changes with ROBUSTNESS_GOLDENS_BLESS=\"why\"."
);
}
for (seed, g) in &got {
file["seeds"][seed.to_string()] = g.clone();
}
let mut notes = file["notes"].as_array().cloned().unwrap_or_default();
let seeds: Vec<u64> = changed.iter().map(|(s, _)| *s).collect();
// The parts of one record (the tests run side by side) share one note.
match notes.last_mut() {
Some(last) if last["reason"] == json!(reason) => {
let mut all: Vec<u64> = last["seeds"].as_array().into_iter().flatten().filter_map(Value::as_u64).chain(seeds).collect();
all.sort_unstable();
all.dedup();
last["seeds"] = json!(all);
}
_ => notes.push(json!({"reason": reason, "seeds": seeds})),
}
file["notes"] = Value::Array(notes);
// Seeds in order, one per line, so a change is a small diff.
let seeds: BTreeMap<u64, Value> =
file["seeds"].as_object().into_iter().flatten().filter_map(|(k, v)| Some((k.parse::<u64>().ok()?, v.clone()))).collect();
let mut s = format!("{{\n \"about\": {},\n \"seeds\": {{\n", file["about"]);
let n = seeds.len();
for (i, (k, v)) in seeds.iter().enumerate() {
s += &format!(" \"{k}\": {}{}\n", v, if i + 1 < n { "," } else { "" });
}
let notes: Vec<String> = file["notes"].as_array().into_iter().flatten().map(|n| format!(" {n}")).collect();
s += &format!(" }},\n \"notes\": [\n{}\n ]\n}}\n", notes.join(",\n"));
std::fs::write(path(), s).unwrap_or_else(|e| panic!("{}: {e}", path().display()));
return;
}
assert!(
changed.is_empty(),
"{} seed(s) build differently from tests/robustness/goldens.json (if that is intended, record it with ROBUSTNESS_GOLDENS_BLESS=\"why\" and say why in the pull request):\n{}",
changed.len(),
changed.iter().map(|(s, d)| format!("seed {s}: {d}")).collect::<Vec<_>>().join("\n")
);
}
#[test]
fn sample_0() {
check(0, 4, 8);
}
#[test]
fn sample_1() {
check(1, 4, 8);
}
#[test]
fn sample_2() {
check(2, 4, 8);
}
#[test]
fn sample_3() {
check(3, 4, 8);
}
#[test]
fn all_0() {
check(0, 4, 1);
}
#[test]
fn all_1() {
check(1, 4, 1);
}
#[test]
fn all_2() {
check(2, 4, 1);
}
#[test]
fn all_3() {
check(3, 4, 1);
}
+2 -1
View File
@@ -76,7 +76,8 @@ On GitHub, one job builds the tests into a
Pull requests skip a few heavy tests (each takes one to four minutes); they run on every push to
`main`, nightly, and in `cargo xtask test`:
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness`,
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness` and
`tests::robustness_goldens` (pull requests that touch `crates/kernel` run its `sample_*` part),
`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`
+18
View File
@@ -36,6 +36,24 @@ rejected before any jobs or output directories are created. The maximum seed
- `cargo test` runs a fixed set of seeds that must stay clean
(`crates/engine/src/tests/robustness.rs`), next to the minimised cases fixed so far.
## Seeded goldens
`tests/robustness/goldens.json` records what each of seeds 0–95 builds (eight steps at most):
its steps, how it ended (ok, or the kind and the start of the error it was refused with), and
for the design it left the bodies' total volume and area (within a relative 1e-6) and their
face, edge and shell counts (exact). `tests::robustness_goldens` fails on any change, better or
worse, so a kernel change shows every seed it moves. Pull requests that touch `crates/kernel`
run every 8th seed (`sample_0` … `sample_3`, about two minutes); pushes to `main` and the
nightly run them all (`all_0` … `all_3`).
To record an intended change, give the reason:
```
ROBUSTNESS_GOLDENS_BLESS="why it changed" cargo test --profile ci -p solidcraft-engine --lib robustness_goldens::all
```
The changed seeds and the reason are added to the file's `notes`; say why in the pull request too.
## Successful Boolean retries
When an accepted Boolean result needs a numerical retry, the feature shows a
+104
View File
@@ -0,0 +1,104 @@
{
"about": "What each robustness seed builds (crates/engine/src/tests/robustness_goldens.rs, docs/robustness.md). Record changes with ROBUSTNESS_GOLDENS_BLESS=\"why\".",
"seeds": {
"0": {"area":4795.764399,"bodies":1,"edges":79,"error":null,"faces":29,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","hole","circular pattern","circular pattern","cut sphere","join cylinder","cut cylinder","fillet"],"unsupported":1,"volume":13466.472104},
"1": {"area":2642.476675,"bodies":1,"edges":37,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut cylinder","join cylinder"],"unsupported":0,"volume":6991.559474},
"2": {"area":2493.96451,"bodies":1,"edges":59,"error":null,"faces":22,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet","cut sphere","rectangular pattern","cut sphere"],"unsupported":3,"volume":5020.244288},
"3": {"area":6336.456108,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut box","hole","fillet","hole"],"unsupported":0,"volume":28037.146872},
"4": {"area":5813.939544,"bodies":1,"edges":27,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet"],"unsupported":0,"volume":28462.605133},
"5": {"area":1696.077144,"bodies":1,"edges":24,"error":"boolean Cut: the operation failed: no result; the operation ","faces":12,"outcome":"failed","rejected":1,"shells":1,"steps":["box","cut cylinder","hole","cut cylinder"],"unsupported":0,"volume":3520.994026},
"6": {"area":9989.926765,"bodies":1,"edges":22,"error":"boolean Union: the operation failed: no result; the operatio","faces":9,"outcome":"failed","rejected":0,"shells":1,"steps":["box","hole","join box","join cylinder"],"unsupported":0,"volume":63836.414118},
"7": {"area":7365.885875,"bodies":3,"edges":79,"error":null,"faces":34,"outcome":"ok","rejected":0,"shells":3,"steps":["extruded profile","join box","cut cylinder","join box","circular pattern","hole"],"unsupported":0,"volume":32867.700222},
"8": {"area":2865.472238,"bodies":1,"edges":24,"error":null,"faces":10,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut cylinder"],"unsupported":0,"volume":10024.248065},
"9": {"area":2434.461264,"bodies":1,"edges":38,"error":"lumps: shell boundaries intersect or touch; cavity ownership","faces":17,"outcome":"failed","rejected":0,"shells":2,"steps":["revolved section","chamfer","fillet","join box","cut sphere","rectangular pattern","rectangular pattern","cut box"],"unsupported":4,"volume":6547.123546},
"10": {"area":2786.620097,"bodies":1,"edges":44,"error":null,"faces":18,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut box","chamfer","hole"],"unsupported":1,"volume":6668.980399},
"11": {"area":7858.300481,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","hole","rectangular pattern","fillet"],"unsupported":0,"volume":34605.048259},
"12": {"area":4668.664174,"bodies":2,"edges":62,"error":null,"faces":25,"outcome":"ok","rejected":0,"shells":2,"steps":["extruded profile","chamfer","join cylinder","join box","cut cylinder","circular pattern"],"unsupported":3,"volume":11874.471611},
"13": {"area":1857.434659,"bodies":4,"edges":78,"error":null,"faces":34,"outcome":"ok","rejected":1,"shells":4,"steps":["revolved section","cut cylinder","join box","rectangular pattern","cut cylinder","cut box","fillet","hole"],"unsupported":0,"volume":2300.910644},
"14": {"area":10553.267699,"bodies":1,"edges":43,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","fillet"],"unsupported":0,"volume":10369.009812},
"15": {"area":1974.784048,"bodies":1,"edges":40,"error":"boolean Cut: the operation failed: no result; the operation ","faces":18,"outcome":"failed","rejected":0,"shells":2,"steps":["revolved section","fillet","cut box","rectangular pattern","cut sphere","cut cylinder","cut sphere"],"unsupported":1,"volume":4781.224576},
"16": {"area":8872.873884,"bodies":3,"edges":49,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":3,"steps":["box","join cylinder","circular pattern","hole"],"unsupported":0,"volume":44953.403148},
"17": {"area":6048.425784,"bodies":1,"edges":56,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut box","shell","cut cylinder"],"unsupported":0,"volume":2968.531542},
"18": {"area":5524.583818,"bodies":4,"edges":98,"error":"boolean Cut: the operation failed: no result; the operation ","faces":44,"outcome":"failed","rejected":0,"shells":4,"steps":["extruded profile","join cylinder","circular pattern","cut cylinder","hole","cut cylinder","hole","circular pattern"],"unsupported":0,"volume":21568.016708},
"19": {"area":5938.978965,"bodies":1,"edges":74,"error":null,"faces":25,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere","shell"],"unsupported":1,"volume":2942.886331},
"20": {"area":5727.101213,"bodies":1,"edges":116,"error":null,"faces":45,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","cut box","cut cylinder","hole","circular pattern","cut box"],"unsupported":1,"volume":17142.726126},
"21": {"area":5968.473044,"bodies":2,"edges":54,"error":null,"faces":21,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","join box","hole","fillet","hole","cut box"],"unsupported":1,"volume":18926.835174},
"22": {"area":4634.464699,"bodies":2,"edges":108,"error":null,"faces":40,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","join box","join box","join box","rectangular pattern","cut cylinder"],"unsupported":0,"volume":7855.54666},
"23": {"area":1795.696192,"bodies":1,"edges":16,"error":null,"faces":7,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere"],"unsupported":0,"volume":3198.694598},
"24": {"area":13432.401544,"bodies":1,"edges":86,"error":null,"faces":35,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","chamfer","hole","cut box","cut sphere","cut sphere","shell"],"unsupported":0,"volume":9985.175049},
"25": {"area":6134.243434,"bodies":1,"edges":56,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","shell"],"unsupported":0,"volume":5828.585616},
"26": {"area":3077.407407,"bodies":1,"edges":28,"error":"boolean Union: the operation failed: no result; the operatio","faces":14,"outcome":"failed","rejected":0,"shells":1,"steps":["revolved section","join box"],"unsupported":0,"volume":5890.433103},
"27": {"area":11263.08174,"bodies":3,"edges":99,"error":null,"faces":42,"outcome":"ok","rejected":0,"shells":4,"steps":["box","chamfer","cut sphere","cut sphere","join box","circular pattern","hole","join box"],"unsupported":0,"volume":49451.079636},
"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":10195.468258,"bodies":3,"edges":75,"error":null,"faces":31,"outcome":"ok","rejected":1,"shells":3,"steps":["box","cut box","fillet","join box","circular pattern","cut box","circular pattern","hole"],"unsupported":1,"volume":56416.134415},
"32": {"area":3220.802776,"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.955387},
"33": {"area":5269.06481,"bodies":2,"edges":64,"error":null,"faces":29,"outcome":"ok","rejected":0,"shells":3,"steps":["revolved section","fillet","join box","cut cylinder","join box","chamfer","fillet","fillet"],"unsupported":2,"volume":13840.242661},
"34": {"area":1086.303472,"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},
"35": {"area":4605.630216,"bodies":1,"edges":26,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["box","fillet","join box"],"unsupported":0,"volume":13965.526982},
"36": {"area":10408.82575,"bodies":1,"edges":60,"error":null,"faces":26,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","cut cylinder","join box","fillet","join cylinder"],"unsupported":0,"volume":55729.313461},
"37": {"area":11714.822253,"bodies":1,"edges":44,"error":null,"faces":18,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","join cylinder","cut sphere","circular pattern","fillet","circular pattern","rectangular pattern"],"unsupported":3,"volume":75556.602064},
"38": {"area":11769.375817,"bodies":1,"edges":44,"error":"offset: a corner whose faces don't meet the same way after t","faces":12,"outcome":"failed","rejected":1,"shells":1,"steps":["box","hole","cut cylinder","shell"],"unsupported":0,"volume":78261.020973},
"39": {"area":1892.050815,"bodies":1,"edges":27,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","chamfer","cut sphere","rectangular pattern"],"unsupported":1,"volume":4040.62787},
"40": {"area":15548.516183,"bodies":1,"edges":49,"error":"offset: a corner whose faces don't meet the same way after t","faces":22,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","cut cylinder","join cylinder","shell"],"unsupported":0,"volume":16543.777645},
"41": {"area":7611.393446,"bodies":1,"edges":99,"error":null,"faces":35,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut sphere","shell","fillet","cut cylinder","rectangular pattern","cut sphere","join cylinder"],"unsupported":2,"volume":5973.93328},
"42": {"area":5486.881252,"bodies":1,"edges":45,"error":null,"faces":17,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut box","cut cylinder","rectangular pattern"],"unsupported":0,"volume":19095.110963},
"43": {"area":2623.031137,"bodies":1,"edges":72,"error":"boolean Union: the operation failed: no result; the operatio","faces":26,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","rectangular pattern","fillet","hole","rectangular pattern","join cylinder"],"unsupported":3,"volume":4325.21806},
"44": {"area":7972.435565,"bodies":1,"edges":36,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","shell","cut sphere"],"unsupported":0,"volume":7622.184369},
"45": {"area":14416.876392,"bodies":2,"edges":62,"error":"boolean Union: implausible result volume ","faces":29,"outcome":"failed","rejected":0,"shells":3,"steps":["revolved section","join cylinder","fillet","shell","cut box","join box"],"unsupported":1,"volume":14286.791174},
"46": {"area":3445.160599,"bodies":1,"edges":99,"error":null,"faces":31,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","join cylinder","hole","cut cylinder","rectangular pattern","cut box","cut cylinder"],"unsupported":0,"volume":6232.317431},
"47": {"area":3098.575624,"bodies":1,"edges":42,"error":null,"faces":16,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","cut sphere","fillet"],"unsupported":0,"volume":8834.29745},
"48": {"area":9651.031297,"bodies":1,"edges":66,"error":null,"faces":28,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","hole","circular pattern","join box","shell","cut box"],"unsupported":2,"volume":7044.773463},
"49": {"area":7512.5,"bodies":1,"edges":24,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["box","join box"],"unsupported":0,"volume":40546.875},
"50": {"area":9385.667614,"bodies":2,"edges":48,"error":null,"faces":21,"outcome":"ok","rejected":0,"shells":2,"steps":["extruded profile","fillet","join cylinder","join cylinder","fillet","cut box","cut cylinder"],"unsupported":2,"volume":40127.620224},
"51": {"area":3316.022299,"bodies":1,"edges":51,"error":null,"faces":21,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut box","hole","rectangular pattern","chamfer","chamfer"],"unsupported":1,"volume":6812.582331},
"52": {"area":7851.282527,"bodies":1,"edges":32,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["box","shell","cut cylinder","chamfer","fillet"],"unsupported":0,"volume":5741.788461},
"53": {"area":17168.464938,"bodies":1,"edges":110,"error":null,"faces":46,"outcome":"ok","rejected":0,"shells":4,"steps":["extruded profile","cut cylinder","shell","join cylinder","circular pattern","fillet"],"unsupported":1,"volume":16607.030819},
"54": {"area":7080.555724,"bodies":4,"edges":90,"error":null,"faces":38,"outcome":"ok","rejected":0,"shells":4,"steps":["extruded profile","join box","circular pattern","cut cylinder","cut box","join cylinder","cut cylinder"],"unsupported":3,"volume":15737.224446},
"55": {"area":6796.974648,"bodies":1,"edges":60,"error":"Body","faces":23,"outcome":"invalid","rejected":0,"shells":1,"steps":["box","fillet","cut sphere","circular pattern","rectangular pattern","cut sphere"],"unsupported":0,"volume":24741.239331},
"56": {"area":4468.944174,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut box","chamfer"],"unsupported":0,"volume":11835.9375},
"57": {"area":4678.619266,"bodies":1,"edges":63,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut sphere","chamfer","circular pattern","cut sphere","cut sphere","cut cylinder"],"unsupported":2,"volume":16016.157732},
"58": {"area":4197.04925,"bodies":1,"edges":42,"error":null,"faces":16,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","cut sphere"],"unsupported":0,"volume":12543.173615},
"59": {"area":7770.5632,"bodies":1,"edges":36,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["box","join box","join cylinder"],"unsupported":0,"volume":29735.209746},
"60": {"area":3443.32863,"bodies":1,"edges":39,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere"],"unsupported":0,"volume":11779.204752},
"61": {"area":5020.413157,"bodies":1,"edges":36,"error":"boolean Cut: the operation failed: no result; the operation ","faces":15,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","rectangular pattern"],"unsupported":0,"volume":4906.628359},
"62": {"area":8771.003707,"bodies":1,"edges":60,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","circular pattern","cut box","cut box","join cylinder"],"unsupported":2,"volume":40107.001722},
"63": {"area":3029.212486,"bodies":1,"edges":51,"error":"boolean Union: the bodies touch along faces that could not b","faces":19,"outcome":"failed","rejected":0,"shells":1,"steps":["revolved section","join box","cut cylinder","hole","cut cylinder","join box"],"unsupported":1,"volume":6571.859243},
"64": {"area":4137.422404,"bodies":1,"edges":36,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","join cylinder","hole"],"unsupported":1,"volume":8399.391489},
"65": {"area":8469.64766,"bodies":1,"edges":30,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet","rectangular pattern","circular pattern","circular pattern","fillet","cut box"],"unsupported":0,"volume":45143.068793},
"66": {"area":9222.76638,"bodies":1,"edges":99,"error":null,"faces":37,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","join box","join box","join box","cut sphere","cut box"],"unsupported":1,"volume":35217.203139},
"67": {"area":7942.099033,"bodies":2,"edges":85,"error":null,"faces":35,"outcome":"ok","rejected":0,"shells":2,"steps":["extruded profile","cut cylinder","hole","join cylinder","join box","hole","chamfer","rectangular pattern"],"unsupported":2,"volume":27607.71011},
"68": {"area":7338.675126,"bodies":1,"edges":36,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","hole","hole","rectangular pattern","rectangular pattern","fillet","circular pattern"],"unsupported":2,"volume":29780.742221},
"69": {"area":6451.122768,"bodies":1,"edges":132,"error":"offset: a corner whose faces don't meet the same way after t","faces":27,"outcome":"failed","rejected":0,"shells":3,"steps":["revolved section","join box","shell","shell"],"unsupported":0,"volume":4761.477514},
"70": {"area":3682.669157,"bodies":1,"edges":24,"error":"lumps: shell boundaries intersect or touch; cavity ownership","faces":12,"outcome":"failed","rejected":0,"shells":2,"steps":["extruded profile","join box","hole"],"unsupported":1,"volume":9155.027162},
"71": {"area":3389.533577,"bodies":1,"edges":34,"error":"boolean Union: the operation failed: no result; the operatio","faces":14,"outcome":"failed","rejected":0,"shells":1,"steps":["revolved section","cut box","cut cylinder","hole","join cylinder"],"unsupported":0,"volume":14306.661547},
"72": {"area":5770.270336,"bodies":2,"edges":46,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":2,"steps":["box","join cylinder","hole","join box","join box","cut box","cut cylinder","cut cylinder"],"unsupported":1,"volume":16081.801492},
"73": {"area":7478.071654,"bodies":2,"edges":69,"error":"boolean Cut: implausible result volume ","faces":28,"outcome":"failed","rejected":0,"shells":2,"steps":["extruded profile","join box","hole","cut cylinder","hole","rectangular pattern"],"unsupported":4,"volume":34525.602795},
"74": {"area":9294.57311,"bodies":1,"edges":22,"error":null,"faces":9,"outcome":"ok","rejected":0,"shells":1,"steps":["box","hole"],"unsupported":0,"volume":55866.183074},
"75": {"area":7893.117931,"bodies":1,"edges":27,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut cylinder","chamfer","rectangular pattern","rectangular pattern","cut box"],"unsupported":0,"volume":38757.62083},
"76": {"area":899.010133,"bodies":1,"edges":29,"error":null,"faces":13,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","fillet","cut cylinder"],"unsupported":0,"volume":1745.3677},
"77": {"area":12157.059928,"bodies":1,"edges":126,"error":null,"faces":54,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","hole","circular pattern","rectangular pattern","circular pattern","join cylinder"],"unsupported":0,"volume":64306.616746},
"78": {"area":2423.346733,"bodies":1,"edges":39,"error":"offset: a corner whose faces don't meet the same way after t","faces":15,"outcome":"failed","rejected":0,"shells":1,"steps":["revolved section","cut cylinder","cut box","shell"],"unsupported":0,"volume":3707.453171},
"79": {"area":13585.252616,"bodies":1,"edges":27,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","join cylinder"],"unsupported":0,"volume":104810.763072},
"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.811798,"bodies":2,"edges":75,"error":null,"faces":29,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","cut sphere","circular pattern","rectangular pattern","hole","join cylinder","join box","cut sphere"],"unsupported":3,"volume":13139.562234},
"82": {"area":3195.487113,"bodies":1,"edges":36,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","hole","circular pattern","cut box"],"unsupported":0,"volume":5902.162954},
"83": {"area":5125.0,"bodies":1,"edges":28,"error":"offset: a corner whose faces don't meet the same way after t","faces":12,"outcome":"failed","rejected":0,"shells":1,"steps":["box","join box","rectangular pattern","shell"],"unsupported":0,"volume":19250.0},
"84": {"area":4906.192237,"bodies":1,"edges":67,"error":null,"faces":25,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","hole","hole","cut cylinder","cut box","fillet"],"unsupported":2,"volume":14219.865875},
"85": {"area":1923.81126,"bodies":1,"edges":45,"error":"boolean Union: the operation failed: no result; the operatio","faces":19,"outcome":"failed","rejected":0,"shells":1,"steps":["revolved section","fillet","cut sphere","circular pattern","join cylinder","hole","join box"],"unsupported":1,"volume":4382.218633},
"86": {"area":7885.25656,"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.106764},
"87": {"area":4853.708989,"bodies":1,"edges":24,"error":null,"faces":10,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut box","rectangular pattern"],"unsupported":0,"volume":18568.611108},
"88": {"area":3947.799648,"bodies":1,"edges":18,"error":null,"faces":8,"outcome":"ok","rejected":0,"shells":1,"steps":["box","hole"],"unsupported":0,"volume":12788.04706},
"89": {"area":2512.5,"bodies":1,"edges":12,"error":null,"faces":6,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut box"],"unsupported":0,"volume":5906.25},
"90": {"area":9703.747183,"bodies":1,"edges":30,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","hole"],"unsupported":0,"volume":46947.022876},
"91": {"area":4061.429312,"bodies":1,"edges":36,"error":null,"faces":14,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","cut cylinder","chamfer"],"unsupported":2,"volume":9885.654049},
"92": {"area":2855.139226,"bodies":1,"edges":30,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","chamfer","cut sphere"],"unsupported":0,"volume":9814.303984},
"93": {"area":7266.404017,"bodies":7,"edges":115,"error":null,"faces":51,"outcome":"ok","rejected":0,"shells":7,"steps":["extruded profile","fillet","chamfer","join box","circular pattern","rectangular pattern","cut cylinder"],"unsupported":0,"volume":12734.393171},
"94": {"area":3504.024629,"bodies":1,"edges":73,"error":null,"faces":27,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut cylinder","hole","rectangular pattern","cut box","cut box","join box"],"unsupported":2,"volume":5899.987584},
"95": {"area":2339.366789,"bodies":1,"edges":27,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","hole","cut cylinder","cut sphere","chamfer","circular pattern"],"unsupported":1,"volume":6587.694591}
},
"notes": [
{"reason":"First record: what each seed builds on main at 1a0100cd","seeds":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95]}
]
}