feat(ui): scale-relative fuzzy default, measured (M2-4 B5)

`FUZZY_FRACTION = 1e-7`: every production boolean runs at
`1e-7 × the target's bounding-box diagonal` — 5.2e-6 mm on the 52 mm
block. Both bounds measured before the number was written.

LOWER BOUND — the noise the default must cover. `measure_flush_wall_noise`
(ignored harness, compile/tests/booleans.rs) fuses a 40x30x15 box with a
10x30x15 box authored `gap` past its x = 40 wall, over a (gap x fuzzy)
grid. At fuzzy 0 OCCT's own confusion closes gaps up to 2e-7 mm and
leaves 5e-7 open: twelve faces, two solids. So delta* = 5e-7. A fuzzy
value closes a gap iff it is >= the gap (5e-7 closes 5e-7, 1e-6 closes
1e-6, ...). The spec's rule, fraction * diag >= 2 * delta*, gives
fraction >= 1.9e-8 on diag = sqrt(40^2+30^2+15^2) = 52.2.

UPPER BOUND — the largest fraction keeping every gate green. Swept 1e-4,
1e-5, 1e-6, 1e-7, 1e-8 over vernier-ui, vernier-app and the selftest.
Every analytic 1e-9 gate held at every value — mass properties carry no
tolerance — but one thing does move: OCCT's fuzzy inflates the result's
tolerances and the tolerance-inclusive Bnd_Box grows by ~1.6x the fuzzy
value, and that box IS `bbox_diagonal`, NAMING.md section 5's scale.
Lane A's split-rebind fixture pins D at 1e-6 relative:
  1e-6: 52.20161787 vs 52.20153254 analytic, 1.6e-6 -> red
  1e-7: green (1.6e-7)
So 1e-7 sits five times over the floor and five times under the ceiling.
Not a STOP: the window [1.9e-8, ~6e-7] is open.

WHAT THE FIXTURE HAD TO LEARN. The fuzzy fuse FILLS the gap rather than
snapping the neighbour across it: the volume is 22500.000224999996
against 22500 + 5e-7 * 30 * 15 = 22500.000225 (1.8e-16 relative), and the
bare sum 22500 is off by exactly 1e-8, ten times the gate. The fixtures
assert the filled union. `a_flush_wall_fuses_to_one_face_under_the_default`
(document level, production policy: 1 solid, 6 faces, exact volume) and
`the_same_gap_stays_a_sliver_at_zero_fuzzy` (the SAME two boxes at
fuzzy 0: 12 faces, 2 solids; at the default: 6, 1; and a 1e-7 gap closes
either way, so the default only works where zero fuzzy fails). Selftest
`m2-flush-wall` (29th, appended) records both outcomes:
(94, f9fb78428f4def90).

Also fixed in the bridging fixture from B3: its underside filter caught
the slot's ceiling (normal -Z at z = 5) once fuzzy nudged OCCT's face
order — it now demands z = 0 as well.

Selftest digests, B4 -> B5:
  l1-bike-light-mount (195, 0e0084d3d6e499af) -> (195, 4aff517a7a816113)
  probed: same normal, same volume, same 12 angled faces; the centroid's
  last digits moved at 1.2e-14 relative (10.016404753393848 vs ...973),
  the per-face integration order again. m3-mirror unchanged at 12: the
  bracket's ~1e-9 solved-plane offset is under the confusion and its
  walls still do not unify.

