mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 20:02:16 +00:00
Tests: the kernel-cases ladder is the reference for what the kernel builds; the seeded robustness goldens go
The seeded robustness goldens (tests/robustness/goldens.json, tests::robustness_goldens) recorded what truck built for seeds 0-95, so they now fail on every seed SMLib builds differently. The kernel-cases ladder (tests/kernel-cases/ladder, cargo xtask kernel-cases --ladder), whose cases each carry their own right answer, supersedes them. Gone with them: fuzz::golden, the CI goldens lane (the goldens output, SLOW_SEEDS, the re-record hint), their nextest filter and priority entries, the mutants sample and the tests/robustness/ test-data mapping in affected.py. The robustness seeds and tests::robustness stay. The docs point at the ladder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+3
-8
@@ -1,14 +1,9 @@
|
||||
# cargo-mutants (docs/mutation-testing.md). Mutants are judged by the black-box suites that will
|
||||
# outlive the kernel backend: the reference tests (tests/oracle, through the oracle_cases test)
|
||||
# and the pull-request sample of the robustness goldens (every 8th seed, as ci.yml). The
|
||||
# kernel's own unit tests are left out on purpose: a mutant only they catch is a behaviour the
|
||||
# outlive the kernel backend: the reference tests (tests/oracle, through the oracle_cases test).
|
||||
# The kernel's own unit tests are left out on purpose: a mutant only they catch is a behaviour the
|
||||
# backend-neutral suites would miss when the kernel is swapped.
|
||||
profile = "mutants"
|
||||
additional_cargo_test_args = [
|
||||
"--test", "oracle_cases", "--lib", "--", "--include-ignored",
|
||||
"--test", "oracle_cases", "--", "--include-ignored",
|
||||
"oracle_suite",
|
||||
"robustness_goldens::seed_00", "robustness_goldens::seed_08", "robustness_goldens::seed_16",
|
||||
"robustness_goldens::seed_24", "robustness_goldens::seed_32", "robustness_goldens::seed_40",
|
||||
"robustness_goldens::seed_48", "robustness_goldens::seed_56", "robustness_goldens::seed_64",
|
||||
"robustness_goldens::seed_72", "robustness_goldens::seed_80", "robustness_goldens::seed_88",
|
||||
]
|
||||
|
||||
@@ -12,7 +12,6 @@ not (
|
||||
(package(solidcraft-engine) & (
|
||||
test(=tests::hostile_params_never_panic)
|
||||
| test(/^tests::robustness::/)
|
||||
| test(/^tests::robustness_goldens::seed_/)
|
||||
| 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)
|
||||
@@ -44,13 +43,12 @@ threads-required = 12
|
||||
priority = 100
|
||||
|
||||
# The slowest tests start first, so a shard does not end with one of them running alone on a
|
||||
# core while the others sit idle (seed_81 alone takes about 20 minutes under CI load). Medians
|
||||
# from the CI test timings (the junit artifacts), slowest first; a test over ~100 s belongs here.
|
||||
# core while the others sit idle. Medians from the CI test timings (the junit artifacts), slowest
|
||||
# first; a test over ~100 s belongs here.
|
||||
[[profile.ci.overrides]]
|
||||
filter = """
|
||||
(package(solidcraft-engine) & (
|
||||
test(/^tests::robustness_goldens::seed_(81|45|20|24|95|02|57|40|18|48|32|46|85|61)$/)
|
||||
| test(/^tests::robustness::/)
|
||||
test(/^tests::robustness::/)
|
||||
| test(=tests::hostile_params_never_panic)
|
||||
| test(=cmd::clearance::tests::distances_are_exact_on_edges_and_curved_faces)
|
||||
| test(=cmd::clipboard::tests::a_pasted_coil_joins_the_pasted_body)
|
||||
@@ -92,14 +90,13 @@ threads-required = "num-cpus"
|
||||
priority = 100
|
||||
|
||||
[[profile.local-ci.overrides]]
|
||||
filter = "(package(solidcraft-engine) & test(=tests::robustness_goldens::seed_81)) | (package(solidcraft-kernel) & test(=tspline::tests::hostile_tsplines_never_panic))"
|
||||
filter = "package(solidcraft-kernel) & test(=tspline::tests::hostile_tsplines_never_panic)"
|
||||
priority = 99
|
||||
|
||||
[[profile.local-ci.overrides]]
|
||||
filter = """
|
||||
(package(solidcraft-engine) & (
|
||||
test(/^tests::robustness_goldens::seed_(81|45|20|24|95|02|57|40|18|48|32|46|85|61)$/)
|
||||
| test(/^tests::robustness::/)
|
||||
test(/^tests::robustness::/)
|
||||
| test(=tests::hostile_params_never_panic)
|
||||
| test(=cmd::clearance::tests::distances_are_exact_on_edges_and_curved_faces)
|
||||
| test(=cmd::clipboard::tests::a_pasted_coil_joins_the_pasted_body)
|
||||
|
||||
@@ -107,7 +107,6 @@ jobs:
|
||||
outputs:
|
||||
native: ${{ steps.paths.outputs.native }}
|
||||
kernel: ${{ steps.paths.outputs.kernel }}
|
||||
goldens: ${{ steps.paths.outputs.goldens }}
|
||||
windows: ${{ steps.paths.outputs.windows }}
|
||||
# The GPU path tests run: native, and the whole workspace or solidcraft-gpu selected. The
|
||||
# job's `if` and the `ci` job's check both read this, so they cannot disagree.
|
||||
@@ -142,7 +141,6 @@ jobs:
|
||||
run: |
|
||||
native=false
|
||||
kernel=false
|
||||
goldens=false
|
||||
windows=false
|
||||
full=true
|
||||
packages=
|
||||
@@ -218,13 +216,6 @@ jobs:
|
||||
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
|
||||
# What a pull request re-checks the goldens for: the kernel, truck and the goldens
|
||||
# themselves (a re-record is checked before it is queued). The goldens are frozen, so
|
||||
# on any other change they would only re-check unchanged results; the merge queue,
|
||||
# main and the nightly run every seed before anything lands.
|
||||
case "$path" in
|
||||
crates/kernel/*|vendor/truck-*|tests/robustness/*) goldens=true ;;
|
||||
esac
|
||||
done < "$RUNNER_TEMP/ci-paths"
|
||||
# Computed as for a merge group, then turned off below (the fast lane).
|
||||
windows_paths "$RUNNER_TEMP/ci-paths" "$BASE_SHA...$HEAD_SHA"
|
||||
@@ -232,10 +223,6 @@ jobs:
|
||||
# selected means no code changed.
|
||||
select_packages "$RUNNER_TEMP/ci-paths"
|
||||
if [ "$full" = true ] || [ -n "$packages" ]; then native=true; fi
|
||||
# The goldens are solidcraft-engine tests: its tests must be in the archive.
|
||||
if [ "$goldens" = true ] && [ "$full" != true ]; then
|
||||
case " $packages " in *" solidcraft-engine "*) ;; *) packages="$packages solidcraft-engine" ;; esac
|
||||
fi
|
||||
fi
|
||||
# The reference tests replay recipes through solidcraft-cli: needed when it, or anything
|
||||
# it depends on, is selected.
|
||||
@@ -250,13 +237,12 @@ jobs:
|
||||
doctests=$native
|
||||
if [ "$EVENT_NAME" = pull_request ]; then windows=false; oracle=false; doctests=false; fi
|
||||
# Until a maintainer adds approved-for-ci, a pull request gets a quick compile instead of
|
||||
# Clippy, the tests, the doc tests and the goldens.
|
||||
# Clippy, the tests and the doc tests.
|
||||
quick=false
|
||||
if [ "$EVENT_NAME" = pull_request ] && [ "$APPROVED" != true ]; then
|
||||
quick=$native
|
||||
native=false
|
||||
oracle=false
|
||||
goldens=false
|
||||
fi
|
||||
# A push that only merges main into the change (Merge branch 'main', Update branch) when
|
||||
# the change itself already passed: nothing new to test here, and main tests the merged
|
||||
@@ -269,7 +255,6 @@ jobs:
|
||||
if [ -n "$carried" ]; then
|
||||
echo "Only a merge of main into a change that passed in $carried: repository checks only."
|
||||
native=false
|
||||
goldens=false
|
||||
fi
|
||||
fi
|
||||
# The GPU path tests (their own job) run with the other tests, when solidcraft-gpu is
|
||||
@@ -283,7 +268,6 @@ jobs:
|
||||
echo "native=$native"
|
||||
echo "gpu=$gpu"
|
||||
echo "kernel=$kernel"
|
||||
echo "goldens=$goldens"
|
||||
echo "windows=$windows"
|
||||
echo "oracle=$oracle"
|
||||
echo "doctests=$doctests"
|
||||
@@ -510,13 +494,8 @@ jobs:
|
||||
- name: Run tests
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
GOLDENS: ${{ needs.changes.outputs.goldens }}
|
||||
FULL: ${{ needs.changes.outputs.full }}
|
||||
PACKAGES: ${{ needs.changes.outputs.packages }}
|
||||
# The robustness golden seeds a pull request skips: each takes one to twenty minutes under
|
||||
# CI load, together about four fifths of the goldens' time. Pushes to main, the merge
|
||||
# queue and the nightly run every seed.
|
||||
SLOW_SEEDS: 81|45|20|24|95|02|57|40|18|48|32|46|85|61|77|34|14
|
||||
run: |
|
||||
# Pull requests skip the heavy tests (default-filter in .config/nextest.toml); `default()`
|
||||
# below is that filter, so a pull request still runs only what it ran before.
|
||||
@@ -530,29 +509,16 @@ jobs:
|
||||
# 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.
|
||||
base='not (package(solidcraft-kernel) & binary(boolean_props))'
|
||||
elif [ "$GOLDENS" = true ]; then
|
||||
# A pull request that can change a robustness golden (tests/robustness/goldens.json)
|
||||
# also checks every seed but the slow ones, in the same run so they share its cores.
|
||||
base="(default() & not (binary(action_matrix) & test(=action_matrix_sample))) | (package(solidcraft-engine) & test(/^tests::robustness_goldens::/) & not test(/^tests::robustness_goldens::seed_($SLOW_SEEDS)\$/))"
|
||||
fi
|
||||
junit=${CARGO_TARGET_DIR:-target}/nextest/ci/junit.xml
|
||||
rm -f "$junit"
|
||||
# Pull requests test the affected packages (scripts/ci/affected.py).
|
||||
scope=(--workspace)
|
||||
if [ "$FULL" != true ]; then scope=(); for p in $PACKAGES; do scope+=(-p "$p"); done; fi
|
||||
status=0
|
||||
# The GPU path tracer's tests run in their own job (GPU path tests): one at a time, they
|
||||
# took about 36 of this job's 45 minutes.
|
||||
cargo nextest run "${scope[@]}" --cargo-profile ci --locked --profile ci --no-tests=pass \
|
||||
--ignore-default-filter -E "($base) & not (package(=solidcraft-gpu) & test(/^path::/))" || status=1
|
||||
if [ "$status" != 0 ]; then
|
||||
# A failed golden gets the hint to re-record it (junit.xml lists the failures).
|
||||
if [ -f "$junit" ] && tr '\n' ' ' < "$junit" | grep -q '<testcase name="tests::robustness_goldens::[^"]*"[^>]*>[[:space:]]*<failure'; then
|
||||
echo "::error::A robustness golden changed. If the new result is intended, re-record it in this pull request and say why:"
|
||||
echo "::error::ROBUSTNESS_GOLDENS_BLESS=\"why it changed\" cargo test --profile ci -p solidcraft-engine --lib robustness_goldens:: (see docs/robustness.md)"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
--ignore-default-filter -E "($base) & not (package(=solidcraft-gpu) & test(/^path::/))"
|
||||
- name: Compiler cache statistics
|
||||
if: always()
|
||||
run: sccache --show-stats
|
||||
|
||||
+3
-5
@@ -158,9 +158,8 @@ the most complete description of the house style.
|
||||
| UI scenarios | `crates/ui-egui/tests/scenarios/*.json` | part of `cargo test` |
|
||||
| Reference tests | `cargo xtask oracle` | `tests/oracle/` (see below) |
|
||||
|
||||
A geometry change that changes the robustness goldens re-records them in the same pull request:
|
||||
`ROBUSTNESS_GOLDENS_BLESS="why it changed" cargo test --profile ci -p solidcraft-engine --lib robustness_goldens::`
|
||||
(see [docs/robustness.md](docs/robustness.md)); CI runs every seed on pull requests that touch geometry.
|
||||
The reference for what the kernel builds is the kernel-cases ladder (`tests/kernel-cases/ladder/`,
|
||||
`cargo xtask kernel-cases --ladder`, [docs/kernel-ladder.md](docs/kernel-ladder.md)).
|
||||
A change to the persistent names of faces and edges, or to what a stored name finds, re-records the
|
||||
corpus's names in the same pull request, saying why:
|
||||
`NAMING_CORPUS_BLESS=1 cargo test --profile ci -p solidcraft-cli --test naming_corpus` (`tests/naming/`).
|
||||
@@ -222,8 +221,7 @@ Pull requests skip a few heavy tests (one to twelve minutes each), which run in
|
||||
Check that validation 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` and
|
||||
the seeds of `tests::robustness_goldens` (`seed_00` … `seed_95`; pull requests that change the kernel, truck or the goldens run all but the slowest, `SLOW_SEEDS` in `ci.yml`),
|
||||
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness`,
|
||||
`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
|
||||
|
||||
@@ -293,42 +293,6 @@ pub fn run_with(seed: u64, max_steps: usize, on_step: &mut dyn FnMut(&Step)) ->
|
||||
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);
|
||||
|
||||
@@ -359,7 +359,6 @@ mod pleat_join;
|
||||
mod primitive_points;
|
||||
mod rim_cut;
|
||||
mod robustness;
|
||||
mod robustness_goldens;
|
||||
mod sphere_lens;
|
||||
mod spiral_join;
|
||||
mod step_import_options;
|
||||
|
||||
@@ -1,465 +0,0 @@
|
||||
//! 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::` (docs/robustness.md); run
|
||||
//! it with `cargo test`, whose tests share one process and so one lock on the file, not with
|
||||
//! nextest. The reason goes into the file's `notes` with the seeds it changed.
|
||||
//!
|
||||
//! Each seed is its own test (`seed_00` … `seed_95`), so the runner spreads them over the CI
|
||||
//! shards and their cores: a few seeds take minutes, and grouped four ways they made one test
|
||||
//! of half an hour. Pull requests that can change them run every seed but the slowest few
|
||||
//! (`SLOW_SEEDS` in ci.yml); the merge queue, pushes to main and the nightly run every seed.
|
||||
|
||||
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("; "))
|
||||
}
|
||||
|
||||
/// Only Windows needs an environment split; other OS policies remain compatible.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Target<'a> {
|
||||
os: &'a str,
|
||||
env: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Target<'a> {
|
||||
fn current() -> Self {
|
||||
// cfg! describes the compilation target, including when cross-compiling.
|
||||
let env = if cfg!(target_env = "msvc") {
|
||||
"msvc"
|
||||
} else if cfg!(target_env = "gnu") {
|
||||
"gnu"
|
||||
} else {
|
||||
"other"
|
||||
};
|
||||
Self { os: std::env::consts::OS, env }
|
||||
}
|
||||
|
||||
fn platform(self) -> Option<&'a str> {
|
||||
match (self.os, self.env) {
|
||||
("windows", "msvc") => Some("windows"), // Existing MSVC-verified records.
|
||||
("windows", "gnu") => Some("windows-gnu"),
|
||||
("windows", _) => None,
|
||||
(os, _) => Some(os),
|
||||
}
|
||||
}
|
||||
|
||||
fn recording_platform(self, file: &Value) -> Result<Option<&'a str>, String> {
|
||||
let platform = self.platform();
|
||||
if self.os == "windows" {
|
||||
return platform.map(Some).ok_or_else(|| format!("no recording policy for Windows target_env={}; define one before blessing", self.env));
|
||||
}
|
||||
Ok(platform.filter(|p| file["platforms"][*p]["seeds"].is_object()))
|
||||
}
|
||||
}
|
||||
|
||||
/// An override replaces the entire seed record, never individual fields.
|
||||
/// The second return value identifies the selected reference in diagnostics.
|
||||
fn expected<'a, 't>(file: &'a Value, seed: u64, target: Target<'t>) -> (&'a Value, &'t str) {
|
||||
let key = seed.to_string();
|
||||
if let Some(platform) = target.platform()
|
||||
&& let Some(record) = file.get("platforms").and_then(|p| p.get(platform)).and_then(|p| p.get("seeds")).and_then(|s| s.get(&key))
|
||||
{
|
||||
return (record, platform);
|
||||
}
|
||||
(&file["seeds"][key], "shared")
|
||||
}
|
||||
|
||||
fn difference(file: &Value, seed: u64, got: &Value, target: Target<'_>) -> Option<String> {
|
||||
let (want, group) = expected(file, seed, target);
|
||||
let change = if want.is_null() { Some("new".to_string()) } else { differs(want, got) };
|
||||
change.map(|d| format!("seed {seed} [target_os={}, target_env={}, reference={group}]: {d}", target.os, target.env))
|
||||
}
|
||||
|
||||
/// In-memory recording seam: regression tests never write the real reference file.
|
||||
fn record(file: &mut Value, seed: u64, got: &Value, reason: &str, target: Target<'_>) -> Result<bool, String> {
|
||||
if reason.trim().is_empty() {
|
||||
return Err("ROBUSTNESS_GOLDENS_BLESS needs the reason for the change".to_string());
|
||||
}
|
||||
let platform = target.recording_platform(file)?;
|
||||
if difference(file, seed, got, target).is_none() {
|
||||
return Ok(false);
|
||||
}
|
||||
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\"."
|
||||
);
|
||||
}
|
||||
if let Some(platform) = platform {
|
||||
file["platforms"][platform]["seeds"][seed.to_string()] = got.clone();
|
||||
} else {
|
||||
file["seeds"][seed.to_string()] = got.clone();
|
||||
}
|
||||
let mut notes = file["notes"].as_array().cloned().unwrap_or_default();
|
||||
// Coalesce only the same reason AND destination, including shared vs platform.
|
||||
// Windows destinations encode the environment, so GNU and MSVC cannot coalesce.
|
||||
let destination = platform.map(|p| json!(p));
|
||||
match notes.last_mut() {
|
||||
Some(last) if last["reason"] == json!(reason) && last.get("platform") == destination.as_ref() => {
|
||||
let mut all: Vec<u64> = last["seeds"].as_array().into_iter().flatten().filter_map(Value::as_u64).chain([seed]).collect();
|
||||
all.sort_unstable();
|
||||
all.dedup();
|
||||
last["seeds"] = json!(all);
|
||||
}
|
||||
_ => {
|
||||
let mut note = json!({"reason": reason, "seeds": [seed]});
|
||||
if let Some(platform) = platform {
|
||||
note["platform"] = json!(platform);
|
||||
}
|
||||
notes.push(note);
|
||||
}
|
||||
}
|
||||
file["notes"] = Value::Array(notes);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn serialize(file: &Value) -> String {
|
||||
// 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 += " },\n";
|
||||
if let Some(platforms) = file.get("platforms") {
|
||||
s += &format!(" \"platforms\": {platforms},\n");
|
||||
}
|
||||
s += &format!(" \"notes\": [\n{}\n ]\n}}\n", notes.join(",\n"));
|
||||
s
|
||||
}
|
||||
|
||||
fn check(seed: u64) {
|
||||
let got = fuzz::golden(seed, MAX_STEPS);
|
||||
let _lock = FILE.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut file = load();
|
||||
let target = Target::current();
|
||||
if let Ok(reason) = std::env::var("ROBUSTNESS_GOLDENS_BLESS") {
|
||||
if record(&mut file, seed, &got, &reason, target).unwrap_or_else(|e| panic!("{e}")) {
|
||||
std::fs::write(path(), serialize(&file)).unwrap_or_else(|e| panic!("{}: {e}", path().display()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
let change = difference(&file, seed, &got, target);
|
||||
assert!(
|
||||
change.is_none(),
|
||||
"build differs from tests/robustness/goldens.json (if that is intended, record it with ROBUSTNESS_GOLDENS_BLESS=\"why\" and say why in the pull request):\n{}",
|
||||
change.unwrap_or_default()
|
||||
);
|
||||
}
|
||||
|
||||
const MSVC: Target<'_> = Target { os: "windows", env: "msvc" };
|
||||
const GNU: Target<'_> = Target { os: "windows", env: "gnu" };
|
||||
const LINUX: Target<'_> = Target { os: "linux", env: "gnu" };
|
||||
|
||||
fn policy_fixture() -> Value {
|
||||
json!({
|
||||
"seeds": {"1": {"volume": 10, "faces": 2, "outcome": "ok"}, "2": {"volume": 20}},
|
||||
"platforms": {
|
||||
"windows": {"seeds": {"1": {"volume": 11, "outcome": "failed"}}, "verification": "MSVC"},
|
||||
"macos": {"seeds": {"1": {"volume": 12}}}
|
||||
},
|
||||
"notes": [{"platform": "windows", "reason": "same reason", "seeds": [1]}]
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_selects_msvc_and_gnu_overrides_with_shared_fallback() {
|
||||
let mut file = policy_fixture();
|
||||
assert_eq!(expected(&file, 1, MSVC), (&file["platforms"]["windows"]["seeds"]["1"], "windows"));
|
||||
assert_eq!(expected(&file, 1, GNU), (&file["seeds"]["1"], "shared"));
|
||||
file["platforms"]["windows-gnu"]["seeds"]["1"] = json!({"volume": 13});
|
||||
assert_eq!(expected(&file, 1, GNU), (&file["platforms"]["windows-gnu"]["seeds"]["1"], "windows-gnu"));
|
||||
for target in [MSVC, GNU] {
|
||||
assert_eq!(expected(&file, 2, target), (&file["seeds"]["2"], "shared"));
|
||||
}
|
||||
// A platform record is whole, including failure outcomes and missing fields.
|
||||
assert!(expected(&file, 1, MSVC).0.get("faces").is_none());
|
||||
assert_eq!(expected(&file, 1, MSVC).0["outcome"], "failed");
|
||||
assert!(differs(expected(&file, 1, MSVC).0, &file["seeds"]["1"]).is_some());
|
||||
assert!(expected(&file, 1, GNU).0.get("faces").is_none());
|
||||
assert!(expected(&file, 99, GNU).0.is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_preserves_non_windows_and_refuses_unknown_windows_recording() {
|
||||
let mut file = policy_fixture();
|
||||
let macos = Target { os: "macos", env: "" };
|
||||
assert_eq!(expected(&file, 1, LINUX), (&file["seeds"]["1"], "shared"));
|
||||
assert_eq!(expected(&file, 1, macos), (&file["platforms"]["macos"]["seeds"]["1"], "macos"));
|
||||
assert_eq!(LINUX.recording_platform(&file), Ok(None));
|
||||
assert_eq!(macos.recording_platform(&file), Ok(Some("macos")));
|
||||
for env in ["", "other", "new-env"] {
|
||||
let unknown = Target { os: "windows", env };
|
||||
let before = file.clone();
|
||||
assert_eq!(expected(&file, 1, unknown), (&file["seeds"]["1"], "shared"));
|
||||
assert!(record(&mut file, 1, &json!({"volume": 30}), "why", unknown).is_err());
|
||||
assert_eq!(file, before);
|
||||
}
|
||||
assert_eq!(expected(&file, 1, Target { os: "future-os", env: "gnu" }).1, "shared");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_records_only_changed_gnu_seeds_and_preserves_other_destinations_and_notes() {
|
||||
let mut file = policy_fixture();
|
||||
let before = file.clone();
|
||||
let got = json!({"volume": 30, "outcome": "failed", "error": "known failure"});
|
||||
assert_eq!(record(&mut file, 2, &got, "same reason", GNU), Ok(true));
|
||||
assert_eq!(file["seeds"], before["seeds"]);
|
||||
assert_eq!(file["platforms"]["windows"], before["platforms"]["windows"]);
|
||||
assert_eq!(file["platforms"]["macos"], before["platforms"]["macos"]);
|
||||
assert_eq!(file["notes"][0], before["notes"][0]);
|
||||
assert_eq!(file["notes"][1], json!({"platform": "windows-gnu", "reason": "same reason", "seeds": [2]}));
|
||||
assert_eq!(expected(&file, 2, GNU), (&got, "windows-gnu"));
|
||||
assert!(file["platforms"]["windows-gnu"]["seeds"].get("1").is_none());
|
||||
assert_eq!(record(&mut file, 1, &got, "same reason", GNU), Ok(true));
|
||||
assert_eq!(file["notes"][1]["seeds"], json!([1, 2]));
|
||||
let unchanged = file.clone();
|
||||
assert_eq!(record(&mut file, 1, &got, "same reason", GNU), Ok(false));
|
||||
assert_eq!(file, unchanged);
|
||||
assert_eq!(record(&mut file, 1, &json!({"volume": 31}), "same reason", GNU), Ok(true));
|
||||
assert_eq!(file["notes"][1]["seeds"], json!([1, 2]));
|
||||
assert!(expected(&file, 1, GNU).0.get("error").is_none());
|
||||
assert_eq!(serde_json::from_str::<Value>(&serialize(&file)).ok().as_ref(), Some(&file));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_isolates_msvc_recording_even_without_a_namespace() {
|
||||
for target in [MSVC, GNU] {
|
||||
let mut file = json!({"seeds": {"1": {"volume": 10}}, "notes": []});
|
||||
let before = file.clone();
|
||||
assert_eq!(record(&mut file, 1, &json!({"volume": 10}), "why", target), Ok(false));
|
||||
assert_eq!(file, before); // No spurious namespace or note for a matching fallback.
|
||||
assert_eq!(record(&mut file, 1, &json!({"volume": 11}), "why", target), Ok(true));
|
||||
assert_eq!(file["seeds"], before["seeds"]);
|
||||
assert_eq!(expected(&file, 1, target).0["volume"], 11);
|
||||
assert_eq!(file["notes"][0]["platform"], json!(target.platform()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_notes_never_coalesce_across_destinations_in_either_direction() {
|
||||
let mut file = policy_fixture();
|
||||
for target in [GNU, MSVC, LINUX, GNU, LINUX, MSVC] {
|
||||
let previous_notes = file["notes"].as_array().cloned().unwrap_or_default();
|
||||
let got = json!({"volume": 100 + previous_notes.len()});
|
||||
assert_eq!(record(&mut file, 1, &got, "same reason", target), Ok(true));
|
||||
let notes = file["notes"].as_array().cloned().unwrap_or_default();
|
||||
assert_eq!(notes.len(), previous_notes.len() + 1);
|
||||
assert_eq!(¬es[..previous_notes.len()], previous_notes);
|
||||
}
|
||||
let before = file["notes"].clone();
|
||||
assert_eq!(record(&mut file, 2, &json!({"volume": 50}), "different reason", MSVC), Ok(true));
|
||||
assert_eq!(file["notes"].as_array().map(Vec::len), before.as_array().map(|n| n.len() + 1));
|
||||
let before = file.clone();
|
||||
assert!(record(&mut file, 1, &json!({"volume": 99}), " ", GNU).is_err());
|
||||
assert_eq!(file, before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_comparator_stays_strict_and_diagnostics_name_the_selected_reference() {
|
||||
let want = json!({"volume": 100.0, "area": 10.0, "faces": 2, "outcome": "ok", "steps": ["box"], "error": null, "rejected": 0, "unsupported": 0});
|
||||
let mut got = want.clone();
|
||||
got["volume"] = json!(100.00005);
|
||||
got["area"] = json!(10.000005);
|
||||
assert_eq!(differs(&want, &got), None);
|
||||
for (field, value) in [
|
||||
("volume", json!(100.001)),
|
||||
("area", json!(10.001)),
|
||||
("faces", json!(3)),
|
||||
("outcome", json!("failed")),
|
||||
("steps", json!(["hole"])),
|
||||
("error", json!("refused")),
|
||||
("rejected", json!(1)),
|
||||
("unsupported", json!(1)),
|
||||
] {
|
||||
let mut got = want.clone();
|
||||
got[field] = value;
|
||||
assert!(differs(&want, &got).is_some(), "{field}");
|
||||
}
|
||||
let file = policy_fixture();
|
||||
let diagnostic = difference(&file, 1, &json!({"volume": 100}), GNU).unwrap_or_default();
|
||||
assert!(diagnostic.contains("target_os=windows, target_env=gnu, reference=shared"), "{diagnostic}");
|
||||
let diagnostic = difference(&file, 1, &json!({"volume": 100}), MSVC).unwrap_or_default();
|
||||
assert!(diagnostic.contains("target_env=msvc, reference=windows"), "{diagnostic}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_non_windows_recording_keeps_existing_os_destination_and_shared_notes() {
|
||||
let mut file = policy_fixture();
|
||||
let before = file.clone();
|
||||
let got = json!({"volume": 40});
|
||||
let macos = Target { os: "macos", env: "" };
|
||||
assert_eq!(record(&mut file, 1, &got, "same reason", macos), Ok(true));
|
||||
assert_eq!(file["seeds"], before["seeds"]);
|
||||
assert_eq!(file["platforms"]["windows"], before["platforms"]["windows"]);
|
||||
assert_eq!(expected(&file, 1, macos), (&got, "macos"));
|
||||
let platforms = file["platforms"].clone();
|
||||
assert_eq!(record(&mut file, 1, &got, "same reason", LINUX), Ok(true));
|
||||
assert_eq!(file["platforms"], platforms);
|
||||
assert_eq!(file["notes"][2], json!({"reason": "same reason", "seeds": [1]}));
|
||||
assert_eq!(record(&mut file, 2, &got, "same reason", LINUX), Ok(true));
|
||||
assert_eq!(file["notes"][2]["seeds"], json!([1, 2]));
|
||||
}
|
||||
|
||||
/// One test per seed, and the list of them.
|
||||
macro_rules! seeds {
|
||||
($($name:ident = $seed:literal),* $(,)?) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
check($seed);
|
||||
}
|
||||
)*
|
||||
const LISTED: &[u64] = &[$($seed),*];
|
||||
};
|
||||
}
|
||||
|
||||
seeds!(
|
||||
seed_00 = 0,
|
||||
seed_01 = 1,
|
||||
seed_02 = 2,
|
||||
seed_03 = 3,
|
||||
seed_04 = 4,
|
||||
seed_05 = 5,
|
||||
seed_06 = 6,
|
||||
seed_07 = 7,
|
||||
seed_08 = 8,
|
||||
seed_09 = 9,
|
||||
seed_10 = 10,
|
||||
seed_11 = 11,
|
||||
seed_12 = 12,
|
||||
seed_13 = 13,
|
||||
seed_14 = 14,
|
||||
seed_15 = 15,
|
||||
seed_16 = 16,
|
||||
seed_17 = 17,
|
||||
seed_18 = 18,
|
||||
seed_19 = 19,
|
||||
seed_20 = 20,
|
||||
seed_21 = 21,
|
||||
seed_22 = 22,
|
||||
seed_23 = 23,
|
||||
seed_24 = 24,
|
||||
seed_25 = 25,
|
||||
seed_26 = 26,
|
||||
seed_27 = 27,
|
||||
seed_28 = 28,
|
||||
seed_29 = 29,
|
||||
seed_30 = 30,
|
||||
seed_31 = 31,
|
||||
seed_32 = 32,
|
||||
seed_33 = 33,
|
||||
seed_34 = 34,
|
||||
seed_35 = 35,
|
||||
seed_36 = 36,
|
||||
seed_37 = 37,
|
||||
seed_38 = 38,
|
||||
seed_39 = 39,
|
||||
seed_40 = 40,
|
||||
seed_41 = 41,
|
||||
seed_42 = 42,
|
||||
seed_43 = 43,
|
||||
seed_44 = 44,
|
||||
seed_45 = 45,
|
||||
seed_46 = 46,
|
||||
seed_47 = 47,
|
||||
seed_48 = 48,
|
||||
seed_49 = 49,
|
||||
seed_50 = 50,
|
||||
seed_51 = 51,
|
||||
seed_52 = 52,
|
||||
seed_53 = 53,
|
||||
seed_54 = 54,
|
||||
seed_55 = 55,
|
||||
seed_56 = 56,
|
||||
seed_57 = 57,
|
||||
seed_58 = 58,
|
||||
seed_59 = 59,
|
||||
seed_60 = 60,
|
||||
seed_61 = 61,
|
||||
seed_62 = 62,
|
||||
seed_63 = 63,
|
||||
seed_64 = 64,
|
||||
seed_65 = 65,
|
||||
seed_66 = 66,
|
||||
seed_67 = 67,
|
||||
seed_68 = 68,
|
||||
seed_69 = 69,
|
||||
seed_70 = 70,
|
||||
seed_71 = 71,
|
||||
seed_72 = 72,
|
||||
seed_73 = 73,
|
||||
seed_74 = 74,
|
||||
seed_75 = 75,
|
||||
seed_76 = 76,
|
||||
seed_77 = 77,
|
||||
seed_78 = 78,
|
||||
seed_79 = 79,
|
||||
seed_80 = 80,
|
||||
seed_81 = 81,
|
||||
seed_82 = 82,
|
||||
seed_83 = 83,
|
||||
seed_84 = 84,
|
||||
seed_85 = 85,
|
||||
seed_86 = 86,
|
||||
seed_87 = 87,
|
||||
seed_88 = 88,
|
||||
seed_89 = 89,
|
||||
seed_90 = 90,
|
||||
seed_91 = 91,
|
||||
seed_92 = 92,
|
||||
seed_93 = 93,
|
||||
seed_94 = 94,
|
||||
seed_95 = 95,
|
||||
);
|
||||
|
||||
/// Every seed in 0..SEEDS has its test, once, and every recorded seed is one of them.
|
||||
#[test]
|
||||
fn every_seed_has_a_test() {
|
||||
assert_eq!(LISTED, (0..SEEDS).collect::<Vec<_>>().as_slice());
|
||||
let file = load();
|
||||
let recorded: Vec<u64> = file["seeds"].as_object().into_iter().flatten().filter_map(|(k, _)| k.parse().ok()).collect();
|
||||
assert!(recorded.iter().all(|s| *s < SEEDS), "{recorded:?}");
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Shell regressions found by the robustness fuzzer (tests/robustness/goldens.json).
|
||||
//! Shell regressions found by the robustness fuzzer (docs/robustness.md).
|
||||
//!
|
||||
//! The box-on-box designs below have a saddle corner: where the top box's left face meets the
|
||||
//! lower box's back face, four planar faces meet with convex and concave edges in turn (the
|
||||
|
||||
@@ -110,9 +110,7 @@ settings in `.config/nextest.toml`), the slowest tests first; doc tests run in t
|
||||
Pull requests skip a few heavy tests (each takes one to twelve 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` and
|
||||
the seeds of `tests::robustness_goldens` (`seed_00` … `seed_95`; pull requests that change the kernel, truck or
|
||||
the goldens run all but the slowest, `SLOW_SEEDS` in `ci.yml`),
|
||||
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness`,
|
||||
`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
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
SolidCraft will move from truck to solidcore (#1597). The tests that have to catch regressions
|
||||
across that switch are the ones that don't depend on the kernel: the reference tests
|
||||
([docs/oracle.md](oracle.md)), the robustness goldens ([docs/robustness.md](robustness.md)) and
|
||||
the engine's command tests. Most of the kernel's own unit tests call truck-specific code and go
|
||||
away with it.
|
||||
([docs/oracle.md](oracle.md)) and the engine's command tests. The reference for what the kernel
|
||||
builds is the kernel-cases ladder (`tests/kernel-cases/ladder/`,
|
||||
[docs/kernel-ladder.md](kernel-ladder.md)). Most of the kernel's own unit tests call
|
||||
truck-specific code and go away with it.
|
||||
|
||||
Mutation testing checks those suites. [cargo-mutants](https://mutants.rs) makes small changes to
|
||||
the source, one at a time (`*` becomes `/`, a condition is negated, a function returns a default
|
||||
@@ -18,7 +19,6 @@ nobody would notice, and solidcore could get the same thing wrong without anyone
|
||||
- `oracle_suite` (`apps/solidcraft-cli/tests/oracle_cases.rs`): every case in `tests/oracle` with
|
||||
expected values and no `known_failure` must pass, as `cargo xtask oracle` requires. It is
|
||||
`#[ignore]`d so CI doesn't replay the oracle twice.
|
||||
- The pull-request sample of the robustness goldens: every 8th seed, as `ci.yml` runs it.
|
||||
|
||||
The kernel's unit tests are left out on purpose. A mutant that only they catch marks a behaviour
|
||||
the black-box suites would miss once the kernel is swapped.
|
||||
|
||||
+10
-53
@@ -36,64 +36,22 @@ 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
|
||||
## Reference results
|
||||
|
||||
`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 can change them (the
|
||||
kernel, engine, doc, sketch and geom crates) run every seed but the slowest few (`SLOW_SEEDS` in
|
||||
`.github/workflows/ci.yml`, about four fifths of their time); the merge queue, pushes to `main`
|
||||
and the nightly run them all. Each seed is its own test
|
||||
(`seed_00` … `seed_95`), so the CI shards and their cores share them out.
|
||||
|
||||
Reference selection uses the compilation target's operating system and environment.
|
||||
On Windows, `platforms.windows.seeds` contains the MSVC-verified records;
|
||||
`platforms.windows-gnu.seeds`, when present, contains GNU records. An override
|
||||
replaces the whole seed record. Seeds missing from the selected group use the shared
|
||||
record, never another Windows group's record. Non-Windows targets keep their existing
|
||||
OS-group selection and shared fallback. Unknown Windows environments compare with the
|
||||
shared records and refuse recording until an explicit policy is defined.
|
||||
All 96 seeds and the strict measurement and topology tolerances remain unchanged.
|
||||
|
||||
The Windows records for seeds 1, 23 and 34 were independently reproduced on unmodified
|
||||
main `1e984c8e`; seed 94 was independently reproduced on unmodified main `c5992386`
|
||||
after the extrusion rebase. Both match the extrusion branch. Seed 13's was recorded on
|
||||
unmodified main `65e6d9da`; seed 23's was re-recorded after #1653. Seed 36's, recorded
|
||||
on main `79a843c9`, holds a failure: on Windows (and macOS) its last step leaves a body that fails the
|
||||
validity check, where Linux builds it ok (#3583). The record only makes a further change
|
||||
show; it does not accept the failure. Recording on a platform with such entries updates its
|
||||
changed seeds and preserves shared references. A platform record replaces the shared
|
||||
one whole, so a change that moves a seed with a platform record needs that seed checked
|
||||
on that platform too: seed 67's Windows record went stale this way after #2064 and
|
||||
#2247 and was removed once Windows built the shared record again.
|
||||
The Windows records were made with the MSVC target (`x86_64-pc-windows-msvc`) and all
|
||||
six were checked again on main `e9490093`. GNU builds (`x86_64-pc-windows-gnu`)
|
||||
use the shared records unless an explicit GNU override exists, so MSVC-only outcomes
|
||||
do not leak into GNU validation (#4104).
|
||||
|
||||
To record an intended change, give the reason:
|
||||
|
||||
```
|
||||
ROBUSTNESS_GOLDENS_BLESS="why it changed" cargo test --profile ci -p solidcraft-engine --lib robustness_goldens::seed_
|
||||
```
|
||||
|
||||
The changed seeds and the reason are added to the file's `notes`; say why in the pull request too.
|
||||
Recording on Windows always writes to the current environment's own group, even when
|
||||
that group does not exist yet. It preserves shared records and the other Windows
|
||||
environment's records. Non-Windows recording keeps its existing OS-group policy.
|
||||
Notes coalesce only when both the reason and destination match.
|
||||
The robustness runs check that every step builds a sound solid; they do not record what each
|
||||
seed builds. The reference for what the kernel builds is the kernel-cases ladder
|
||||
([`tests/kernel-cases/ladder/`](../tests/kernel-cases/ladder/), run by
|
||||
`cargo xtask kernel-cases --ladder`, [docs/kernel-ladder.md](kernel-ladder.md)): every case has
|
||||
its own right answer from analysis or invariants. The seeded goldens that recorded truck's results for seeds 0–95 are
|
||||
superseded by it and gone.
|
||||
|
||||
The generator feeds the current geometry's bounds and face/edge picks back into later
|
||||
commands. The same RNG seed can therefore generate different command coordinates or picks
|
||||
on different math libraries. The original inputs are retained, including near-contact cases.
|
||||
Platform snapshots track repeatability; they do not certify cross-platform geometric
|
||||
equivalence or resolve the numerical differences tracked in #924.
|
||||
on different math libraries.
|
||||
|
||||
### Independent measurement checks
|
||||
|
||||
The seeded snapshots also have checks against geometric references. Seed 34 starts with a
|
||||
Two seeds also have checks against geometric references. Seed 34 starts with a
|
||||
quarter cylinder of radius 15 and height 12.5 (`x,y >= 0`, `-12.5 <= z <= 0`), cuts it with a
|
||||
radius-2.5 sphere centred at (10, 12.5, 0), then drills an independent radius-2, depth-5 blind
|
||||
hole from (0, 7.5, -6.25) along +X, with a 118-degree tip.
|
||||
@@ -109,8 +67,7 @@ body after a cut (#2247), with a combined analytic surface inventory of 22 plana
|
||||
cylindrical and one conical face after merging redundant subdivisions. The commands are
|
||||
fixed because the generator chooses later operations from the current body and edge layout;
|
||||
changes to body grouping can therefore change the generated design. This preserves the
|
||||
original geometric reference independently of those choices. These checks supplement the
|
||||
snapshots' stricter repeatability comparisons of raw topology and tessellated measurements.
|
||||
original geometric reference independently of those choices.
|
||||
|
||||
## Booleans
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ EMBEDDED_PREFIXES = {"assets/locales/": ("solidcraft-ui-egui", "xtask"), "skills
|
||||
# Test data and the package directories whose tests (or xtask) read it. Any other folder under
|
||||
# tests/ selects the full workspace.
|
||||
TEST_DATA = {
|
||||
"tests/robustness/": ("crates/engine/",),
|
||||
"tests/hole_matrix/": ("crates/engine/",),
|
||||
"tests/projection/": ("crates/engine/",),
|
||||
"tests/taper/": ("crates/engine/",),
|
||||
|
||||
@@ -110,8 +110,8 @@ class RealWorkspace(unittest.TestCase):
|
||||
def test_docs_only_selects_nothing(self):
|
||||
self.assertEqual(affected.select(self.meta, ["docs/book/src/dev/testing.md", "CONTRIBUTING.md"]), (False, []))
|
||||
|
||||
def test_goldens_select_the_engine_and_its_dependents_not_the_kernel(self):
|
||||
full, pk = affected.select(self.meta, ["tests/robustness/goldens.json"])
|
||||
def test_engine_test_data_selects_the_engine_and_its_dependents_not_the_kernel(self):
|
||||
full, pk = affected.select(self.meta, ["tests/hole_matrix/known_failures.txt"])
|
||||
self.assertFalse(full)
|
||||
self.assertIn("solidcraft-engine", pk)
|
||||
self.assertNotIn("solidcraft-kernel", pk)
|
||||
|
||||
@@ -24,7 +24,7 @@ A failing test without its fix cannot merge: it turns `main` red for everyone. I
|
||||
- Every test runs in its own process: do not rely on state left by another test, or on wall-clock timing.
|
||||
|
||||
## Goldens, matrices and skipped tests
|
||||
- A geometry change that moves the robustness goldens re-records them in the same PR (`ROBUSTNESS_GOLDENS_BLESS="why it changed"`, `docs/robustness.md`).
|
||||
- The reference for what the kernel builds is the kernel-cases ladder (`tests/kernel-cases/ladder/`, `cargo xtask kernel-cases --ladder`, `docs/kernel-ladder.md`).
|
||||
- The action matrix is a ratchet: record a newly passing action with `cargo xtask action-matrix --bless` (`docs/action-matrix.md`).
|
||||
- Pull requests skip a few heavy tests, which run in the merge queue, nightly and on `main`. Check that validation after merging. The list is in `.config/nextest.toml` and the testing page.
|
||||
- Mutation testing finds behaviour the suites miss: `docs/mutation-testing.md`.
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
{
|
||||
"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":4807.582879,"bodies":1,"edges":72,"error":null,"faces":27,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","hole","circular pattern","circular pattern","cut sphere","join cylinder","cut cylinder","cut sphere"],"unsupported":0,"volume":13472.328916},
|
||||
"1": {"area":2642.450646,"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.573271},
|
||||
"2": {"area":2522.922626,"bodies":1,"edges":36,"error":"boolean Cut: the operation failed: no result","faces":14,"outcome":"failed","rejected":1,"shells":1,"steps":["box","cut cylinder","fillet","cut sphere","rectangular pattern"],"unsupported":2,"volume":5580.088497},
|
||||
"3": {"area":6336.456143,"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.146888},
|
||||
"4": {"area":5810.088602,"bodies":1,"edges":27,"error":null,"faces":11,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet"],"unsupported":0,"volume":28461.628006},
|
||||
"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":11580.298484,"bodies":1,"edges":28,"error":"offset: a corner whose faces don't meet the same way after t","faces":13,"outcome":"failed","rejected":0,"shells":1,"steps":["box","hole","join box","join cylinder","cut cylinder","shell"],"unsupported":1,"volume":69137.260899},
|
||||
"7": {"area":7462.860059,"bodies":1,"edges":72,"error":null,"faces":32,"outcome":"ok","rejected":0,"shells":3,"steps":["extruded profile","join box","cut cylinder","join box","circular pattern","hole"],"unsupported":0,"volume":32823.694227},
|
||||
"8": {"area":2865.472237,"bodies":1,"edges":24,"error":null,"faces":10,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut cylinder"],"unsupported":0,"volume":10024.247951},
|
||||
"9": {"area":2403.699957,"bodies":1,"edges":47,"error":null,"faces":20,"outcome":"ok","rejected":2,"shells":2,"steps":["revolved section","chamfer","fillet","join box","chamfer","cut sphere","rectangular pattern","rectangular pattern"],"unsupported":0,"volume":6524.641809},
|
||||
"10": {"area":2809.248846,"bodies":1,"edges":39,"error":null,"faces":16,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","cut box","fillet","hole"],"unsupported":0,"volume":6696.329905},
|
||||
"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.773162,"bodies":1,"edges":59,"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.973223},
|
||||
"13": {"area":2112.846954,"bodies":1,"edges":99,"error":null,"faces":39,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","cut cylinder","join box","rectangular pattern","cut cylinder","cut box","fillet","hole"],"unsupported":0,"volume":2410.783741},
|
||||
"14": {"area":10554.182993,"bodies":1,"edges":43,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","fillet"],"unsupported":0,"volume":10317.362887},
|
||||
"15": {"area":2312.730168,"bodies":1,"edges":39,"error":null,"faces":15,"outcome":"ok","rejected":2,"shells":1,"steps":["revolved section","cut box","rectangular pattern","cut sphere","cut cylinder","cut sphere","join cylinder"],"unsupported":0,"volume":5728.661237},
|
||||
"16": {"area":8872.878228,"bodies":1,"edges":49,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":3,"steps":["box","join cylinder","circular pattern","hole"],"unsupported":0,"volume":44953.407975},
|
||||
"17": {"area":6022.921702,"bodies":1,"edges":48,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut box","shell","cut cylinder"],"unsupported":0,"volume":2981.269959},
|
||||
"18": {"area":5969.14407,"bodies":1,"edges":131,"error":null,"faces":53,"outcome":"ok","rejected":0,"shells":4,"steps":["extruded profile","join cylinder","circular pattern","cut cylinder","hole","cut cylinder","hole","circular pattern"],"unsupported":0,"volume":20696.295391},
|
||||
"19": {"area":3440.869757,"bodies":1,"edges":42,"error":null,"faces":16,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","chamfer","cut sphere"],"unsupported":0,"volume":5909.174021},
|
||||
"20": {"area":5729.705185,"bodies":1,"edges":116,"error":null,"faces":45,"outcome":"ok","rejected":1,"shells":1,"steps":["extruded profile","fillet","cut box","cut cylinder","hole","circular pattern","cut box"],"unsupported":0,"volume":17175.653386},
|
||||
"21": {"area":5814.791707,"bodies":1,"edges":56,"error":null,"faces":22,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","join box","hole","fillet","hole","cut box"],"unsupported":1,"volume":19153.734422},
|
||||
"22": {"area":4634.445571,"bodies":1,"edges":138,"error":null,"faces":54,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","join box","join box","join box","rectangular pattern","cut cylinder"],"unsupported":0,"volume":7855.557174},
|
||||
"23": {"area":1795.692727,"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":8910.288004,"bodies":1,"edges":64,"error":null,"faces":20,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","chamfer","hole","cut box","cut sphere","cut sphere","hole"],"unsupported":1,"volume":32456.126515},
|
||||
"25": {"area":7142.717684,"bodies":1,"edges":48,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","shell"],"unsupported":0,"volume":7012.754625},
|
||||
"26": {"area":6461.702601,"bodies":1,"edges":56,"error":null,"faces":22,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","join box","cut cylinder","rectangular pattern","cut sphere"],"unsupported":0,"volume":21188.159601},
|
||||
"27": {"area":11263.105391,"bodies":1,"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.079765},
|
||||
"28": {"area":2394.667072,"bodies":1,"edges":20,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":2,"steps":["revolved section","join cylinder"],"unsupported":0,"volume":5902.111713},
|
||||
"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":850.154862,"bodies":1,"edges":21,"error":null,"faces":9,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","chamfer"],"unsupported":0,"volume":1327.098159},
|
||||
"31": {"area":10237.245823,"bodies":1,"edges":74,"error":null,"faces":33,"outcome":"ok","rejected":2,"shells":3,"steps":["box","cut box","fillet","join box","circular pattern","cut box","circular pattern","hole"],"unsupported":0,"volume":56395.425534},
|
||||
"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":8708.011527,"bodies":1,"edges":90,"error":null,"faces":39,"outcome":"ok","rejected":4,"shells":4,"steps":["revolved section","fillet","join box","cut cylinder","join box","circular pattern","fillet","cut box"],"unsupported":1,"volume":24911.274066},
|
||||
"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},
|
||||
"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.825748,"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.313103},
|
||||
"37": {"area":11714.822253,"bodies":1,"edges":44,"error":null,"faces":18,"outcome":"ok","rejected":1,"shells":1,"steps":["box","cut cylinder","join cylinder","cut sphere","circular pattern","fillet","circular pattern","rectangular pattern"],"unsupported":2,"volume":75556.602064},
|
||||
"38": {"area":11769.375811,"bodies":1,"edges":30,"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.020975},
|
||||
"39": {"area":1892.051593,"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.619835},
|
||||
"40": {"area":15549.121775,"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":16538.74573},
|
||||
"41": {"area":7295.282884,"bodies":1,"edges":93,"error":null,"faces":32,"outcome":"ok","rejected":1,"shells":1,"steps":["box","cut sphere","shell","fillet","cut cylinder","rectangular pattern","fillet","cut sphere"],"unsupported":0,"volume":5185.221068},
|
||||
"42": {"area":5486.877622,"bodies":1,"edges":51,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut box","cut cylinder","rectangular pattern"],"unsupported":0,"volume":19095.106157},
|
||||
"43": {"area":3492.988343,"bodies":1,"edges":72,"error":null,"faces":26,"outcome":"ok","rejected":2,"shells":1,"steps":["extruded profile","cut cylinder","rectangular pattern","fillet","hole","rectangular pattern","join cylinder"],"unsupported":1,"volume":7528.548742},
|
||||
"44": {"area":7972.435566,"bodies":1,"edges":36,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","shell","cut sphere"],"unsupported":0,"volume":7622.184366},
|
||||
"45": {"area":14308.150092,"bodies":1,"edges":63,"error":"boolean Union: implausible result volume ","faces":30,"outcome":"failed","rejected":0,"shells":3,"steps":["revolved section","join cylinder","fillet","shell","hole","cut box","join box"],"unsupported":0,"volume":13544.638725},
|
||||
"46": {"area":3445.162934,"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.317935},
|
||||
"47": {"area":3098.575625,"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.297438},
|
||||
"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":9319.136457,"bodies":1,"edges":51,"error":null,"faces":21,"outcome":"ok","rejected":0,"shells":2,"steps":["extruded profile","fillet","join cylinder","join cylinder","fillet","cut box","fillet"],"unsupported":1,"volume":40030.256071},
|
||||
"51": {"area":4221.874863,"bodies":1,"edges":75,"error":null,"faces":31,"outcome":"ok","rejected":1,"shells":2,"steps":["box","cut box","hole","rectangular pattern","chamfer","join box"],"unsupported":1,"volume":8682.025064},
|
||||
"52": {"area":8628.608654,"bodies":1,"edges":45,"error":null,"faces":18,"outcome":"ok","rejected":0,"shells":1,"steps":["box","shell","cut cylinder","chamfer","join cylinder"],"unsupported":1,"volume":9001.505742},
|
||||
"53": {"area":17463.451761,"bodies":1,"edges":116,"error":null,"faces":48,"outcome":"ok","rejected":0,"shells":4,"steps":["extruded profile","cut cylinder","shell","fillet","join cylinder","circular pattern"],"unsupported":0,"volume":16954.551768},
|
||||
"54": {"area":7075.531663,"bodies":1,"edges":81,"error":"offset: a corner whose faces don't meet the same way after t","faces":35,"outcome":"failed","rejected":0,"shells":4,"steps":["extruded profile","join box","circular pattern","cut cylinder","cut box","join cylinder","shell"],"unsupported":2,"volume":16028.147251},
|
||||
"55": {"area":6795.599353,"bodies":1,"edges":60,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":1,"steps":["box","fillet","cut sphere","circular pattern","rectangular pattern","cut sphere"],"unsupported":0,"volume":24743.500149},
|
||||
"56": {"area":4453.31728,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut box","chamfer"],"unsupported":0,"volume":11829.375},
|
||||
"57": {"area":4671.095724,"bodies":1,"edges":66,"error":null,"faces":24,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut sphere","circular pattern","chamfer","cut sphere","cut sphere","cut cylinder"],"unsupported":2,"volume":16017.21038},
|
||||
"58": {"area":4197.049258,"bodies":1,"edges":42,"error":null,"faces":16,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","cut sphere"],"unsupported":0,"volume":12543.173591},
|
||||
"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.328607,"bodies":1,"edges":39,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere"],"unsupported":0,"volume":11779.204543},
|
||||
"61": {"area":5020.412837,"bodies":1,"edges":36,"error":"boolean Cut: the operation failed: no result","faces":15,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","rectangular pattern"],"unsupported":0,"volume":4906.555871},
|
||||
"62": {"area":8771.003732,"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.001728},
|
||||
"63": {"area":4316.842909,"bodies":1,"edges":100,"error":null,"faces":38,"outcome":"ok","rejected":0,"shells":3,"steps":["revolved section","join box","cut cylinder","hole","cut cylinder","join box","rectangular pattern"],"unsupported":3,"volume":8229.233195},
|
||||
"64": {"area":4088.335645,"bodies":1,"edges":36,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","join cylinder"],"unsupported":0,"volume":8495.235267},
|
||||
"65": {"area":8436.255548,"bodies":1,"edges":37,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet","rectangular pattern","circular pattern","circular pattern","fillet","cut box"],"unsupported":0,"volume":45143.475817},
|
||||
"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":7940.064,"bodies":1,"edges":84,"error":null,"faces":35,"outcome":"ok","rejected":1,"shells":2,"steps":["extruded profile","cut cylinder","hole","join cylinder","join box","hole","chamfer","rectangular pattern"],"unsupported":1,"volume":27606.450855},
|
||||
"68": {"area":7338.675126,"bodies":1,"edges":36,"error":null,"faces":14,"outcome":"ok","rejected":2,"shells":1,"steps":["extruded profile","hole","hole","rectangular pattern","rectangular pattern","fillet","circular pattern"],"unsupported":0,"volume":29780.741772},
|
||||
"69": {"area":6451.122767,"bodies":1,"edges":60,"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.477515},
|
||||
"70": {"area":5638.09657,"bodies":1,"edges":48,"error":"lumps: shell boundaries intersect or touch; cavity ownership","faces":23,"outcome":"failed","rejected":0,"shells":3,"steps":["extruded profile","join box","shell","hole"],"unsupported":0,"volume":4120.897175},
|
||||
"71": {"area":3980.574721,"bodies":1,"edges":52,"error":null,"faces":20,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut box","cut cylinder","hole","join cylinder"],"unsupported":0,"volume":15749.927814},
|
||||
"72": {"area":5770.270336,"bodies":1,"edges":44,"error":null,"faces":20,"outcome":"ok","rejected":1,"shells":2,"steps":["box","join cylinder","hole","join box","join box","cut box","cut cylinder","cut cylinder"],"unsupported":0,"volume":16081.801492},
|
||||
"73": {"area":6868.162117,"bodies":1,"edges":40,"error":"offset: a corner whose faces don't meet the same way after t","faces":18,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","join box","shell"],"unsupported":1,"volume":36461.939381},
|
||||
"74": {"area":9294.57311,"bodies":1,"edges":18,"error":null,"faces":9,"outcome":"ok","rejected":0,"shells":1,"steps":["box","hole"],"unsupported":0,"volume":55866.183074},
|
||||
"75": {"area":7890.438301,"bodies":1,"edges":31,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut cylinder","chamfer","rectangular pattern","rectangular pattern","cut box"],"unsupported":0,"volume":38753.33959},
|
||||
"76": {"area":853.668183,"bodies":1,"edges":32,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","chamfer","cut cylinder"],"unsupported":1,"volume":1680.708844},
|
||||
"77": {"area":9671.786688,"bodies":1,"edges":42,"error":null,"faces":19,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","fillet","hole","circular pattern","rectangular pattern","circular pattern"],"unsupported":0,"volume":63910.630446},
|
||||
"78": {"area":2423.235496,"bodies":1,"edges":34,"error":"offset: a corner whose faces don't meet the same way after t","faces":14,"outcome":"failed","rejected":0,"shells":1,"steps":["revolved section","cut cylinder","cut box","shell"],"unsupported":0,"volume":3707.429306},
|
||||
"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.847611,"bodies":1,"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.657989},
|
||||
"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":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":7580.468361,"bodies":1,"edges":66,"error":"offset: a corner whose faces don't meet the same way after t","faces":30,"outcome":"failed","rejected":0,"shells":3,"steps":["extruded profile","join cylinder","join cylinder","circular pattern","hole","rectangular pattern","shell"],"unsupported":1,"volume":20442.860492},
|
||||
"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":9676.011803,"bodies":1,"edges":30,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","hole"],"unsupported":0,"volume":46965.837216},
|
||||
"91": {"area":3958.046518,"bodies":1,"edges":50,"error":null,"faces":20,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","cut cylinder","fillet"],"unsupported":0,"volume":9721.244548},
|
||||
"92": {"area":2855.139217,"bodies":1,"edges":30,"error":null,"faces":12,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","chamfer","cut sphere"],"unsupported":0,"volume":9814.30292},
|
||||
"93": {"area":7266.379615,"bodies":1,"edges":111,"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.336056},
|
||||
"94": {"area":3480.70333,"bodies":1,"edges":63,"error":null,"faces":27,"outcome":"ok","rejected":1,"shells":2,"steps":["revolved section","cut cylinder","fillet","hole","rectangular pattern","cut box","cut box"],"unsupported":0,"volume":5456.446856},
|
||||
"95": {"area":2375.340299,"bodies":1,"edges":33,"error":null,"faces":12,"outcome":"ok","rejected":2,"shells":1,"steps":["revolved section","hole","cut cylinder","cut sphere","circular pattern","cut sphere"],"unsupported":1,"volume":6704.220611}
|
||||
},
|
||||
"platforms": {"windows":{"seeds":{"1":{"area":2642.450837,"bodies":1,"edges":32,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut cylinder","join cylinder"],"unsupported":0,"volume":6991.569339},"13":{"area":2101.521015,"bodies":1,"edges":103,"error":null,"faces":41,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","cut cylinder","join box","rectangular pattern","cut cylinder","cut box","fillet","hole"],"unsupported":0,"volume":2396.939546},"23":{"area":1795.693105,"bodies":1,"edges":15,"error":null,"faces":7,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere"],"unsupported":0,"volume":3198.694089},"34":{"area":1086.303608,"bodies":1,"edges":30,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere","hole"],"unsupported":0,"volume":2134.3653},"36":{"area":9964.083285,"bodies":1,"edges":45,"error":"lumps: shell boundaries intersect or touch; cavity ownership","faces":21,"outcome":"failed","rejected":0,"shells":2,"steps":["revolved section","cut cylinder","join box","fillet","join cylinder"],"unsupported":0,"volume":54852.503099},"94":{"area":3470.837042,"bodies":1,"edges":63,"error":null,"faces":27,"outcome":"ok","rejected":1,"shells":2,"steps":["revolved section","cut cylinder","fillet","hole","rectangular pattern","cut box","cut box"],"unsupported":0,"volume":5456.438772}}}},
|
||||
"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]},
|
||||
{"reason":"seed 0 meshes differently since display tessellation plans (#805), which landed alongside these goldens (#826)","seeds":[0]},
|
||||
{"reason":"the kernel fixes merged together on 13 October (#838, #839, #841, #855, #856) change what these seeds build: several joins that used to fail now succeed, small volume and area shifts, and shell now refuses seeds 6, 14, 43 and 73 with an offset corner error (seed 14 built before: #897)","seeds":[2,6,14,22,36,43,50,63,71,73,94]},
|
||||
{"reason":"#936: a fillet or chamfer of a smooth seam is refused as an invalid pick (no corner to round) instead of as unsupported","seeds":[72]},
|
||||
{"reason":"the model's body mesh is the reference mesher's again (the display plan's since #805, which moved these seeds rather than the kernel fixes above): they build as they did before #805, and seed 14 builds again (#897)","seeds":[0,2,14,36,43,71,94]},
|
||||
{"reason":"a fillet that used to be refused as unsupported on seed 64's extruded profile now builds, so the design goes on differently: rounds that meet at corners on curved faces (#851)","seeds":[64]},
|
||||
{"reason":"#936: fillets and chamfers of smooth seams are refused as invalid picks (rejected) instead of unsupported; nothing else changes","seeds":[10,15,20,21,37,41,50,53,68,81,84,91]},
|
||||
{"reason":"#946: small tools in large bodies are traced at their own scale; seeds 26 and 85 now build, 18, 43, 61, 63 and 71 fail with a different message, 19 builds two fewer faces (its fillet pick is also refused as in #936)","seeds":[18,19,26,43,61,63,71,85]},
|
||||
{"reason":"#921 with its grid lines kept to display and export meshes (Booleans mesh as before #921): measured volume and area move by about 1e-6 (seed 94 also as in #936)","seeds":[22,78,94]},
|
||||
{"reason":"a hole placed on a face without a direction drills into that face (it took the direction from any face whose plane passed through the point, the last one found, such as a side of a patterned box standing on the point's plane): the holes on these seeds now go straight in (#950)","seeds":[7,21,85]},
|
||||
{"reason":"the last-resort tool nudge is tried the other way when it fails or leaves sliver faces the operands didn't have: seed 43's join builds and the design runs to the end, 31 and 78 come out with fewer faces (#827, #897)","seeds":[31,43,78]},
|
||||
{"reason":"main's fillet changes since the last record (#926, #949) move seeds 33 and 91: a fillet step builds differently or is picked differently","seeds":[33,91]},
|
||||
{"reason":"hole tangent inside a stepped boss: touching sews split zero-thickness curved walls, so a pattern of holes spaced by their diameter (touching) in a stepped body cuts exactly instead of by a tool nudge: 42 builds more faces (the walls split where the holes touch), 51 picks other edges afterwards","seeds":[42,51]},
|
||||
{"reason":"corner blends on curved faces (#854): seed 33's last fillet (its second body) is built differently; its display-plan and measured volumes now agree (4442.73 mm³; on main they read 4439.42 and 4443.51)","seeds":[33]},
|
||||
{"reason":"#952: a hole placed on a face drills into the material; seed 81's hole moves its volume by 0.1 mm³ (area by 0.04 mm²)","seeds":[81]},
|
||||
{"reason":"Cuts join the straight edges they used to leave split along untouched walls (#1049). Replaying each changed seed's new commands on the build before and after gives the same volume, area and faces at every step, and the same refusals (seeds 2 and 95 end on a cut the old build refuses too), except: seed 94's final join was 1.7 mm³ too large before (the box adds 875 − 2.5 × 97.967 = 630.08 mm³ to 5270.48; now 5900.56, before 5902.27), and seed 71's hole differs only in the coarse measuring mesh (identical at 2e-4 mm). Every other volume change is the generator picking other edges by index for fillets and chamfers, some of which are now accepted; seed 71's join now builds.","seeds":[2,4,7,12,13,17,25,31,38,42,45,51,52,56,65,67,69,71,72,74,82,93,94,95]},
|
||||
{"reason":"#1164: a pattern copy of a box sitting flush on the half-ring's top face (a 27 mm2 contact patch) now joins the ring instead of staying a separate shell, so the later random steps pick different edges and positions","seeds":[13]},
|
||||
{"reason":"A point at a boundary curve's end is that end in the boolean (#391): seeds 0 and 41 lose a sliver edge (8e-5 mm on seed 41) that a cut left. Seed 0's fillet of its two edges then takes out 10.62 mm³, as its edges do alone (3.11 and 7.40 mm³), where it took 5.86. Seed 39 moves by 0.008 mm³; seed 57's chamfer and pattern come in the other order, its body valid with no edge under 10 µm","seeds":[0,39,41,57]},
|
||||
{"reason":"#391 after merging main: seed 34's area moves by 0.002 mm² (1086.303472 to 1086.305562, 2e-6 relative); its steps, outcome, volume and face, edge and shell counts are unchanged; the same small kind of move as seed 39's","seeds":[34]},
|
||||
{"reason":"#1213: a round or chamfer on an edge chain that runs on across a curved seam now builds (it was refused with 'a curved seam where the edge chain turns'). Seed 41's second fillet (three edges, radius 1) was one of those refusals and is now built. Replaying the seed's commands on the build before and after gives the same volume, area, faces and edges after each of steps 1 to 6; step 7, the fillet, is refused before and builds after (5292.19 to 5289.17 mm³, area 7395.26 to 7381.22 mm²); the run then reaches its eight steps one step sooner, so its last step is a cut sphere (5185.22 mm³) where it was a join cylinder (the generator draws its later steps from the new state).","seeds":[41]},
|
||||
{"reason":"#391 after merging main with #1213: seed 41 now reaches its second fillet (built since #1213), and the end-snap fix again drops a sliver edge the cuts left (94 to 93 edges); its steps, outcome, faces and shells are unchanged, volume moves by 0.005 mm³ (5185.215737 to 5185.221068) and area by 0.0014 mm²","seeds":[41]},
|
||||
{"reason":"the chamfer bug is on main too, see #1390","seeds":[57]},
|
||||
{"reason":"#1390: a chamfer of a convex edge that would grow the part is refused (TooLarge). Seed 57's 0.5 mm chamfer on the sphere cut's rim, which grew the part since #391 reordered its steps, is now refused, so the run goes on to its later steps and ends ok (16017.21 mm³, 24 faces)","seeds":[57]},
|
||||
{"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":"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]},
|
||||
{"reason":"the 2D route sews a cut whose outline has a point a hair from a segment's end (text-recut): seed 53's shell (step 3, a 2 mm shell of an extruded L with an arc side, opened at the notch's inner face) builds its cavity by the 2D route in 77 ms, where it took the B-rep path (2.4 s) and gave a part 336 mm³ lighter, which a box can't then be intersected with (no result). Its section at mid height is 338.27 mm² (a distance-rule raster of the profile gives 343.7, rounding the cavity's concave corners); the generator then builds the step 4 fillet it refused before, so the run goes on another way (16954.55 mm³, 48 faces)","seeds":[53]},
|
||||
{"reason":"#726 (#1687): a full revolve of a profile off the axis comes out with its planar faces whole, so the revolved section has fewer faces and edges. The fuzz generator picks edges and faces by index, so from their second step these seeds build different designs. Replaying each new design's commands on main and on this branch: seeds 45, 77 and 94 end the same; in seed 21 a box joined onto the ring's flat top now fuses with it, where main kept two touching shells that a later hole split into two bodies.","seeds":[21,45,77,94]},
|
||||
{"reason":"#726 (#1718): a partial revolve made as a new body comes out with its planar faces whole, so the revolved section has fewer faces and edges. The fuzz generator picks edges and faces by index, so from their second step these seeds build different designs (seed 24's shell now picks the centroid of a whole 270 deg annulus, which lies in the bore, so it is refused and the run goes on). Replaying each new design's commands on main and on this branch: 12 seeds end the same; in seeds 13 and 26 a box joined onto a flat revolved face now fuses with it (main kept two touching shells that a later step split into two bodies), seed 15's last cylinder join, which main refuses, now builds, and seed 78's cut box no longer leaves 87.5 mm² of extra area (the end cap's); seed 63 refuses the same fillet on both and ends with nearly the same body. Seed 9 now ends ok instead of failing, but its new design ends the same on main. No seed gains a failure.","seeds":[9,10,13,15,17,19,22,24,25,26,30,63,75,76,78,90,91]},
|
||||
{"reason":"Current main replay: seed 55 now meshes every face and passes validity; edge-consistent measuring meshes (#1753) change its measured volume from 24741.239331 to 24740.927259 mm3. Seed 71 joins with one fewer redundant face and two fewer edges (19/50); its five steps, successful outcome, volume and area are unchanged within the existing 1e-6 tolerances. Both fixed command sequences were replayed with validity checks after every step.","seeds":[55,71]},
|
||||
{"reason":"Seed 20: trace conical cutters against earlier conical cuts at finer unshifted tolerances before coarse copies can leave unusable junctions. The circular pattern and final box cut both build; replayed in the app with no feature errors, unchanged 45 faces/116 edges/one shell, no tool nudge, and volume/spatial guards retained. The finer intersection curves change measured volume by 0.020503 mm3 and area by 0.171629 mm2.","seeds":[20]},
|
||||
{"reason":"After integrating #1718, replay the regenerated revolved-section recipes on the combined branch: seed 13 has 40 faces/101 edges (one face/two edges fewer), seed 94 has 27 faces/63 edges (one face/two edges more). Both complete fixed command sequences pass validity and volume-direction checks after every step, and two independent golden runs per seed agree exactly. Steps, outcomes, body and shell counts are unchanged; area and volume match within existing 1e-6 relative tolerances (seed 13 volume differs by 0.000002 mm3). Only topology counts are updated. The finer unshifted conical-cut route and its geometry guards remain in place.","seeds":[13,94]},
|
||||
{"reason":"#1670 and #1753 (mesh export: twisted ruled faces divided to the chord tolerance, and the faces of an edge sharing its points exactly) change the meshes the kernel's validity and volume checks measure: seed 55's body (a filleted box cut by spheres and patterned) now passes the validity check it failed, and its volume moves by 1.3e-5 relative. Pull requests ran only every 8th seed, so main's full run found it. Recorded from main's CI output (Linux).","seeds":[55]},
|
||||
{"reason":"#853 recorded seeds 13, 71 and 94 as its own branch built them (13: 40 faces/101 edges, 71: 19/50, 94: 27/63), but with main's kernel they build as before (41/103, 20/52, 26/61), so every full run on main failed on them after #853 landed. This puts back the counts main builds; #853's conical-cut refinement (seed 20) is unchanged. Steps, outcomes, volumes and areas are the same.","seeds":[13,71,94]},
|
||||
{"reason":"#1853 (a part revolve of a profile with an edge on the axis is swept a third of a turn at most per face, so the boolean can intersect its faces): seeds 28 and 95 start with a rectangle on the axis turned half a turn, now two quarter turns. Seed 28 (joined by a cylinder) has more faces for the same shape (5902.1214 -> 5902.1117 mm3, the new faces' meshes). Seed 95's hole into it failed with 'boolean Cut: no result' on the half-turn faces and now builds, so the generator's later picks move. Values from Linux CI.","seeds":[28,95]},
|
||||
{"platform":"windows","reason":"Independently reproduced on unmodified main 1e984c8e on Windows; the extrusion branch produces the same values. Shared references and tolerances remain unchanged.","seeds":[1,23,34,67]},
|
||||
{"platform":"windows","reason":"Seed 94 independently reproduced on unmodified main c5992386 on Windows after the extrusion rebase: the same steps, outcome and geometry values as the rebased branch. Shared references and tolerances remain unchanged.","seeds":[94]},
|
||||
{"reason":"#2064 (multi-profile extrusion) merges coplanar faces the boolean used to leave split. Seed 67 has one face/two edges fewer from its first cut on, at the same volume at every step; seed 94's last box cut leaves one face/two edges more, same volume and area. Seed 13's second cylinder cut leaves 35 faces/84 edges instead of 37/88 at the same volume, so its random fillet (edges picked by index) rounds different edges: 2688.99 instead of 2675.18 mm3 after the fillet; the hole after it removes the same 278.2 mm3. Every step passes the validity and volume-direction checks. Pull request CI ran only every 8th seed, so main was the first run to see these.","seeds":[13,67,94]},
|
||||
{"reason":"#2247: a cut no longer splits the separate pieces of a body into bodies of their own, so these seeds keep one body (bodies N -> 1). The generator looks at the model, so after the first such cut some seeds build different steps: seed 33 joins and patterns a box instead of chamfering (volume 13858 -> 24911 mm3), seed 18 now builds (its hole pattern failed with no result), and seed 54 picks a shell that fails the same way on main before #2247 (offset corner). Every step passes the validity and volume-direction checks.","seeds":[7,9,12,16,18,22,27,31,33,45,50,54,67,72,81,93]},
|
||||
{"reason":"Reseam rotationally symmetric drill cutters after failed cuts (#2387): seed 86 now drills its hole, so its following pattern, shell and sphere cut complete with the fuzz validity and material checks.","seeds":[86]},
|
||||
{"reason":"Rebase #2387 onto #2247: patterned pieces stay in one body. Seed 86 drills its hole, then reaches the same offset-corner shell refusal as main instead of shelling separate bodies. Completed steps still pass validity and volume-direction checks.","seeds":[86]},
|
||||
{"reason":"A join whose coincident faces were pushed apart now trims the slab beyond the plane by editing faces when the boolean cut of it fails (a prism whose edge in the plane lies in the end face of a slab with rounded corners). Seed 70: the shell of a body in two lumps, refused before, now builds; its volume matches an independent offset of the profile (4120.90 vs 4120.67 mm3, the gap is the unshelled profile's own tessellation).","seeds":[70]},
|
||||
{"reason":"#1648: a face whose boundary touches itself within the tolerance is triangulated through the touching point instead of across the face, so faces that meshed with triangles missing or outside them now mesh whole. Seeds 20 and 55 build the same designs (same steps, faces, edges and shells); only the measured area, and for seed 55 the volume, of their final bodies move. Seed 55: replaying it with the old and the new triangulation gives the same final body; two curved faces measured 442.14 and 325.42 mm2 with the old triangulation at a fine tolerance, 436.29 and 305.83 mm2 with the new one, which agrees with their coarser meshes, and its volume moves from 24740.93 to 24744.18 mm3, against 24743.58 mm3 from a mesh at 1e-4 mm. Seed 20, as main now builds it, moves only in area, from 5726.92956 to 5727.102692 mm2 (3e-5 relative). Recorded from the fork CI output (Linux).","seeds":[20,55]},
|
||||
{"reason":"A cut no longer comes out empty where a shell of one body meets nothing of the other (a sealed void, a body clear of the tool): truck placed such faces by their surfaces alone, which a turned body's reversed faces and a void's inward shell got wrong. Seeds 0, 45 and 77 each had a cut refused as empty that now takes what it should, and the generator builds different steps after it: seed 0 a sphere clear of the body (removes nothing), seed 45 a hole on the axis of a shelled turned body (widens its bore from r 2.5 to r 4 over 17.5 mm: 536.0 mm3), seed 77 a hole inside a turned body's bore (removes nothing; a later pattern copy drills r 1.5 through 27.5 mm: 194.4 mm3). Every step passes the validity and volume-direction checks.","seeds":[0,45,77]},
|
||||
{"reason":"Trimmed faces of display meshes stay on their surfaces (#1653): seeds 1, 23, 55, 64 and 86 end with bodies whose trimmed faces were meshed off their surfaces; measured on the corrected meshes their area and volume move slightly (seed 86 most: area +9.6 mm2, volume -41.4 mm3, 0.2 %). Steps, outcome and counts are unchanged; recorded from Linux CI on main at the time of the rebase. The slow seeds 14, 20 and 40 (run only by the merge queue) move too: 14 area +0.92 mm2 / volume +2.64 mm3, 20 area +2.60 mm2 / volume +14.8 mm3, 40 area +0.61 mm2 / volume -2.25 mm3, from merge-group run 39450833424.","seeds":[1,14,20,23,40,55,64,86]},
|
||||
{"reason":"measure (#2069): a body whose meshes leave a crack is summed about its box's centre instead of the origin, so the crack is closed by a cone to a point inside the body. Seeds 14, 20, 40, 55, 61 and 86 end with such a body; their volumes move from main's by -0.52 %, +0.11 %, -0.017 %, -0.001 %, -0.0015 % and +0.026 %. Steps, outcomes, counts and areas (to 3e-7 relative) are unchanged. Recorded on Linux.","seeds":[14,20,40,55,61,86]},
|
||||
{"reason":"Chamfer cutters that reach into a nearby wall are not used (#4539): a chamfer in seed 9 was refused, as its result changed 33 mm3 of the part, more than the chamfer can (20). With its cutter refused it builds locally, so the design takes 8 steps without the final cut box. Area 2427.18 -> 2403.70 mm2, volume 6417.16 -> 6524.64 mm3, unsupported steps 2 -> 0; counts unchanged. Recorded on Linux.","seeds":[9]},
|
||||
{"platform":"windows","reason":"Seed 67's Windows record (from main 1e984c8e) predates #2064 and #2247, which changed the shared record. On main 65e6d9da Windows builds the shared record exactly (one body, 35 faces/84 edges, 27606.450855 mm3, 7940.064 mm2): its generated coordinates differ from Linux's only in the last digits. The stale Windows record is removed, so Windows checks seed 67 against the shared record again.","seeds":[67]},
|
||||
{"platform":"windows","reason":"Seed 13 on Windows (main 65e6d9da) builds what Linux built before #2064. Its first four steps match Linux to nine decimals. The second cylinder cut takes its base from the body bounds: y = -1.78e-14 on Windows, -1.95e-14 on Linux (math-library rounding, #924). At the same volume (2694.4476 mm3 on both) Windows leaves split the two coplanar faces that #2064 merges on Linux (37 faces/88 edges, not 35/84). The fillet picks its edge by index, so it rounds a different edge (2675.18 mm3, not 2688.99), and the hole after it removes the same 278.2 mm3. Every step passes the validity and volume-direction checks.","seeds":[13]},
|
||||
{"platform":"windows","reason":"Seed 23's Windows record predates #1653 (trimmed faces of display meshes stay on their surfaces), which moved its shared record's area; on Windows the final body's area moves the same way (1795.696636 to 1795.693105 mm2). Steps, outcome and counts are unchanged.","seeds":[23]},
|
||||
{"platform":"windows","reason":"Seed 36 on Windows (main 79a843c9) ends differently from Linux: the body after its last step (join cylinder) fails the validity check ('lumps: shell boundaries intersect or touch; cavity ownership'), so the run ends failed with 21 faces/45 edges, 54852.503099 mm3. macOS fails the same way; only Linux x86_64 builds it ok. Recorded so Windows notices any further change; the failure itself is tracked in #3583.","seeds":[36]}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user