MISTAKES.md's zero-volume entry records the resolution; README's count
27 -> 28 -> 29 across B4/B5 (set to what this branch holds).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-06 19:45:33 +02:00
co-authored by Claude Fable 5.1
parent d06f82c1a1
commit 79b80e029c
6 changed files with 297 additions and 9 deletions
+13
View File
@@ -41,6 +41,19 @@ These are documented in advance because they are near-certain and expensive.
A second confirmed-clean data point, and the reason the scale-relative-fuzzy calibration
is now more urgent than it was: the geometry that would expose a wrong default is no
longer something a user has to go out of their way to draw.
- **Resolved 2026-09-06 with `FUZZY_FRACTION = 1e-7`** (`vernier-ui/src/compile/merge.rs`,
M2-4): every production boolean now runs at `1e-7 × the target's bounding-box diagonal`.
Both bounds were measured, not chosen: at zero fuzzy OCCT's own confusion closes a flush
gap of 2e-7 mm and leaves 5e-7 open (two solids, twelve faces), and a fuzzy value closes a
gap iff it is at least the gap — so the floor is 2·5e-7 / 52.2 mm ≈ 1.9e-8; the ceiling
is that OCCT's fuzzy inflates the result's tolerances and the tolerance-inclusive
`Bnd_Box` (`bbox_diagonal`, §5's scale) grows by ~1.6× the fuzzy value, which the
split-rebind fixture's 1e-6 pin on D refuses at 1e-6 and accepts at 1e-7. Fixture:
`a_flush_wall_fuses_to_one_face_under_the_default` with its zero-fuzzy sibling
`the_same_gap_stays_a_sliver_at_zero_fuzzy` (`compile/tests/booleans.rs`), selftest
`m2-flush-wall`. One thing the fixture had to learn: the fuzzy fuse FILLS the gap rather
than snapping the neighbour across it, so the exact volume is `V + δ·A_contact`, and
asserting the bare sum at 1e-9 fails by exactly 1e-8.
### Fillet silently moves to a different edge after a sketch edit
+2 -2
View File
@@ -36,7 +36,7 @@ Headless checks need no display at all:
```sh
cargo test --workspace # every crate
cargo run --release -p vernier-cli -- --selftest # 28 end-to-end checks, JSON
cargo run --release -p vernier-cli -- --selftest # 29 end-to-end checks, JSON
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
```
@@ -159,7 +159,7 @@ the viewport.
## Tests and gates
`cargo test --workspace` runs everything; `--selftest` runs 28 end-to-end
`cargo test --workspace` runs everything; `--selftest` runs 29 end-to-end
checks with a JSON report, including golden geometry and a recompute-cost
gauge. Screenshot and picking tests render through the production pipeline and
read the framebuffer back.
@@ -135,3 +135,116 @@ pub(crate) fn check_m2_unify_push_pull() -> Result<Vec<u8>, String> {
)
.into_bytes())
}
/// A standalone id source for the kernel-level control below.
struct Counter(u64);
impl vernier_kernel::naming::EntityIdSource for Counter {
fn allocate(&mut self) -> vernier_kernel::EntityId {
self.0 += 1;
vernier_kernel::EntityId::from_raw(self.0)
}
}
/// `m2-flush-wall`: the scale-relative fuzzy default closes the gap a
/// zero-fuzzy fuse leaves open (M2-4).
///
/// A 40 × 30 × 15 block and a 10 × 30 × 15 neighbour authored δ* = 5e-7 mm
/// past its `x = 40` wall — the smallest gap OCCT's own confusion does not
/// close (measured 2026-09-06: 2e-7 closes, 5e-7 does not). Compiled
/// through the production policy the part is ONE solid of 6 faces and,
/// the gap FILLED, exactly `22500 + δ*·30·15 = 22500.000225 mm³` at 1e-9
/// relative (measured 1.8e-16 from that; the bare 22500 would be 1e-8 off,
/// ten times the gate — the fuzzy fuse fills a gap, it does not snap the
/// neighbour across it). The control runs the same two
/// boxes through the kernel at `fuzzy_mm = 0.0`: two solids, twelve
/// faces — so the check cannot pass for the wrong reason (a δ* the
/// confusion already covers would make the default a no-op that looks like
/// a success).
///
/// Artifact: both outcomes and the numbers.
pub(crate) fn check_m2_flush_wall() -> Result<Vec<u8>, String> {
use vernier_kernel::evaluate::{
BooleanKind, BooleanOptions, Transform, evaluate_boolean, evaluate_box, evaluate_transform,
};
use vernier_kernel::naming::NameTable;
const GAP: f64 = 5e-7;
const TOL: f64 = 1e-9;
let close = |what: &str, got: f64, want: f64| -> Result<(), String> {
if ((got - want) / want).abs() > TOL {
return Err(format!("{what} is {got}, expected {want}"));
}
Ok(())
};
let mut doc = Document::new();
block(&mut doc, "block", [0.0, 0.0], [40.0, 30.0], 15.0)?;
block(
&mut doc,
"neighbour",
[40.0 + GAP, 0.0],
[50.0 + GAP, 30.0],
15.0,
)?;
let mut kernel = vernier_kernel::OcctKernel::new();
let mut store = NamingStore::new();
let compiled =
compile_document(&mut doc, &mut kernel, &mut store).map_err(|e| e.to_string())?;
if !compiled.warnings.is_empty() {
return Err(format!(
"the flush fuse must warn of nothing: {:?}",
compiled.warnings
));
}
let body = compiled.body.ok_or("no fused body")?;
let (faces, solids, volume) = (
body.faces.len(),
body.summary.topology.solids,
body.summary.geometry.volume,
);
if (faces, solids) != (6, 1) {
return Err(format!(
"under the default the flush walls are {faces} faces in {solids} solids, not 6 in 1"
));
}
close("the fused volume", volume, 22500.0 + GAP * 30.0 * 15.0)?;
// The control: the same two boxes at zero fuzzy stay two solids.
let mut ids = Counter(0);
let mut tables = (NameTable::new(), NameTable::new(), NameTable::new());
let a = evaluate_box(&mut kernel, &mut tables.0, &mut ids, 40.0, 30.0, 15.0)
.map_err(|e| e.to_string())?;
let b_home = evaluate_box(&mut kernel, &mut tables.1, &mut ids, 10.0, 30.0, 15.0)
.map_err(|e| e.to_string())?;
let b = evaluate_transform(&kernel, &b_home, Transform::Translate(40.0 + GAP, 0.0, 0.0))
.map_err(|e| e.to_string())?;
let open = evaluate_boolean(
&kernel,
&mut tables.2,
&mut ids,
BooleanKind::Fuse,
&a,
&b,
BooleanOptions {
fuzzy_mm: 0.0,
simplify: true,
},
)
.map_err(|e| e.to_string())?;
let (open_faces, open_solids) = (open.faces.len(), open.summary.topology.solids);
if (open_faces, open_solids) != (12, 2) {
return Err(format!(
"at zero fuzzy the gap must stay open — {open_faces} faces in {open_solids} solids \
says δ* = {GAP} is not the noise, and the fixture is the bug"
));
}
close(
"the control's volume",
open.summary.geometry.volume,
22500.0,
)?;
Ok(format!(
"gap {GAP}\ndefault faces {faces} solids {solids} volume {volume}\n\
zero-fuzzy faces {open_faces} solids {open_solids}\n"
)
.into_bytes())
}
+4
View File
@@ -190,6 +190,10 @@ pub(crate) fn registry() -> Vec<Check> {
name: "m2-unify-push-pull",
run: m2_booleans::check_m2_unify_push_pull,
},
Check {
name: "m2-flush-wall",
run: m2_booleans::check_m2_flush_wall,
},
]
}
+19 -6
View File
@@ -25,12 +25,25 @@ use super::{CompileError, CompileWarning, evaluate};
const CUT_MIN_REMOVAL: f64 = 1e-9;
/// Fuzzy boolean tolerance as a fraction of the TARGET body's bounding-box
/// diagonal (façade-computed). Calibrated by
/// `a_flush_wall_fuses_to_one_face_under_the_default` and bounded above by
/// every analytic gate at 1e-9; measured value and both bounds in the
/// commit that set it (M2-4). `0.0` until then: the flip of `simplify`
/// (M2-3) and the fuzzy default are two decisions with two measurements.
pub(super) const FUZZY_FRACTION: f64 = 0.0;
/// diagonal (façade-computed). **Both bounds measured 2026-09-06 (M2-4):**
///
/// * Lower: two flush walls with a gap of δ* = 5e-7 mm are the smallest
/// gap OCCT's own confusion leaves open (2e-7 closes, 5e-7 is two solids
/// and twelve faces — `measure_flush_wall_noise`), and a fuzzy value
/// closes a gap iff it is ≥ the gap. The default must cover 2δ* on the
/// 52.2 mm block: fraction ≥ 1.9e-8. Calibrated by
/// `a_flush_wall_fuses_to_one_face_under_the_default`, proven by its
/// zero-fuzzy sibling.
/// * Upper: OCCT's fuzzy inflates the result's tolerances, and the
/// tolerance-inclusive `Bnd_Box` grows by ~1.6× the fuzzy value — which
/// is `bbox_diagonal`, NAMING.md §5's scale. Every analytic gate at 1e-9
/// holds at every fraction tried (mass properties carry no tolerance),
/// but the split-rebind fixture pins D at 1e-6 relative: 1e-6 fails
/// (1.6e-6), 1e-7 passes.
///
/// 1e-7 sits five times over the floor and five times under the ceiling:
/// 5.2e-6 mm on the 52 mm block, ten times δ*.
pub(super) const FUZZY_FRACTION: f64 = 1e-7;
/// The one production boolean policy: unify same-domain faces (M2-3,
/// Option C — a target face that absorbs tool faces of the same operation
+146 -1
View File
@@ -409,10 +409,14 @@ fn a_boss_bridging_two_strips_of_one_face_is_merged_by_name() {
.body
.unwrap();
near(slotted.summary.geometry.volume, 18000.0 - 10.0 * 30.0 * 5.0);
// The underside's two strips: normal −Z AND at z = 0 — the slot's
// ceiling at z = 5 faces down too.
let strips: Vec<_> = slotted
.faces
.iter()
.filter(|(_, r)| r.normal.is_some_and(|n| (n[2] + 1.0).abs() < 1e-9))
.filter(|(_, r)| {
r.normal.is_some_and(|n| (n[2] + 1.0).abs() < 1e-9) && r.centroid[2].abs() < 1e-9
})
.map(|(&id, _)| id)
.collect();
assert_eq!(strips.len(), 2, "the slot splits the underside in two");
@@ -465,3 +469,144 @@ fn a_boss_bridging_two_strips_of_one_face_is_merged_by_name() {
let wall = generated(&store, block, block_curves[0]).unwrap();
assert_eq!(filled.verdicts.get(wall), Some(&Verdict::Continued(wall)));
}
/// A standalone id source for kernel-level probes.
struct Counter(u64);
impl vernier_kernel::naming::EntityIdSource for Counter {
fn allocate(&mut self) -> vernier_kernel::EntityId {
self.0 += 1;
vernier_kernel::EntityId::from_raw(self.0)
}
}
/// **δ\***: the smallest gap between two flush walls that a zero-fuzzy
/// fuse leaves open, mm. Measured by `measure_flush_wall_noise`
/// (2026-09-06): OCCT's own confusion closes 2e-7 and stops at 5e-7 —
/// twelve faces, two solids. The default must cover twice this.
const FLUSH_GAP: f64 = 5e-7;
/// Fuses a 40 × 30 × 15 box with a 10 × 30 × 15 box whose left face sits
/// at `x = 40 + gap`, under `fuzzy_mm` (`None` = the production policy for
/// that target) and `simplify`. Returns (faces, solids, volume).
fn flush_fuse(gap: f64, fuzzy_mm: Option<f64>) -> (usize, u32, f64) {
use vernier_kernel::evaluate::{Transform, evaluate_box, evaluate_transform};
let mut kernel = OcctKernel::new();
let mut ids = Counter(0);
let mut table_a = NameTable::new();
let mut table_b = NameTable::new();
let mut fuse_table = NameTable::new();
let a = evaluate_box(&mut kernel, &mut table_a, &mut ids, 40.0, 30.0, 15.0).unwrap();
let b_home = evaluate_box(&mut kernel, &mut table_b, &mut ids, 10.0, 30.0, 15.0).unwrap();
let b =
evaluate_transform(&kernel, &b_home, Transform::Translate(40.0 + gap, 0.0, 0.0)).unwrap();
let fused = evaluate_boolean(
&kernel,
&mut fuse_table,
&mut ids,
BooleanKind::Fuse,
&a,
&b,
BooleanOptions {
fuzzy_mm: fuzzy_mm
.unwrap_or_else(|| crate::compile::merge::production_options(&a).fuzzy_mm),
simplify: true,
},
)
.unwrap();
(
fused.faces.len(),
fused.summary.topology.solids,
fused.summary.geometry.volume,
)
}
/// **The M2-4 noise probe** (spec B(5)): what gap between two flush walls
/// the fuse still closes at `fuzzy_mm = 0`, and what fuzzy value closes a
/// given gap. Print-only and ignored; run as
/// `cargo test -p vernier-ui measure_flush_wall_noise -- --ignored --nocapture`.
/// The numbers it printed are in the commit that set `FUZZY_FRACTION`.
#[test]
#[ignore = "measurement harness: prints, never asserts"]
fn measure_flush_wall_noise() {
let gaps = [
0.0, 1e-12, 1e-10, 1e-9, 1e-8, 5e-8, 1e-7, 1.5e-7, 2e-7, 5e-7, 1e-6, 1e-5, 1e-4,
];
let fuzzies = [0.0, 1e-8, 1e-7, 2e-7, 5e-7, 1e-6, 1e-5, 1e-4, 1e-3];
println!("gap \\ fuzzy: faces/solids/volume-22500");
for gap in gaps {
let mut row = format!("{gap:>8.1e}:");
for fuzzy in fuzzies {
let (faces, solids, volume) = flush_fuse(gap, Some(fuzzy));
row.push_str(&format!(
" {fuzzy:.0e}->{faces}/{solids}/{:+.2e}",
volume - 22500.0
));
}
println!("{row}");
}
}
/// **The M2-4 calibration fixture.** A 40 × 30 × 15 block and a 10 × 30 × 15
/// neighbour authored `FLUSH_GAP` = δ* past its `x = 40` wall — the gap a
/// zero-fuzzy fuse leaves open (`the_same_gap_stays_a_sliver_at_zero_fuzzy`
/// is the proof) — compiled through the production policy: ONE solid, 6
/// faces, and the volume of the union WITH THE GAP FILLED,
/// `40·30·15 + 10·30·15 + δ*·30·15 = 22500.000225`, at 1e-9 relative.
/// Measured: the fuzzy fuse fills the gap rather than snapping the
/// neighbour across it — 22500.000224999996 against 22500.000225, 1.8e-16
/// relative — so that is the exact number, and 22500 would be off by
/// 1e-8, ten times the gate. This is the zero-volume trap of MISTAKES.md
/// (2026-08-10) closed with a number rather than a warning.
#[test]
fn a_flush_wall_fuses_to_one_face_under_the_default() {
let mut doc = Document::new();
let (_, _) = boss(&mut doc, "block", [0.0, 0.0], [40.0, 30.0], 15.0, false);
let (_, _) = boss(
&mut doc,
"neighbour",
[40.0 + FLUSH_GAP, 0.0],
[50.0 + FLUSH_GAP, 30.0],
15.0,
false,
);
let mut kernel = OcctKernel::new();
let mut store = NamingStore::new();
let compiled = compile_document(&mut doc, &mut kernel, &mut store).unwrap();
assert!(compiled.warnings.is_empty(), "{:?}", compiled.warnings);
let body = compiled.body.unwrap();
assert_eq!(
body.summary.topology.solids, 1,
"the default closes the gap"
);
assert_eq!(body.faces.len(), 6, "one block, its flush walls unified");
near(
body.summary.geometry.volume,
22500.0 + FLUSH_GAP * 30.0 * 15.0,
);
}
/// The mutation that proves the default works: the SAME gap under
/// `fuzzy_mm = 0.0` is two solids and twelve faces. If this ever passes at
/// zero fuzzy, δ* was not the noise and the fixture above is the bug
/// (MISTAKES.md 2026-08-10) — so both halves are asserted here, against
/// the same two boxes.
#[test]
fn the_same_gap_stays_a_sliver_at_zero_fuzzy() {
let (faces, solids, volume) = flush_fuse(FLUSH_GAP, Some(0.0));
assert_eq!(
(faces, solids),
(12, 2),
"zero fuzzy must leave the gap open"
);
near(volume, 22500.0);
let (faces, solids, volume) = flush_fuse(FLUSH_GAP, None);
assert_eq!(
(faces, solids),
(6, 1),
"the production default must close it"
);
near(volume, 22500.0 + FLUSH_GAP * 30.0 * 15.0);
// …and a gap the confusion alone closes is closed either way, so the
// default is doing work only where zero fuzzy fails.
assert_eq!(flush_fuse(1e-7, Some(0.0)).0, 6);
}