Skip to main content

brep_kernel/csg/boolean/
mod.rs

1use crate::arrangement::Vec2;
2use crate::classification::{classify_point, parameter_point_in_face, PolygonClass};
3use crate::fragment::{FaceFragmentRecord, FragmentEdgeSource};
4use crate::imprint::{
5    EdgeSplitRecord, FaceImprints, FaceKey, ImprintOptions, ImprintPieceRecord,
6    ImprintResultRecord, ImprintVertex,
7};
8use crate::tolerance::{assembler_weld, commit_weld};
9use crate::topology::{
10    adaptive_coedge_error, BrepSolid, CoedgeRecord, EdgeRecord, FaceRecord, LoopRecord,
11    ShellRecord, VertexRecord,
12};
13use crate::{
14    KernelRefusal, OrRefuse, RefusalClass,
15    apply_edge_splits, apply_edge_splits_with_map, build_imprints, build_pcurve_on_surface,
16    build_pcurve_on_surface_range, classify_surface_pair,
17    fragment_solid, interpolate_curve, merge_curve_continuation_edges,
18    merge_same_surface_faces_excluding,
19    project_point_to_curve, project_point_to_surface, solid_signed_volume, AffineTransform,
20    DiagnosticSeverity, KernelDiagnostics, KernelOutcome, KernelStage, KernelTolerances, NurbsCurve,
21    PointClass, SolidClassifier, SurfacePairRelation, Vec3,
22};
23use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
24use serde::{Deserialize, Serialize};
25use web_time::Instant;
26
27thread_local! {
28    /// Running count (per thread) of one-use edges the EDGE-CONFORMANCE
29    /// repair lanes merged or bridged.  The lanes only fire on an assembly
30    /// that would otherwise refuse, so a zero delta across an operation is
31    /// an honest witness that the boolean assembled cleanly without the
32    /// repair.  Test-observable; not part of any result.
33    pub(crate) static CONFORMANCE_REPAIRS: std::cell::Cell<u64> =
34        const { std::cell::Cell::new(0) };
35}
36
37#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
38#[serde(rename_all = "lowercase")]
39pub enum BooleanOperation {
40    Union,
41    Intersect,
42    Subtract,
43}
44
45#[derive(Clone, Debug, Deserialize)]
46pub struct BooleanOptions {
47    #[serde(default = "default_tolerance")]
48    pub tolerance: f64,
49    /// Optional complete accuracy/search policy.  `tolerance` remains the
50    /// backwards-compatible model-identity shorthand when this is absent.
51    #[serde(default)]
52    pub tolerances: Option<KernelTolerances>,
53    #[serde(default)]
54    pub imprint: ImprintOptions,
55    /// Coalesce adjacent result fragments after assembly. This defaults to
56    /// true to preserve the kernel's normal clean-boundary behavior.
57    #[serde(default = "default_true")]
58    pub merge_coplanar_faces: bool,
59    /// Face-name substrings that PIN a result face out of the coplanar/cosurface
60    /// merge (only consulted when [`merge_coplanar_faces`] is on). A face whose
61    /// `name` CONTAINS any of these is emitted unchanged instead of being
62    /// coalesced with a mergeable neighbour; faces matching none merge exactly as
63    /// before. Default empty — no caller sees a behavior change unless it opts
64    /// in. Sheet metal uses this to keep every thickness/side wall face per
65    /// outline segment in the folded solid (so a flange can still attach to a
66    /// specific segment: fusing two collinear thickness faces would erase the
67    /// segment boundary).
68    #[serde(default)]
69    pub keep_unmerged_name_substrs: Vec<String>,
70}
71
72fn default_tolerance() -> f64 {
73    1e-7
74}
75
76fn default_true() -> bool {
77    true
78}
79
80impl Default for BooleanOptions {
81    fn default() -> Self {
82        Self {
83            tolerance: default_tolerance(),
84            tolerances: None,
85            imprint: ImprintOptions::default(),
86            merge_coplanar_faces: true,
87            keep_unmerged_name_substrs: Vec::new(),
88        }
89    }
90}
91
92mod select;
93mod assemble;
94mod operand_state;
95mod rim;
96mod coincident;
97
98use rim::*;
99use select::*;
100use coincident::*;
101pub(crate) use assemble::{
102    assemble_fragments, assemble_open_fragments, commit_nearby_edge_endpoints,
103    edge_interior_lies_on, finalize_assembled_solid,
104};
105// Currently referenced only inside `assemble`, so the re-export is unused.
106#[allow(unused_imports)]
107pub(crate) use assemble::apply_assembly_heal_chain;
108
109pub fn boolean_operation(
110    first: &BrepSolid,
111    second: &BrepSolid,
112    operation: BooleanOperation,
113    options: &BooleanOptions,
114) -> Result<BrepSolid, KernelRefusal> {
115    match boolean_operation_with_diagnostics(first, second, operation, options) {
116        Ok(outcome) => Ok(outcome.value),
117        Err(error) => {
118            // Perturbation-fallback lane (Simulation of Simplicity). The exact
119            // arrangement only errs on a DEGENERACY here (coincident /
120            // near-tangent carrier faces make it structurally inconsistent) —
121            // e.g. a partial torus whose equatorial circles lie exactly in a box
122            // face. Retry once on a rigidly perturbed second operand; accept only
123            // an oracle-clean, volume-consistent result. See
124            // `perturbation_retry`.
125            if std::env::var("BREP_NO_PERTURB").as_deref() == Ok("1")
126                || !error.class.perturbation_eligible()
127            {
128                return Err(error);
129            }
130            // OPERAND-CONSISTENCY GATE. A rigid nudge lifts a coincidence
131            // BETWEEN the operands; it cannot reconcile an operand with
132            // itself. A planar face whose straight boundary sits farther off
133            // its plane than the assembly weld — the shape the STEP importer's
134            // planar split repairs, so it reaches here only on a body built
135            // some other way — fails the exact arrangement for every rung
136            // alike: the 2026-09-12 mesh-import report paid twenty ~7 s rungs
137            // for one 3.4e-3 gap before the import split existed. Name the
138            // gap and stop. The predicate is deliberately the splitter's own,
139            // not a pcurve-error band: vendor bodies whose fitted edges stand
140            // a few 1e-4 off curved faces are legitimate input (per-entity
141            // tolerances) and keep every rung.
142            // OPERAND-CLOSURE GATE. A boolean of a body that is not a closed
143            // shell has no solid answer, exact or nudged: its inside is not
144            // defined, and its volume depends on the reference point. The
145            // slimmed fuzz fixtures (13, 14 and 15, operand A cut from a larger
146            // part by a box filter) carry 54, 97 and 66 edges that bound one
147            // face; every one of their 20 rungs refused on the exact lane, and
148            // 15's identity was read from an intersection with the open shell.
149            // Only consulted on the failure path, so nothing that succeeds
150            // today changes. Escape hatch: BREP_OPEN_OPERAND_GATE=0.
151            if std::env::var("BREP_OPEN_OPERAND_GATE").as_deref() != Ok("0") {
152                if let Some((operand, open)) = [first, second]
153                    .iter()
154                    .enumerate()
155                    .find_map(|(operand, solid)| {
156                        let open = one_use_edges(solid);
157                        (open > 0).then_some((operand, open))
158                    })
159                {
160                    return Err(error.with_message(|message| format!(
161                        "{message}; operand {operand} is not a closed shell: {open} of its edges bound \
162                         only one face, so no boolean of it encloses a solid and no perturbation \
163                         can close it"
164                    )));
165                }
166            }
167            if let Some((operand, face_id, deviation, weld)) =
168                planar_operand_off_its_plane(first, second, options)
169            {
170                return Err(error.with_message(|message| format!(
171                    "{message}; operand {operand} face {face_id} is a plane whose straight \
172                     boundary sits {deviation:.3e} off it (assembly weld {weld:.1e}), which \
173                     no perturbation can reconcile — the body's planes must contain their \
174                     boundary before it can take part in an exact boolean"
175                )));
176            }
177            // INTERNAL-TANGENCY PINCH GATE. Perturbation is only legitimate
178            // where the exact arrangement is UNSTABLE but its ANSWER is not:
179            // the nearby transversal case must carry the same topology the
180            // degenerate one does. An internal tangency in a DIFFERENCE breaks
181            // exactly that premise — the exact `A − B` pinches to zero
182            // thickness along the contact, so an epsilon either tears the wall
183            // open (a through-slot where the exact answer has intact material)
184            // or leaves a sub-micron web. Both are within epsilon in Hausdorff
185            // distance and in volume, so neither the oracle (boundary skip
186            // band) nor the 1% CSG volume bound can see them, and the lane
187            // would ship the tolerant-kernel sliver this kernel refuses. Only
188            // consulted on the failure path, so nothing that succeeds today can
189            // change; the honest exact-path error is returned instead.
190            // Escape hatch for tamper-verification: BREP_PINCH_GATE=0.
191            if matches!(operation, BooleanOperation::Subtract)
192                && std::env::var("BREP_PINCH_GATE").as_deref() != Ok("0")
193                && subtract_pinches_at_internal_tangency(first, second, options.tolerance)
194                    .unwrap_or(false)
195            {
196                return Err(error.with_message(|message| format!(
197                    "{message}; internal tangency: the operands touch tangentially with \
198                     co-directed normals, so the exact difference pinches to zero thickness \
199                     (non-manifold, unrepresentable in a boundary model) — refusing rather \
200                     than returning a perturbed sliver"
201                )));
202            }
203            // TANGENT-NODE GATE, same doctrine as the pinch gate above and for
204            // the same reason. A 4-valent tangent node is refused because the
205            // kernel cannot imprint it, and perturbation is only legitimate
206            // where the exact arrangement is UNSTABLE but its ANSWER is not:
207            // the nudged configuration resolves the kiss one way or the other
208            // and the kernel has no claim on which. Until 2026-09-13 the ladder
209            // could not produce a candidate here at all; with `fit_polyline`
210            // measuring its own result the equal-radius torus pair's LAST rung
211            // (3.0e-3 of scale) assembles, validates, and passes the semantic
212            // oracle at 399 samples with a zero disagreement rate, reading
213            // 299.4998500 against the ideal union's 299.6 — a 0.03% error that
214            // is the nudge's, not the kernel's answer. Shipping that would
215            // answer a question this class exists to decline, so the class does
216            // not take the ladder. Escape hatch for measuring it:
217            // BREP_TANGENT_NODE_GATE=0.
218            if matches!(error.class, RefusalClass::TangentNodeSingularity)
219                && std::env::var("BREP_TANGENT_NODE_GATE").as_deref() != Ok("0")
220            {
221                return Err(error);
222            }
223            match perturbation_retry(first, second, operation, options) {
224                Ok(solid) => Ok(solid),
225                Err(refused) if refused.is_empty() => Err(error),
226                Err(refused) => {
227                    Err(error.with_message(|message| format!("{message}; {}", refused.join("; "))))
228                }
229            }
230        }
231    }
232}
233
234/// Grid resolution (per parameter direction, endpoints included) used to probe a
235/// face pair for a tangential contact. Endpoints and the midpoint are both
236/// sampled, so a contact sitting on a periodic seam (a cylinder's `u = 0`
237/// ruling) and one sitting at a face's parametric centre are both hit exactly.
238const PINCH_PROBE_STEPS: usize = 8;
239
240/// `|n_a × n_b|` bound below which two unit normals count as parallel — the same
241/// bound `csg::imprint`'s pair classifier uses (`PAIR_ANGULAR_TOLERANCE`).
242const PINCH_ANGULAR_TOLERANCE: f64 = 1e-4;
243
244/// Parameter-space step (fraction of the domain span) taken around a contact
245/// sample to prove the contact is LOWER-DIMENSIONAL — a tangency curve/point
246/// rather than a cosurface patch.
247const PINCH_SEPARATION_STEP: f64 = 1e-2;
248
249/// Would the exact `first − second` PINCH to zero thickness at a tangential
250/// contact?
251///
252/// True when some face of `first` and some face of `second` touch tangentially
253/// (surfaces within the contact band, normals parallel) with CO-DIRECTED outward
254/// normals, at a contact that is lower-dimensional (the surfaces separate as you
255/// step away from it).
256///
257/// Co-directed outward normals at a tangency mean one solid lies locally INSIDE
258/// the other: subtracting leaves material on both sides of the contact that
259/// meets there at zero thickness — a non-manifold pinch no boundary model can
260/// represent. Anti-directed normals are the harmless EXTERNAL tangency (a
261/// cylinder resting against a wall): the difference simply keeps `first` intact.
262/// The lower-dimensionality requirement excludes a CO-SURFACE contact (a pocket
263/// wall flush with an outer wall — co-directed normals, but the surfaces stay
264/// coincident in every direction), where the difference is perfectly well
265/// behaved.
266///
267/// Conservative by construction: it only reports a pinch it can actually witness
268/// on the probe grid, and a miss just leaves the perturbation lane to its
269/// existing gates.
270fn subtract_pinches_at_internal_tangency(
271    first: &BrepSolid,
272    second: &BrepSolid,
273    tolerance: f64,
274) -> Result<bool, KernelRefusal> {
275    // Same contact band the imprint pair classifier uses for its sampled
276    // "these carriers touch" verdict.
277    let contact = (tolerance * 100.0).max(1e-12);
278    let probes_a = first
279        .shells
280        .iter()
281        .flat_map(|shell| &shell.faces)
282        .map(PinchProbe::of)
283        .collect::<Result<Vec<_>, _>>()?;
284    let probes_b = second
285        .shells
286        .iter()
287        .flat_map(|shell| &shell.faces)
288        .map(PinchProbe::of)
289        .collect::<Result<Vec<_>, _>>()?;
290    for probe_a in &probes_a {
291        for probe_b in &probes_b {
292            // Sampled-hull cull, so the (failure-path-only) cost stays linear in
293            // the faces that actually touch rather than quadratic in every face.
294            // The hull is built from the probe grid, so it can under-cover a
295            // curved carrier between samples: pad it by a percent of the pair's
296            // size before culling. Over-keeping a pair only costs a probe that
297            // finds nothing.
298            let pad = 1e-2 * probe_a.extent().max(probe_b.extent());
299            if probe_a.separation(probe_b) > contact + pad {
300                continue;
301            }
302            // CO-SURFACE contacts are 2-dimensional, not tangencies: two flush
303            // walls (a pocket's side coincident with an outer wall, a cylinder
304            // cap lying in a box face) have co-directed normals wherever the
305            // solids nest, yet the difference there is perfectly well behaved.
306            // Only a LOWER-dimensional contact pinches, so drop the pair the
307            // pair classifier calls cosurface before probing it.
308            if classify_surface_pair(
309                &probe_a.face.surface,
310                &probe_b.face.surface,
311                tolerance,
312                PINCH_ANGULAR_TOLERANCE,
313            ).or_refuse(KernelStage::Validate, "csg.boolean.mod")?
314            .relation
315                == SurfacePairRelation::Cosurface
316            {
317                continue;
318            }
319            if faces_touch_with_codirected_normals(probe_a, probe_b, contact)? {
320                return Ok(true);
321            }
322        }
323    }
324    Ok(false)
325}
326
327/// A face's probe grid plus the grid's bounding box, built once per face so the
328/// pair loop only pays for a hull comparison.
329struct PinchProbe<'a> {
330    face: &'a FaceRecord,
331    /// `(u, v, point)` on the face's carrier surface.
332    samples: Vec<(f64, f64, Vec3)>,
333    minimum: [f64; 3],
334    maximum: [f64; 3],
335}
336
337impl<'a> PinchProbe<'a> {
338    fn of(face: &'a FaceRecord) -> Result<Self, KernelRefusal> {
339        let [u0, u1] = face.surface.domain_u().or_refuse(KernelStage::Validate, "domain_u")?;
340        let [v0, v1] = face.surface.domain_v().or_refuse(KernelStage::Validate, "domain_v")?;
341        let steps = PINCH_PROBE_STEPS as f64;
342        let mut samples = Vec::with_capacity((PINCH_PROBE_STEPS + 1).pow(2));
343        let mut minimum = [f64::INFINITY; 3];
344        let mut maximum = [f64::NEG_INFINITY; 3];
345        for i in 0..=PINCH_PROBE_STEPS {
346            let u = u0 + (u1 - u0) * i as f64 / steps;
347            for j in 0..=PINCH_PROBE_STEPS {
348                let v = v0 + (v1 - v0) * j as f64 / steps;
349                let Ok(point) = face.surface.evaluate(u, v) else {
350                    continue;
351                };
352                for (axis, value) in [point.x, point.y, point.z].into_iter().enumerate() {
353                    minimum[axis] = minimum[axis].min(value);
354                    maximum[axis] = maximum[axis].max(value);
355                }
356                samples.push((u, v, point));
357            }
358        }
359        Ok(Self {
360            face,
361            samples,
362            minimum,
363            maximum,
364        })
365    }
366
367    /// Largest side of the sampled hull (0 when nothing sampled).
368    fn extent(&self) -> f64 {
369        (0..3)
370            .map(|axis| self.maximum[axis] - self.minimum[axis])
371            .fold(0.0f64, f64::max)
372    }
373
374    /// Axis-aligned gap between the two sampled hulls (0 when they overlap).
375    fn separation(&self, other: &Self) -> f64 {
376        let mut gap: f64 = 0.0;
377        for axis in 0..3 {
378            gap = gap.max(self.minimum[axis] - other.maximum[axis]);
379            gap = gap.max(other.minimum[axis] - self.maximum[axis]);
380        }
381        gap
382    }
383}
384
385/// One face pair of [`subtract_pinches_at_internal_tangency`]: probe both
386/// surfaces on a grid, keep samples that land on the other surface inside BOTH
387/// trims with parallel co-directed outward normals, and accept only where the
388/// contact provably separates nearby.
389fn faces_touch_with_codirected_normals(
390    probe_a: &PinchProbe<'_>,
391    probe_b: &PinchProbe<'_>,
392    contact: f64,
393) -> Result<bool, KernelRefusal> {
394    for (probe, other) in [(probe_a, probe_b), (probe_b, probe_a)] {
395        let source = probe.face;
396        let target = other.face;
397        for &(u, v, point) in &probe.samples {
398            let projection = project_point_to_surface(&target.surface, point).or_refuse(KernelStage::Validate, "project_point_to_surface")?;
399            if projection.distance > contact {
400                continue;
401            }
402            // A pole (collapsed du × dv) has no reliable orientation here;
403            // skipping it keeps the predicate conservative.
404            let (Ok(source_normal), Ok(target_normal)) = (
405                source.surface.normal(u, v),
406                target.surface.normal(projection.u, projection.v),
407            ) else {
408                continue;
409            };
410            let source_outward = outward_normal(source_normal, source.same_sense);
411            let target_outward = outward_normal(target_normal, target.same_sense);
412            if source_outward.cross(target_outward).length() > PINCH_ANGULAR_TOLERANCE
413                || source_outward.dot(target_outward) <= 0.0
414            {
415                continue;
416            }
417            if parameter_point_in_face(source, Vec2 { x: u, y: v }, 1e-6).or_refuse(KernelStage::Validate, "parameter_point_in_face")?
418                == PolygonClass::Outside
419                || parameter_point_in_face(
420                    target,
421                    Vec2 {
422                        x: projection.u,
423                        y: projection.v,
424                    },
425                    1e-6,
426                ).or_refuse(KernelStage::Validate, "csg.boolean.mod")? == PolygonClass::Outside
427            {
428                continue;
429            }
430            if contact_separates_locally(source, target, u, v, contact)? {
431                return Ok(true);
432            }
433        }
434    }
435    Ok(false)
436}
437
438fn outward_normal(normal: Vec3, same_sense: bool) -> Vec3 {
439    if same_sense {
440        normal
441    } else {
442        normal.scale(-1.0)
443    }
444}
445
446/// Does the contact at `source(u, v)` LEAVE the contact band when you step away
447/// from it in parameter space? A tangency curve or point does (stepping across
448/// the tangency separates the surfaces quadratically); a cosurface patch does
449/// not (it stays coincident in every direction). Steps are clamped into the
450/// domain, so a sample on a seam or a domain edge probes only inward.
451fn contact_separates_locally(
452    source: &FaceRecord,
453    target: &FaceRecord,
454    u: f64,
455    v: f64,
456    contact: f64,
457) -> Result<bool, KernelRefusal> {
458    let [u0, u1] = source.surface.domain_u().or_refuse(KernelStage::Validate, "domain_u")?;
459    let [v0, v1] = source.surface.domain_v().or_refuse(KernelStage::Validate, "domain_v")?;
460    let step_u = (u1 - u0) * PINCH_SEPARATION_STEP;
461    let step_v = (v1 - v0) * PINCH_SEPARATION_STEP;
462    for (probe_u, probe_v) in [
463        ((u + step_u).min(u1), v),
464        ((u - step_u).max(u0), v),
465        (u, (v + step_v).min(v1)),
466        (u, (v - step_v).max(v0)),
467    ] {
468        if (probe_u - u).abs() < f64::EPSILON && (probe_v - v).abs() < f64::EPSILON {
469            continue;
470        }
471        let Ok(point) = source.surface.evaluate(probe_u, probe_v) else {
472            continue;
473        };
474        // A generous multiple of the band: the step must clear it decisively,
475        // never on projection noise.
476        if project_point_to_surface(&target.surface, point).or_refuse(KernelStage::Validate, "project_point_to_surface")?.distance > contact * 10.0 {
477            return Ok(true);
478        }
479    }
480    Ok(false)
481}
482
483/// Deterministic perturbation-fallback (Simulation of Simplicity), the textbook
484/// CAD answer to an exact/near-tangent arrangement degeneracy, adopted after six
485/// incremental arrangement/imprint/fragment fixes for the near-tangent class were
486/// each falsified, rather than attempting a seventh. Rigidly TRANSLATE the second operand by a tiny
487/// epsilon so the degenerate contact becomes a clean transversal crossing, redo
488/// the boolean, and accept the result ONLY if it is (a) topologically valid —
489/// guaranteed by `assemble`'s own gate returning `Ok` — (b) semantically equal to
490/// the exact CSG of the ORIGINAL operands (the point-classification oracle), and
491/// (c) volume-consistent with the CSG inequality bounds. A wrong perturbed result
492/// is rejected on (b)/(c), so this lane preserves the engine's fail-safe property:
493/// it can only turn a degeneracy-error into a CORRECT solid, never a wrong one.
494///
495/// INVARIANT — no recursion: this calls `boolean_operation_with_diagnostics`
496/// (the EXACT path), never `boolean_operation`, so the fallback can never
497/// re-enter itself. A future refactor must preserve that.
498///
499/// (d) THE IDENTITY. Where the exact lane answers one of the other operations
500/// on the same operands, inclusion–exclusion names the volume this result
501/// must have, and a rung that misses it by more than the case gate's own drift
502/// band (`VOLUME_DRIFT_REL` of that volume, `VOLUME_DRIFT_ABS` floor) is
503/// refused by name. (c)'s 1% of the operands' total cannot see an error in a
504/// small removed volume: on the helmet's cutter − helmet (2026-09-15) the rung
505/// at fraction 3e-4 moved the helmet 5.2e-3, 2.3% of its size, passed the
506/// oracle at 1 in 336, and read 999.999899266 against V(cutter) − V(cutter ∩
507/// helmet) = 999.999905792: 6.9% too much material removed. Where no other
508/// operation clears on the exact lane there is no identity to hold, and the
509/// rung is held to (e) instead. Escape hatch for measuring it:
510/// BREP_PERTURB_IDENTITY=0 (which also lifts (e)).
511///
512/// (e) THE MIRROR. Where no exact-lane boolean of the operands clears in
513/// either order, the rung is held to itself moved the other way: the same
514/// operation, or failing that any sibling reading, on the second operand moved
515/// by −offset, with V(first) and V(second) from the mass integrator and the
516/// operation's sign, names the volume the mirrored rung encloses. A rigid
517/// translation changes the answer to first order in the offset, so half the
518/// difference between the rung's body and its mirror is the body's
519/// first-order translation error, and it must clear the same drift band. A
520/// rung whose mirror clears no boolean at all has nothing to check it and is
521/// refused by name. On 22_sag_boundary_identity_t164 (2026-09-15) the four
522/// shipped rescues at offset 9.7e-4 disagreed with each other through V(∩) by
523/// 4.3e-2 (the difference moves the part; the others move the sphere), against
524/// a bar of 5e-6. Escape hatch: BREP_PERTURB_MIRROR=0.
525///
526/// (d) and (e) run before (b), the oracle, which costs more than either.
527///
528/// (f) THE EARLY EXIT. Along one direction the mirror's error grows with the
529/// offset (`MirrorTrend` states the law and its measurement), so a direction
530/// whose two refused rungs read one sign, growing, is not tried at larger
531/// offsets, and the refusal names it. booleanIssue's refusal walked 20 rungs,
532/// each with its mirror, in 40.5 s against the case's 30 s. Escape hatch:
533/// BREP_MIRROR_EARLY_EXIT=0.
534///
535/// `Err` carries one sentence per rung refused on (d), empty when none was.
536fn perturbation_retry(
537    first: &BrepSolid,
538    second: &BrepSolid,
539    operation: BooleanOperation,
540    options: &BooleanOptions,
541) -> Result<BrepSolid, Vec<String>> {
542    // Generic translation directions: each has a substantial component on ALL
543    // three axes, so whatever axis the coincidence normal lies along (an
544    // equatorial plane flush with a box face, a cap plane flush with a side
545    // face, …) at least one direction has a component that lifts the contact off
546    // exact tangency. Axis-aligned directions are deliberately excluded — a nudge
547    // parallel to the coincident plane leaves the degeneracy in place (verified:
548    // pure ±X / ±Z never rescue the flush-torus case). Fixed, not hash-seeded
549    // as the lane was first sketched: the seeding's only purpose was to rule out
550    // RNG/clock nondeterminism, which fixed vectors already satisfy, and four
551    // diverse directions cover any axis-aligned normal.
552    const DIRECTIONS: [[f64; 3]; 4] = [
553        [0.4034, 0.7973, 0.4491],
554        [0.7973, 0.4491, 0.4034],
555        [0.4491, 0.4034, 0.7973],
556        [0.5774, -0.5774, 0.5774],
557    ];
558    // Fractions of operand scale, ordered SMALLEST-FIRST so the accepted solid
559    // carries the least geometric offset. Sized above the near-tangent sliver
560    // dead zone and below local feature size; the ladder + accept-gate make the
561    // exact magnitude non-critical (a rung that lands in a dead zone simply fails
562    // the gate and the next rung is tried).
563    const FRACTIONS: [f64; 5] = PERTURBATION_FRACTIONS;
564
565    let debug = std::env::var("BREP_DEBUG_BOOL").is_ok();
566    let scale = crate::tolerance::solid_scale(first)
567        .max(crate::tolerance::solid_scale(second))
568        .max(1.0);
569
570    // Original operand volumes for the CSG inequality backstop (the oracle skips
571    // points near boundaries, so a wrong thin shell hugging the flush face is
572    // invisible to it but shows up here).
573    let _caller = crate::mass_caller("boolean.perturbation_rescue");
574    let va = solid_signed_volume(first).ok().map(f64::abs);
575    let vb = solid_signed_volume(second).ok().map(f64::abs);
576    let hold_identity = std::env::var("BREP_PERTURB_IDENTITY").as_deref() != Ok("0");
577    // (e) is held only with (d): `BREP_PERTURB_MIRROR=0` alone ships a rung
578    // with no identity on (a)–(c), as before.
579    let hold_mirror = hold_identity && std::env::var("BREP_PERTURB_MIRROR").as_deref() != Ok("0");
580    // (f) A direction whose mirror errors say no larger offset can clear is
581    // closed. Escape hatch: `BREP_MIRROR_EARLY_EXIT=0` walks every rung.
582    let early_exit = std::env::var("BREP_MIRROR_EARLY_EXIT").as_deref() != Ok("0");
583    let mut trends = [MirrorTrend::default(); 4];
584    // Measured once, on the first rung that clears (a)–(c).
585    let mut identity: Option<Option<InclusionExclusion>> = None;
586    let mut refused = Vec::new();
587    // `BREP_DEBUG_PERTURB_LADDER=1`: every rung's disposition on stderr, and
588    // for every rung that builds, its miss against the identity, measured
589    // before the ladder starts. Changes no decision.
590    let ladder = std::env::var("BREP_DEBUG_PERTURB_LADDER").is_ok();
591    let ladder_start = web_time::Instant::now();
592    if ladder {
593        let measured = inclusion_exclusion_volume(first, second, operation, options, va, vb, false);
594        match &measured {
595            Some(identity) => eprintln!(
596                "[ladder] identity {} = {:.9} from the exact lane's {} ({:.9}); V(first) {:?} V(second) {:?}",
597                identity.formula, identity.expected, identity.reading, identity.read, va, vb
598            ),
599            None => eprintln!("[ladder] no identity: no exact-lane boolean of these operands clears in either order"),
600        }
601        identity = Some(measured);
602    }
603    let rung_miss = |identity: &Option<Option<InclusionExclusion>>, candidate: &BrepSolid| -> String {
604        let volume = body_volume(candidate);
605        match (identity, volume) {
606            (Some(Some(identity)), Some(volume)) => format!(
607                "volume {volume:.9} miss {:+.3e} bar {:.3e}",
608                volume - identity.expected,
609                crate::VOLUME_DRIFT_ABS.max(crate::VOLUME_DRIFT_REL * identity.expected.abs())
610            ),
611            (_, Some(volume)) => format!("volume {volume:.9}"),
612            _ => "no volume".to_string(),
613        }
614    };
615
616    for &fraction in &FRACTIONS {
617        let magnitude = scale * fraction;
618        for (direction, dir) in DIRECTIONS.iter().enumerate() {
619            if trends[direction].closed {
620                if ladder {
621                    eprintln!("[ladder] dir={dir:?} fraction={fraction:.1e} offset {magnitude:.3e}: not tried, its direction is closed");
622                }
623                continue;
624            }
625            let offset = [dir[0] * magnitude, dir[1] * magnitude, dir[2] * magnitude];
626            let translate = match AffineTransform::new([
627                1.0, 0.0, 0.0, offset[0], //
628                0.0, 1.0, 0.0, offset[1], //
629                0.0, 0.0, 1.0, offset[2], //
630                0.0, 0.0, 0.0, 1.0,
631            ]) {
632                Ok(transform) => transform,
633                Err(_) => continue,
634            };
635            let moved = match crate::transform_brep(second, translate, false) {
636                Ok(solid) => solid,
637                Err(_) => continue,
638            };
639            let rung = format!("dir={dir:?} fraction={fraction:.1e} offset {magnitude:.3e}");
640            let rung_start = web_time::Instant::now();
641            let candidate =
642                match boolean_operation_with_diagnostics(first, &moved, operation, options) {
643                    Ok(outcome) => outcome.value,
644                    Err(error) => {
645                        if ladder {
646                            eprintln!("[ladder] {rung}: exact lane refused {:?}", error.class);
647                        }
648                        continue;
649                    }
650                };
651            let built = rung_start.elapsed();
652            // A perturbation must not "rescue" a case into emptiness.
653            if candidate.shells.is_empty() {
654                if ladder {
655                    eprintln!("[ladder] {rung}: empty");
656                }
657                continue;
658            }
659            // (c) Volume must satisfy the CSG inequality for the ORIGINAL
660            // operands (generous 1% relative slack: catches gross wrongness —
661            // doubled/halved bodies, missing lobes — without rejecting the
662            // legitimate sub-micron offset).
663            if let (Some(va), Some(vb)) = (va, vb) {
664                if let Ok(vr) = solid_signed_volume(&candidate) {
665                    if !volume_within_csg_bounds(operation, va, vb, vr.abs()) {
666                        if ladder {
667                            eprintln!("[ladder] {rung}: outside the CSG bound, {}", rung_miss(&identity, &candidate));
668                        }
669                        continue;
670                    }
671                }
672            }
673            // (d) and (e) run before (b): each costs a volume, and (e) one
674            // exact-lane boolean, where the oracle classifies 400 points. A
675            // rung ships only when every gate clears, so the order decides
676            // which gate names a refusal, never which rung ships.
677            let bounded = rung_start.elapsed();
678            // (d) The exact lane's inclusion–exclusion identity.
679            if hold_identity {
680                let identity = identity.get_or_insert_with(|| {
681                    let identity =
682                        inclusion_exclusion_volume(first, second, operation, options, va, vb, false);
683                    if debug && identity.is_none() {
684                        eprintln!(
685                            "[perturb] no exact-lane boolean of these operands clears in either \
686                             order; no identity to hold"
687                        );
688                    }
689                    identity
690                });
691                if let Some(identity) = identity {
692                    let expected = identity.expected;
693                    let bar = crate::VOLUME_DRIFT_ABS.max(crate::VOLUME_DRIFT_REL * expected.abs());
694                    let volume = body_volume(&candidate);
695                    let miss = volume.map(|volume| (volume - expected).abs());
696                    if !miss.is_some_and(|miss| miss <= bar) {
697                        let sentence = format!(
698                            "perturbation rung dir={dir:?} fraction={fraction:.1e} \
699                             (offset {magnitude:.3e}) refused: its body reads {} against the \
700                             inclusion–exclusion identity {} = {expected:.9} (the exact lane's \
701                             {} reads {:.9}), a miss of {} beyond the volume drift bar {bar:.3e}",
702                            volume.map_or("no volume".to_string(), |volume| format!("{volume:.9}")),
703                            identity.formula,
704                            identity.reading,
705                            identity.read,
706                            miss.map_or("unmeasured".to_string(), |miss| format!("{miss:.3e}")),
707                        );
708                        if debug {
709                            eprintln!("[perturb] {sentence}");
710                        }
711                        if ladder {
712                            eprintln!(
713                                "[ladder] {rung}: refused by the identity, volume {} miss {} bar {bar:.3e}",
714                                volume.map_or("none".to_string(), |volume| format!("{volume:.9}")),
715                                volume.map_or("unmeasured".to_string(), |volume| format!("{:+.3e}", volume - expected)),
716                            );
717                        }
718                        refused.push(sentence);
719                        continue;
720                    }
721                } else if hold_mirror {
722                    // (e) No exact reading of these operands: the rung's
723                    // mirror.
724                    let mirror_start = web_time::Instant::now();
725                    let mirrored = AffineTransform::new([
726                        1.0, 0.0, 0.0, -offset[0], //
727                        0.0, 1.0, 0.0, -offset[1], //
728                        0.0, 0.0, 1.0, -offset[2], //
729                        0.0, 0.0, 0.0, 1.0,
730                    ])
731                    .ok()
732                    .and_then(|transform| crate::transform_brep(second, transform, false).ok());
733                    let mirror = mirrored.as_ref().and_then(|mirrored| {
734                        inclusion_exclusion_volume(first, mirrored, operation, options, va, vb, true)
735                    });
736                    let mirror_time = mirror_start.elapsed();
737                    let volume = body_volume(&candidate);
738                    let mut measured = None;
739                    let verdict = match (&mirror, volume) {
740                        (Some(mirror), Some(volume)) => {
741                            let bar = crate::VOLUME_DRIFT_ABS.max(crate::VOLUME_DRIFT_REL * mirror.expected.abs());
742                            let miss = 0.5 * (volume - mirror.expected).abs();
743                            measured = Some((0.5 * (volume - mirror.expected), bar));
744                            if ladder {
745                                eprintln!(
746                                    "[ladder] {rung}: mirror at −offset reads {} = {:.9} (the mirrored {} {:.9}); body {volume:.9}, first-order translation error {:+.3e} bar {bar:.3e}",
747                                    mirror.formula, mirror.expected, mirror.reading, mirror.read, 0.5 * (volume - mirror.expected)
748                                );
749                            }
750                            (miss > bar).then(|| format!(
751                                "perturbation rung dir={dir:?} fraction={fraction:.1e} \
752                                 (offset {magnitude:.3e}) refused: no exact-lane boolean of the \
753                                 operands clears, so the rung is held to its mirror: its body reads \
754                                 {volume:.9} with the second operand moved +offset and {} = {:.9} \
755                                 with it moved −offset (the mirrored {} reads {:.9}), a first-order \
756                                 translation error of {miss:.3e} beyond the volume drift bar {bar:.3e}",
757                                mirror.formula, mirror.expected, mirror.reading, mirror.read
758                            ))
759                        }
760                        _ => Some(format!(
761                            "perturbation rung dir={dir:?} fraction={fraction:.1e} \
762                             (offset {magnitude:.3e}) refused: no exact-lane boolean of the operands \
763                             clears, and none clears with the second operand moved −offset either, \
764                             so nothing checks the rung's body{}",
765                            volume.map_or(String::new(), |volume| format!(" ({volume:.9})"))
766                        )),
767                    };
768                    match verdict {
769                        None => trends[direction].cleared(),
770                        Some(sentence) => {
771                            if debug {
772                                eprintln!("[perturb] {sentence}");
773                            }
774                            if ladder {
775                                eprintln!(
776                                    "[ladder] {rung}: refused by its mirror (rung {:.0} ms: built {:.0}, bound {:.0}, mirror {:.0} ms; ladder {:.1} s)",
777                                    rung_start.elapsed().as_secs_f64() * 1e3,
778                                    built.as_secs_f64() * 1e3,
779                                    (bounded - built).as_secs_f64() * 1e3,
780                                    mirror_time.as_secs_f64() * 1e3,
781                                    ladder_start.elapsed().as_secs_f64()
782                                );
783                            }
784                            refused.push(sentence);
785                            // (f) Two refused rungs of one sign, growing, close
786                            // the direction.
787                            let closure = measured.and_then(|(error, bar)| {
788                                let earlier = trends[direction].refused(magnitude, error, early_exit)?;
789                                Some((earlier, error, bar))
790                            });
791                            if let Some(((earlier_offset, earlier_error), error, bar)) = closure {
792                                let closed = format!(
793                                    "perturbation direction dir={dir:?} closed after offset {magnitude:.3e}: its \
794                                     rungs' first-order translation errors read {earlier_error:+.3e} at offset \
795                                     {earlier_offset:.3e} and {error:+.3e} at {magnitude:.3e}, one sign and \
796                                     growing, so no larger offset along it comes back to the volume drift bar \
797                                     {bar:.3e}"
798                                );
799                                if debug {
800                                    eprintln!("[perturb] {closed}");
801                                }
802                                if ladder {
803                                    eprintln!("[ladder] {closed}");
804                                }
805                                refused.push(closed);
806                            }
807                            continue;
808                        }
809                    }
810                }
811            }
812            // (b) Semantic agreement with the exact CSG of the ORIGINAL
813            // operands. Reject a vacuous verdict (skip-band swallowed every
814            // sample so nothing was actually checked).
815            match crate::oracle::boolean_semantic_disagreement(
816                first, second, operation, &candidate, 400,
817            ) {
818                Ok(report) if report.considered >= 30 && !report.is_flagged() => {
819                    if ladder {
820                        eprintln!(
821                            "[ladder] {rung}: accepted (oracle {} considered, rate {:.4}), {}",
822                            report.considered, report.disagreement_rate, rung_miss(&identity, &candidate)
823                        );
824                    }
825                    if debug {
826                        eprintln!(
827                            "[perturb] rescued {operation:?}: dir={dir:?} fraction={fraction:.1e} \
828                             magnitude={magnitude:.3e} offset={offset:?} \
829                             (oracle considered={} rate={:.4})",
830                            report.considered, report.disagreement_rate
831                        );
832                    }
833                    return Ok(candidate);
834                }
835                report => {
836                    if ladder {
837                        let verdict = match &report {
838                            Ok(report) => format!("oracle {} considered, rate {:.4}", report.considered, report.disagreement_rate),
839                            Err(error) => format!("oracle error {error:?}"),
840                        };
841                        eprintln!("[ladder] {rung}: refused by the oracle ({verdict}), {}", rung_miss(&identity, &candidate));
842                    }
843                    continue;
844                }
845            }
846        }
847    }
848    if debug {
849        eprintln!("[perturb] ladder exhausted for {operation:?}; no rung produced a clean result");
850    }
851    Err(refused)
852}
853
854/// The perturbation ladder's offsets, as fractions of the operands' scale,
855/// smallest first.
856const PERTURBATION_FRACTIONS: [f64; 5] = [3e-5, 1e-4, 3e-4, 1e-3, 3e-3];
857
858/// (f) One ladder direction's rungs refused by their mirrors.
859///
860/// Along one direction a rung's signed first-order translation error, half
861/// its body less its mirror's reading, reads s(δ) = kδ + r: a slope k from the
862/// translation, and a remainder r from the degeneracy the rung steps over,
863/// which does not grow with the offset. Two refused rungs at δ₁ < δ₂ with one
864/// sign and |s(δ₂)| ≥ |s(δ₁)| fix k's sign as theirs, so every larger offset
865/// reads farther from zero than s(δ₂), which is already beyond the drift bar:
866/// the direction is closed. Rungs of one sign that shrink toward zero, or that
867/// change sign, say nothing about k, and the direction stays open; a rung whose
868/// mirror clears starts the direction's record again.
869///
870/// Measured 2026-09-15 (`BREP_DEBUG_PERTURB_LADDER=1` prints s). On
871/// 22_sag_boundary_identity_t164, all four operations and all four
872/// directions, s/δ keeps one sign and holds to 3.6% over the ladder's 100× of
873/// offset (r ≈ 0; −22.23 .. −22.34 on the intersection's first direction). On
874/// booleanIssue's flush partial torus union, s/δ holds to 0.5% and 8.5% on two
875/// directions, and to 1.2% on a third past its smallest rung, whose remainder
876/// reads −43.4 against the slope's −24.5. The first direction's smallest rung
877/// reads −1.891e-2 where its slope, +2.3 .. +2.6, gives +3.5e-3 (r ≈ −2.2e-2),
878/// and its next measured rung +3.510e-2: a change of sign, which keeps it open.
879/// A closed direction's skipped rung would have had to fall to the bar, 8.3e2 ..
880/// 1.5e6 times below the smallest refused |s| on those rows, to have cleared.
881#[derive(Clone, Copy, Default)]
882struct MirrorTrend {
883    /// The last rung refused by its mirror: its offset and signed error.
884    last: Option<(f64, f64)>,
885    closed: bool,
886}
887
888impl MirrorTrend {
889    /// Records a rung refused by its mirror at `offset` with signed error
890    /// `error`. When it and the direction's last refused rung close the
891    /// direction, and `close` allows it, the direction is closed and the
892    /// earlier rung's (offset, signed error) returned.
893    fn refused(&mut self, offset: f64, error: f64, close: bool) -> Option<(f64, f64)> {
894        let earlier = self
895            .last
896            .filter(|&(_, earlier)| earlier.signum() == error.signum() && error.abs() >= earlier.abs())
897            .filter(|_| close);
898        self.last = Some((offset, error));
899        self.closed |= earlier.is_some();
900        earlier
901    }
902
903    /// A rung whose mirror clears: the direction's record starts again.
904    fn cleared(&mut self) {
905        self.last = None;
906    }
907}
908
909/// Non-degenerate edges a solid's faces use exactly once.
910fn one_use_edges(solid: &BrepSolid) -> usize {
911    let mut uses: HashMap<u64, usize> = HashMap::default();
912    for coedge in solid
913        .shells
914        .iter()
915        .flat_map(|shell| &shell.faces)
916        .flat_map(|face| &face.loops)
917        .flat_map(|loop_record| &loop_record.coedges)
918    {
919        *uses.entry(coedge.edge_id).or_default() += 1;
920    }
921    solid
922        .edges
923        .iter()
924        .filter(|edge| !edge.degenerate && uses.get(&edge.id) == Some(&1))
925        .count()
926}
927
928/// A body's enclosed volume, zero for an empty one.
929fn body_volume(solid: &BrepSolid) -> Option<f64> {
930    if solid.shells.is_empty() {
931        return Some(0.0);
932    }
933    solid_signed_volume(solid).ok().map(f64::abs)
934}
935
936/// What the exact lane's other operations say `operation` on these operands
937/// must enclose.
938struct InclusionExclusion {
939    /// The rescued operation's volume in terms of V(first), V(second) and
940    /// V(first ∩ second).
941    formula: &'static str,
942    /// The exact-lane boolean that supplied V(first ∩ second).
943    reading: &'static str,
944    /// That boolean's enclosed volume.
945    read: f64,
946    /// The volume `formula` gives.
947    expected: f64,
948}
949
950/// The volume `operation` on these operands must have, by inclusion–exclusion.
951/// Any one exact-lane boolean of the same two operands fixes V(first ∩ second),
952/// and with the operands' own volumes that fixes all four operations. The
953/// readings are tried intersection, union, then difference, each in both
954/// operand orders: the exact lane is not order-symmetric (the helmet's cutter
955/// ∩ helmet refuses where helmet ∩ cutter clears). The rescued operation itself,
956/// in its own order, is skipped, since it is the one that refused, unless
957/// `own_first` asks for it to be read first (a rung's mirror, where it is a
958/// different pair of operands). `None` when no reading clears or an operand has
959/// no volume.
960fn inclusion_exclusion_volume(
961    first: &BrepSolid,
962    second: &BrepSolid,
963    operation: BooleanOperation,
964    options: &BooleanOptions,
965    va: Option<f64>,
966    vb: Option<f64>,
967    own_first: bool,
968) -> Option<InclusionExclusion> {
969    use BooleanOperation::{Intersect, Subtract, Union};
970    let (va, vb) = (va?, vb?);
971    let own = match operation {
972        Intersect => (Intersect, false, "first ∩ second"),
973        Union => (Union, false, "first ∪ second"),
974        Subtract => (Subtract, false, "first − second"),
975    };
976    let readings = [
977        (Intersect, false, "first ∩ second"),
978        (Intersect, true, "second ∩ first"),
979        (Union, false, "first ∪ second"),
980        (Union, true, "second ∪ first"),
981        (Subtract, false, "first − second"),
982        (Subtract, true, "second − first"),
983    ];
984    let own_reading = own_first.then_some(own);
985    let leading = usize::from(own_first);
986    for (index, (other, swapped, reading)) in own_reading.into_iter().chain(readings).enumerate() {
987        if index >= leading && !swapped && std::mem::discriminant(&other) == std::mem::discriminant(&operation) {
988            continue;
989        }
990        let (x, y) = if swapped { (second, first) } else { (first, second) };
991        let Some(read) = boolean_operation_with_diagnostics(x, y, other, options)
992            .ok()
993            .and_then(|outcome| body_volume(&outcome.value))
994        else {
995            continue;
996        };
997        let intersection = match (other, swapped) {
998            (Intersect, _) => read,
999            (Union, _) => va + vb - read,
1000            (Subtract, false) => va - read,
1001            (Subtract, true) => vb - read,
1002        };
1003        let (formula, expected) = match operation {
1004            Intersect => ("V(first ∩ second)", intersection),
1005            Union => ("V(first) + V(second) − V(first ∩ second)", va + vb - intersection),
1006            Subtract => ("V(first) − V(first ∩ second)", va - intersection),
1007        };
1008        return Some(InclusionExclusion { formula, reading, read, expected });
1009    }
1010    None
1011}
1012
1013/// The worst planar face of either operand whose STRAIGHT boundary sits
1014/// farther off its plane than the assembly weld radius:
1015/// `(operand index, face id, gap, weld)`. Mirrors the STEP importer's planar
1016/// split predicate (`step_import::builder::planar_split`) on a finished
1017/// solid: a plane, a single loop of straight non-degenerate edges, and a
1018/// boundary vertex beyond the weld. `None` means no such face, so a failure
1019/// may still be a genuine degeneracy worth a perturbation rung. Only
1020/// consulted on the failure path.
1021fn planar_operand_off_its_plane(
1022    first: &BrepSolid,
1023    second: &BrepSolid,
1024    options: &BooleanOptions,
1025) -> Option<(u8, u64, f64, f64)> {
1026    let policy = options
1027        .tolerances
1028        .clone()
1029        .unwrap_or_else(|| KernelTolerances::for_pair(first, second, options.tolerance));
1030    let weld = crate::tolerance::assembler_weld(policy.model);
1031    let mut worst: Option<(u8, u64, f64)> = None;
1032    for (operand, solid) in [(0u8, first), (1u8, second)] {
1033        let vertex_point = |id: u64| solid.vertices.iter().find(|v| v.id == id).map(|v| v.point);
1034        for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
1035            let Some(crate::AnalyticSurface::Plane {
1036                origin,
1037                u_dir,
1038                v_dir,
1039                ..
1040            }) = face.surface.analytic()
1041            else {
1042                continue;
1043            };
1044            let [loop_record] = face.loops.as_slice() else {
1045                continue;
1046            };
1047            let Ok(normal) = u_dir.cross(*v_dir).normalized() else {
1048                continue;
1049            };
1050            let mut off_plane = 0.0f64;
1051            let mut straight = true;
1052            for coedge in &loop_record.coedges {
1053                let Some(edge) = solid.edges.iter().find(|edge| edge.id == coedge.edge_id) else {
1054                    straight = false;
1055                    break;
1056                };
1057                let (Some(start), Some(end)) =
1058                    (vertex_point(edge.start_vertex_id), vertex_point(edge.end_vertex_id))
1059                else {
1060                    straight = false;
1061                    break;
1062                };
1063                let chord = end.sub(start);
1064                let length = chord.length();
1065                if edge.degenerate || length <= weld {
1066                    straight = false;
1067                    break;
1068                }
1069                let direction = chord.scale(1.0 / length);
1070                for fraction in [0.25, 0.5, 0.75] {
1071                    let Ok(point) = edge.curve.evaluate(edge.t0 + (edge.t1 - edge.t0) * fraction) else {
1072                        straight = false;
1073                        break;
1074                    };
1075                    let delta = point.sub(start);
1076                    let along = delta.dot(direction);
1077                    if delta.sub(direction.scale(along)).length() > weld
1078                        || along < -weld
1079                        || along > length + weld
1080                    {
1081                        straight = false;
1082                        break;
1083                    }
1084                }
1085                if !straight {
1086                    break;
1087                }
1088                off_plane = off_plane.max(start.sub(*origin).dot(normal).abs());
1089            }
1090            if straight && off_plane > weld && worst.is_none_or(|(_, _, gap)| off_plane > gap) {
1091                worst = Some((operand, face.id, off_plane));
1092            }
1093        }
1094    }
1095    worst.map(|(operand, face_id, gap)| (operand, face_id, gap, weld))
1096}
1097
1098/// CSG volume inequality bounds (no extra boolean needed) with 1% relative slack.
1099/// `va`, `vb`, `vr` are absolute volumes of operand A, operand B, and the result.
1100fn volume_within_csg_bounds(operation: BooleanOperation, va: f64, vb: f64, vr: f64) -> bool {
1101    let slack = 1e-2 * (va + vb).max(1.0);
1102    match operation {
1103        // max(A,B) ≤ A∪B ≤ A+B
1104        BooleanOperation::Union => vr >= va.max(vb) - slack && vr <= va + vb + slack,
1105        // A−B ≤ A (and ≥ 0)
1106        BooleanOperation::Subtract => vr <= va + slack && vr >= -slack,
1107        // A∩B ≤ min(A,B)
1108        BooleanOperation::Intersect => vr <= va.min(vb) + slack && vr >= -slack,
1109    }
1110}
1111
1112// ---------------------------------------------------------------------------
1113// N-ary booleans (Golovanov T4.4)
1114//
1115// Union / intersect / subtract of N solids in ONE imprint+fragment pass.  This
1116// is a NEW ADDITIVE entry point: the binary `boolean_operation` above is left
1117// byte-identical.  The pipeline mirrors the binary one but generalizes the two
1118// operand-specific stages to N operands:
1119//
1120//   1. Imprint EVERY unordered pair (i, j) so every mutual intersection edge is
1121//      present on both operands' faces, and accumulate all pairs into ONE global
1122//      imprint (operand indices remapped to 0..N, piece/vertex ids made globally
1123//      unique, per-face piece lists and per-edge split parameters merged).
1124//   2. Split + fragment each operand by that single global imprint, so each
1125//      operand's faces are cut by ALL of its intersections at once.
1126//   3. Select fragments in one pass: the per-fragment keep decision generalizes
1127//      from "vs one other solid" to "vs the SET of other solids" (see
1128//      `nary_keep`).  Because every pair is imprinted, a fragment's interior lies
1129//      wholly inside or wholly outside each other operand, so a single interior
1130//      test point per (fragment, other-solid) is an exact membership verdict.
1131//   4. Assemble with the same machinery (`assemble_fragments`), then merge and
1132//      validate exactly as the binary path does.
1133// ---------------------------------------------------------------------------
1134
1135/// Reverse a fragment's orientation (surface sense + every coedge), the same
1136/// transform the binary Subtract path applies to the second operand's kept
1137/// fragments.  Extracted so the n-ary selector can reuse it verbatim.
1138fn reverse_fragment(fragment: &mut FaceFragmentRecord) -> Result<(), KernelRefusal> {
1139    fragment.same_sense = !fragment.same_sense;
1140    for loop_record in &mut fragment.loops {
1141        loop_record.coedges.reverse();
1142        for coedge in &mut loop_record.coedges {
1143            coedge.forward = !coedge.forward;
1144            coedge.pcurve = coedge.pcurve.reversed().or_refuse(KernelStage::Validate, "reversed")?;
1145        }
1146    }
1147    Ok(())
1148}
1149
1150/// Imprint every unordered pair of operands and fold the pairwise
1151/// `ImprintResultRecord`s into ONE global imprint whose operand indices are the
1152/// operands' global positions (0..N).  Piece and vertex ids are re-based to be
1153/// globally unique; `by_face` piece lists and `edge_splits` parameter lists are
1154/// MERGED per key so `fragment_face` (which takes the first `by_face` match) and
1155/// `apply_edge_splits` (which collects one entry per edge) see every cut.
1156fn build_nary_imprint(
1157    operands: &[BrepSolid],
1158    options: &ImprintOptions,
1159) -> Result<ImprintResultRecord, KernelRefusal> {
1160    let mut section_evidence = false;
1161    let mut vertices: Vec<ImprintVertex> = Vec::new();
1162    let mut pieces: Vec<ImprintPieceRecord> = Vec::new();
1163    let mut by_face: HashMap<(u8, u64), Vec<u64>> = HashMap::default();
1164    let mut edge_splits: HashMap<(u8, u64), Vec<f64>> = HashMap::default();
1165    let mut next_vertex_id: u64 = 1;
1166    let mut next_piece_id: u64 = 1;
1167
1168    for i in 0..operands.len() {
1169        for j in (i + 1)..operands.len() {
1170            let pair = build_imprints(&operands[i], &operands[j], options)?;
1171            let remap = |operand: u8| -> u8 {
1172                if operand == 0 {
1173                    i as u8
1174                } else {
1175                    j as u8
1176                }
1177            };
1178
1179            section_evidence = section_evidence || pair.section_evidence;
1180            let mut vertex_map: HashMap<u64, u64> = HashMap::default();
1181            for vertex in &pair.vertices {
1182                let global = next_vertex_id;
1183                next_vertex_id += 1;
1184                vertex_map.insert(vertex.id, global);
1185                vertices.push(ImprintVertex {
1186                    id: global,
1187                    point: vertex.point,
1188                });
1189            }
1190
1191            let mut piece_map: HashMap<u64, u64> = HashMap::default();
1192            for piece in &pair.pieces {
1193                let global = next_piece_id;
1194                next_piece_id += 1;
1195                piece_map.insert(piece.id, global);
1196                let mut mapped = piece.clone();
1197                mapped.id = global;
1198                mapped.start_vertex_id = *vertex_map
1199                    .get(&piece.start_vertex_id)
1200                    .ok_or_else(|| KernelRefusal::internal(KernelStage::Intersect, "nary.imprint", "n-ary imprint: piece references unknown vertex"))?;
1201                mapped.end_vertex_id = *vertex_map
1202                    .get(&piece.end_vertex_id)
1203                    .ok_or_else(|| KernelRefusal::internal(KernelStage::Intersect, "nary.imprint", "n-ary imprint: piece references unknown vertex"))?;
1204                for pcurve in &mut mapped.pcurves {
1205                    pcurve.operand = remap(pcurve.operand);
1206                }
1207                mapped.support_faces = [
1208                    FaceKey {
1209                        operand: remap(piece.support_faces[0].operand),
1210                        face_id: piece.support_faces[0].face_id,
1211                    },
1212                    FaceKey {
1213                        operand: remap(piece.support_faces[1].operand),
1214                        face_id: piece.support_faces[1].face_id,
1215                    },
1216                ];
1217                pieces.push(mapped);
1218            }
1219
1220            for entry in &pair.by_face {
1221                let list = by_face
1222                    .entry((remap(entry.operand), entry.face_id))
1223                    .or_default();
1224                for piece_id in &entry.piece_ids {
1225                    let global = *piece_map.get(piece_id).ok_or_else(|| {
1226                        KernelRefusal::internal(KernelStage::Intersect, "nary.imprint", "n-ary imprint: by_face references unknown piece")
1227                    })?;
1228                    list.push(global);
1229                }
1230            }
1231
1232            for split in &pair.edge_splits {
1233                edge_splits
1234                    .entry((remap(split.operand), split.edge_id))
1235                    .or_default()
1236                    .extend(split.parameters.iter().copied());
1237            }
1238        }
1239    }
1240
1241    Ok(ImprintResultRecord {
1242        vertices,
1243        pieces,
1244        tangent_nodes: Vec::new(),
1245        barrier_edges: Vec::new(),
1246        section_evidence,
1247        cosurface_pairs: Vec::new(),
1248        by_face: by_face
1249            .into_iter()
1250            .map(|((operand, face_id), piece_ids)| FaceImprints {
1251                operand,
1252                face_id,
1253                piece_ids,
1254            })
1255            .collect(),
1256        edge_splits: edge_splits
1257            .into_iter()
1258            .map(|((operand, edge_id), parameters)| EdgeSplitRecord {
1259                operand,
1260                edge_id,
1261                parameters,
1262            })
1263            .collect(),
1264    })
1265}
1266
1267/// N-ary union / intersect / subtract of `operands` in ONE imprint+fragment
1268/// pass (Golovanov T4.4).  Additive: does not touch the binary path.
1269///
1270/// - `Union`   : the boundary of `∪ operands`.
1271/// - `Intersect`: the boundary of `∩ operands`.
1272/// - `Subtract`: `operands[0]` minus the union of `operands[1..]`.
1273///
1274/// `N = 1` returns the sole operand; `N = 2` runs the same general pipeline and
1275/// reproduces the binary result (same volume, valid).  Returns a clear `Err`
1276/// when the operands cannot assemble into a closed, valid solid.
1277pub fn boolean_operation_nary(
1278    operands: &[BrepSolid],
1279    operation: BooleanOperation,
1280) -> Result<BrepSolid, KernelRefusal> {
1281    if operands.is_empty() {
1282        return Err(KernelRefusal::input(KernelStage::Collect, "operands", "boolean_operation_nary: no operands provided"));
1283    }
1284    if operands.len() == 1 {
1285        return Ok(operands[0].clone());
1286    }
1287    if operands.len() > 255 {
1288        return Err(KernelRefusal::input(KernelStage::Collect, "operands", "boolean_operation_nary: at most 255 operands supported"));
1289    }
1290    let debug = std::env::var("BREP_DEBUG_BOOL").is_ok();
1291    let options = BooleanOptions::default();
1292
1293    // One tolerance policy for the whole set: the largest-scale operand's policy
1294    // dominates (every derived band is monotonic in scale), matching how the
1295    // binary path takes `for_pair` = max of the two scales.
1296    let mut policy = KernelTolerances::for_solid(&operands[0], options.tolerance);
1297    for operand in &operands[1..] {
1298        let candidate = KernelTolerances::for_solid(operand, options.tolerance);
1299        if candidate.sew_search > policy.sew_search {
1300            policy = candidate;
1301        }
1302    }
1303    policy.check().or_input(KernelStage::Collect, "tolerances")?;
1304    let tolerance = policy.model;
1305
1306    // Per-operand fuse-first healing, exactly as the binary path (a no-op on
1307    // clean input, so clean operands are untouched).
1308    let mut healed = operands.to_vec();
1309    for operand in &mut healed {
1310        crate::heal::heal_operands(operand, &policy)?;
1311        // Then refit any inherited trim off the pcurve construction contract
1312        // whose edge lies on every carrier (a no-op without one).
1313        crate::heal::refit_inherited_trims(operand, &policy)?;
1314        // Same operand normalization as the binary path: seam edges for
1315        // seamless full-period band faces.
1316        normalize_operand_band_seams(operand)?;
1317    }
1318
1319    // 1. Imprint every pair into one global imprint.
1320    let mut imprint_options = options.imprint.clone();
1321    imprint_options.tolerance = tolerance;
1322    let imprint = build_nary_imprint(&healed, &imprint_options)?;
1323
1324    // 2. Split every operand's edges, then fragment its faces, by that imprint.
1325    let mut split = Vec::with_capacity(healed.len());
1326    for (index, operand) in healed.iter().enumerate() {
1327        split.push(apply_edge_splits(operand, index as u8, &imprint)?);
1328    }
1329    let mut fragments = Vec::with_capacity(split.len());
1330    for (index, solid) in split.iter().enumerate() {
1331        fragments.push(fragment_solid(solid, index as u8, &imprint)?);
1332    }
1333
1334    // 3. Select fragments in one pass against the set of other operands.
1335    let selected = select_fragments_nary(&fragments, &healed, operation, tolerance, debug)?;
1336
1337    // 4. Assemble + normalize + validate, mirroring the binary tail.
1338    let solids: HashMap<u8, &BrepSolid> = split
1339        .iter()
1340        .enumerate()
1341        .map(|(index, solid)| (index as u8, solid))
1342        .collect();
1343    let solid = assemble_fragments(selected, &solids, &imprint, tolerance)?;
1344    let solid = if options.merge_coplanar_faces
1345        && std::env::var("BREP_COPLANAR_MERGE").as_deref() != Ok("0")
1346    {
1347        let merged = merge_same_surface_faces_excluding(
1348            &solid,
1349            tolerance,
1350            &options.keep_unmerged_name_substrs,
1351        )?;
1352        merge_curve_continuation_edges(&merged, tolerance).map_err(|refusal| KernelRefusal { stage: KernelStage::Validate, ..refusal })?
1353    } else {
1354        solid
1355    };
1356    let validation = solid.validate_detailed(&policy);
1357    if !validation.issues.is_empty() {
1358        return Err(KernelRefusal::new(
1359            RefusalClass::InvalidResultTopology { issues: validation.issues.len() as u32 },
1360            KernelStage::Validate,
1361            format!(
1362            "boolean_operation_nary: invalid result: {:?}",
1363            validation.issues
1364        )));
1365    }
1366    if debug {
1367        if let Ok(report) =
1368            crate::oracle::boolean_semantic_disagreement_nary(&healed, operation, &solid, 300)
1369        {
1370            if report.is_flagged() {
1371                eprintln!(
1372                    "[oracle] n-ary semantic disagreement rate {:.4} ({} of {} decidable points); sample {:?}",
1373                    report.disagreement_rate,
1374                    report.disagreements.len(),
1375                    report.considered,
1376                    report.sample_disagreement()
1377                );
1378            }
1379        }
1380    }
1381    Ok(solid)
1382}
1383
1384/// TANGENT-NODE ATTRIBUTION.
1385///
1386/// The imprint's second-order filter decides which tangential contacts are
1387/// isolated NODES the arrangement can carve, and marches those instead of
1388/// refusing. It cannot decide a THIRD-order singularity — where the transverse
1389/// branch is tangent to a contact curve, every second-order quantity vanishes
1390/// together and the contact reads as a clean saddle at every stopping rule
1391/// (`imprint/tangent_contact.rs`). The assembly is the authority on whether an
1392/// admitted node was really imprintable, so a tear downstream of one is
1393/// reported AGAINST THE NODE rather than as an anonymous degeneracy: same
1394/// refusal class the whole tangent-node family carries, naming the point.
1395///
1396/// Only classes that are ALREADY perturbation-eligible are re-attributed, so a
1397/// refusal that could never take the ladder still cannot.
1398///
1399/// It does decide WHICH refusals are retried, though, and since 2026-09-13 that
1400/// matters: `boolean_operation`'s failure arm gates `TangentNodeSingularity` out
1401/// of the perturbation ladder (the TANGENT-NODE GATE there) and reads the class
1402/// AFTER this rewrite. So a tear downstream of an ADMITTED node — whatever class
1403/// the pipeline actually raised — declines the ladder along with the tangent-node
1404/// family proper. That is the conservative direction and the same claim the class
1405/// makes (a tear after a node is the node proving unimprintable). Anything added
1406/// to this rewrite widens that gate: read the gate when you touch the
1407/// attribution.
1408fn attribute_to_tangent_node(error: KernelRefusal, nodes: &[Vec3]) -> KernelRefusal {
1409    let Some(node) = nodes.first() else {
1410        return error;
1411    };
1412    if !error.class.perturbation_eligible()
1413        || matches!(error.class, RefusalClass::TangentNodeSingularity)
1414    {
1415        return error;
1416    }
1417    KernelRefusal::new(
1418        RefusalClass::TangentNodeSingularity,
1419        error.stage,
1420        format!(
1421            "boolean: unsupported singular/tangent-node surface intersection \
1422             at the tangent node at ({:.6},{:.6},{:.6}): the section through it did not \
1423             assemble into a closed shell",
1424            node.x, node.y, node.z
1425        ),
1426    )
1427}
1428
1429pub fn boolean_operation_with_diagnostics(
1430    first: &BrepSolid,
1431    second: &BrepSolid,
1432    operation: BooleanOperation,
1433    options: &BooleanOptions,
1434) -> Result<KernelOutcome<BrepSolid>, KernelRefusal> {
1435    let mut tangent_nodes = Vec::new();
1436    let mut outcome = boolean_pipeline(first, second, operation, options, &mut tangent_nodes)
1437        .map_err(|error| attribute_to_tangent_node(error, &tangent_nodes))?;
1438    // The assembly's single exit. A boolean can hand back a body whose walls
1439    // pass through each other while `validate()` is silent and the volume
1440    // measures right, which is exactly the failure a regularized boolean must
1441    // not report as success. A crossing the OPERANDS already had is theirs and
1442    // passes through — the defect came in with the input and refusing it here
1443    // would blame the wrong operation — so the operands are only scanned when
1444    // the result is flagged, and the common case pays for one scan.
1445    // The acceptance's refusal is typed at its origin (an `UnsoundResult`
1446    // naming the defect and the faces); it was classed `Internal` here while
1447    // `accept_sound_against` still returned text.
1448    outcome.value = crate::accept_sound_against(&[first, second], outcome.value, "boolean")?;
1449    Ok(outcome)
1450}
1451
1452/// Stage wall-clock line on stderr when `BREP_PROFILE` is set — the same switch
1453/// `imprint.profile` and `assemble.*_ms` use — so a REFUSED boolean, whose
1454/// `KernelDiagnostics` never reach a caller, still shows where its time went.
1455fn profile_stage(code: &str, ms: f64) {
1456    if std::env::var("BREP_PROFILE").is_ok() {
1457        eprintln!("boolean.{code}={ms:.2}");
1458    }
1459}
1460
1461/// `BREP_BOOLEAN_DUMP` names a directory. Every binary boolean then writes its
1462/// stages there as JSON — the two split operands, the selected fragments, the
1463/// assembled solid and the merged one — numbered per process, so a probe can
1464/// read a face's loops at each stage without replaying the document.
1465struct BooleanStageDump {
1466    directory: std::path::PathBuf,
1467    serial: usize,
1468}
1469
1470impl BooleanStageDump {
1471    fn open() -> Option<Self> {
1472        static SERIAL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
1473        let directory = std::env::var("BREP_BOOLEAN_DUMP").ok().filter(|value| !value.is_empty())?;
1474        let serial = SERIAL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1475        Some(Self { directory: directory.into(), serial })
1476    }
1477
1478    fn write<T: Serialize + ?Sized>(&self, stage: &str, value: &T) {
1479        let path = self.directory.join(format!("{:03}-{stage}.json", self.serial));
1480        match serde_json::to_string(value) {
1481            Ok(text) => {
1482                if let Err(error) = std::fs::write(&path, text) {
1483                    eprintln!("BREP_BOOLEAN_DUMP {}: {error}", path.display());
1484                }
1485            }
1486            Err(error) => eprintln!("BREP_BOOLEAN_DUMP {stage}: {error}"),
1487        }
1488    }
1489}
1490
1491/// The tolerance policy a binary boolean of `first` and `second` runs under.
1492fn boolean_policy(
1493    first: &BrepSolid,
1494    second: &BrepSolid,
1495    options: &BooleanOptions,
1496) -> Result<KernelTolerances, KernelRefusal> {
1497    let policy = options
1498        .tolerances
1499        .unwrap_or_else(|| KernelTolerances::for_pair(first, second, options.tolerance));
1500    policy.check().or_input(KernelStage::Collect, "tolerances")?;
1501    Ok(policy)
1502}
1503
1504/// The operands as the imprint reads them: healed, with their band seams
1505/// normalized.
1506fn prepared_operands(
1507    first: &BrepSolid,
1508    second: &BrepSolid,
1509    policy: &KernelTolerances,
1510) -> Result<(BrepSolid, BrepSolid), KernelRefusal> {
1511    // Fuse-first operand healing (Lever A): before any intersection touches the
1512    // operands, snap each one's near-coincident / off-plane vertices to exact
1513    // and re-anchor the incident edge curves onto them, so noisy
1514    // near-degenerate input (points that should coincide but drifted, vertices
1515    // a few microns off a planar face) cannot tip a fixed downstream band over
1516    // the edge.  Healing is per-operand, validate-gated, and — because a clean
1517    // operand has no vertices within `heal_tol` and none off its planes — a
1518    // no-op that leaves the boolean output byte-identical on clean inputs.
1519    let stage_started = Instant::now();
1520    let mut first_owned = first.clone();
1521    let mut second_owned = second.clone();
1522    crate::heal::heal_operands(&mut first_owned, policy)?;
1523    crate::heal::heal_operands(&mut second_owned, policy)?;
1524    // Then refit, on each operand's own carriers, any inherited trim off the
1525    // pcurve construction contract whose edge lies on every incident carrier
1526    // (`heal::refit_inherited_trims`; a byte-identical no-op without one).
1527    crate::heal::refit_inherited_trims(&mut first_owned, policy)?;
1528    crate::heal::refit_inherited_trims(&mut second_owned, policy)?;
1529    profile_stage("heal_ms", stage_started.elapsed().as_secs_f64() * 1_000.0);
1530    // Seamless full-period band faces (STEP import) break the seam-aware
1531    // imprint/arrangement machinery; normalize them to the seam-carrying
1532    // topology native periodic faces use. No-op on operands without such
1533    // faces. See `insert_periodic_band_seam_edges`.
1534    normalize_operand_band_seams(&mut first_owned)?;
1535    normalize_operand_band_seams(&mut second_owned)?;
1536    Ok((first_owned, second_owned))
1537}
1538
1539fn boolean_imprint_options(options: &BooleanOptions, tolerance: f64) -> ImprintOptions {
1540    let mut imprint_options = options.imprint.clone();
1541    imprint_options.tolerance = tolerance;
1542    imprint_options
1543}
1544
1545/// The two operands a binary boolean's arrangement reads: prepared as the
1546/// boolean prepares them, with every boundary edge split where the imprint's
1547/// sections meet it. These are the `NNN-first` and `NNN-second` stages
1548/// `BREP_BOOLEAN_DUMP` writes, not the operands at the boolean's entry.
1549///
1550/// A boolean must read the same on these as on the operands it split them
1551/// from: the splits are vertices its own imprint puts on those edges anyway.
1552/// Where it does not, an imprint lane is deciding on what the input happens to
1553/// have split, and that is a defect.
1554pub fn boolean_split_operands(
1555    first: &BrepSolid,
1556    second: &BrepSolid,
1557    options: &BooleanOptions,
1558) -> Result<(BrepSolid, BrepSolid), KernelRefusal> {
1559    let policy = boolean_policy(first, second, options)?;
1560    let (first, second) = prepared_operands(first, second, &policy)?;
1561    let imprint = build_imprints(&first, &second, &boolean_imprint_options(options, policy.model))?;
1562    let (split_first, _) = apply_edge_splits_with_map(&first, 0, &imprint)?;
1563    let (split_second, _) = apply_edge_splits_with_map(&second, 1, &imprint)?;
1564    Ok((split_first, split_second))
1565}
1566
1567fn boolean_pipeline(
1568    first: &BrepSolid,
1569    second: &BrepSolid,
1570    operation: BooleanOperation,
1571    options: &BooleanOptions,
1572    tangent_nodes: &mut Vec<Vec3>,
1573) -> Result<KernelOutcome<BrepSolid>, KernelRefusal> {
1574    let replaced_operands = operand_state::replace_operands(first, second, operation, options);
1575    let (first, second) = match &replaced_operands {
1576        Some((first, second)) => (first, second),
1577        None => (first, second),
1578    };
1579    let policy = boolean_policy(first, second, options)?;
1580    let tolerance = policy.model;
1581    let (first_owned, second_owned) = prepared_operands(first, second, &policy)?;
1582    let first = &first_owned;
1583    let second = &second_owned;
1584
1585    let mut diagnostics = KernelDiagnostics::default();
1586    // Every pcurve fit made while this scope is open — the imprint's sections,
1587    // the assembler's rebuilt trims, the merge's re-fits — is tallied, so the
1588    // outcome can say whether every curved trim reached its floor instead of
1589    // letting a sample-ceiling exit pass for a met one (`pcurve.unmet_floor`).
1590    let fit_scope = crate::PcurveFitScope::open();
1591    // And every POLYLINE fit: a marched section's interpolant is measured
1592    // against the tolerance it was given, so `fit.polyline.unmet_tolerance`
1593    // reports a section that could not reach it (`geometry/fit.rs`).
1594    let polyline_scope = crate::PolylineFitScope::open();
1595    let operation_started = Instant::now();
1596    diagnostics.count_n(
1597        "collect.faces",
1598        first
1599            .shells
1600            .iter()
1601            .chain(&second.shells)
1602            .map(|shell| shell.faces.len() as u64)
1603            .sum(),
1604    );
1605    diagnostics.measure_max("tolerance.model", policy.model);
1606    diagnostics.measure_max("tolerance.sew_search", policy.sew_search);
1607
1608    let imprint_options = boolean_imprint_options(options, tolerance);
1609    let stage_started = Instant::now();
1610    let imprint = build_imprints(first, second, &imprint_options)?;
1611    tangent_nodes.extend_from_slice(&imprint.tangent_nodes);
1612    if std::env::var("BREP_DEBUG_BOOL").is_ok() {
1613        for piece in &imprint.pieces {
1614            let start = piece.curve.evaluate(piece.t0);
1615            let end = piece.curve.evaluate(piece.t1);
1616            eprintln!(
1617                "piece {} supports={:?} t=[{:.6},{:.6}] start={:?} end={:?}",
1618                piece.id, piece.support_faces, piece.t0, piece.t1, start, end
1619            );
1620        }
1621        for entry in &imprint.by_face {
1622            eprintln!(
1623                "by_face operand={} face={} pieces={:?}",
1624                entry.operand, entry.face_id, entry.piece_ids
1625            );
1626        }
1627        for split in &imprint.edge_splits {
1628            eprintln!(
1629                "edge_split operand={} edge={} params={:?}",
1630                split.operand, split.edge_id, split.parameters
1631            );
1632        }
1633    }
1634    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
1635    profile_stage("imprint_ms", stage_ms);
1636    diagnostics.measure_max("timing.imprint_ms", stage_ms);
1637    diagnostics.count_n("intersect.pieces", imprint.pieces.len() as u64);
1638    diagnostics.count_n("intersect.vertices", imprint.vertices.len() as u64);
1639    diagnostics.count_n("intersect.edge_splits", imprint.edge_splits.len() as u64);
1640    let stage_started = Instant::now();
1641    let (split_first, split_map_first) = apply_edge_splits_with_map(first, 0, &imprint)?;
1642    let (split_second, split_map_second) = apply_edge_splits_with_map(second, 1, &imprint)?;
1643    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
1644    profile_stage("edge_split_ms", stage_ms);
1645    diagnostics.measure_max("timing.edge_split_ms", stage_ms);
1646    let stage_started = Instant::now();
1647    crate::classification::classify_profile_begin();
1648    let fragments_a = fragment_solid(&split_first, 0, &imprint)?;
1649    let fragments_b = fragment_solid(&split_second, 1, &imprint)?;
1650    crate::classification::classify_profile_report("fragment");
1651    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
1652    profile_stage("fragment_ms", stage_ms);
1653    diagnostics.measure_max("timing.fragment_ms", stage_ms);
1654    diagnostics.count_n(
1655        "fragment.candidates",
1656        (fragments_a.len() + fragments_b.len()) as u64,
1657    );
1658    // Operand surface samples for the LEGITIMATE-EMPTY adjudication below:
1659    // every fragment test point is a 3D point ON its operand's boundary and
1660    // inside its face trim, so they witness where the operands' materials sit
1661    // relative to each other without extra geometry work.
1662    let a_surface_samples: Vec<Vec3> = fragments_a
1663        .iter()
1664        .flat_map(|fragment| {
1665            std::iter::once(fragment.test_point).chain(fragment.extra_test_points.iter().copied())
1666        })
1667        .collect();
1668    let b_surface_samples: Vec<Vec3> = fragments_b
1669        .iter()
1670        .flat_map(|fragment| {
1671            std::iter::once(fragment.test_point).chain(fragment.extra_test_points.iter().copied())
1672        })
1673        .collect();
1674    let stage_started = Instant::now();
1675    let mut barrier_edges: HashSet<(u8, u64)> = imprint
1676        .pieces
1677        .iter()
1678        .filter_map(|piece| piece.shared_edge.map(|(operand, edge_id, _)| (operand, edge_id)))
1679        .collect();
1680    barrier_edges.extend(imprint.barrier_edges.iter().copied());
1681    // The barrier set is keyed on ORIGINAL edge ids, but a barrier edge the
1682    // imprint also SPLIT (rotated equator-tangency: the overlapped cap ring
1683    // gains a junction at the sphere-seam crossing) reaches fragment
1684    // selection as its minted sub-edge ids. Remap the barrier through the
1685    // split ledger — keeping the originals too for unsplit references.
1686    for (operand, split_map) in [(0u8, &split_map_first), (1u8, &split_map_second)] {
1687        let minted: Vec<(u8, u64)> = barrier_edges
1688            .iter()
1689            .filter(|(barrier_operand, _)| *barrier_operand == operand)
1690            .filter_map(|(_, edge_id)| split_map.get(edge_id))
1691            .flat_map(|sub_ids| sub_ids.iter().map(|sub_id| (operand, *sub_id)))
1692            .collect();
1693        barrier_edges.extend(minted);
1694    }
1695    crate::classification::classify_profile_begin();
1696    let coincident = coincident_pairs(
1697        &fragments_a,
1698        &fragments_b,
1699        first,
1700        second,
1701        &imprint.cosurface_pairs,
1702        tolerance,
1703    )?;
1704    let mut selected = select_fragments(
1705        fragments_a,
1706        fragments_b,
1707        first,
1708        second,
1709        operation,
1710        tolerance,
1711        &barrier_edges,
1712        &imprint.cosurface_pairs,
1713        &coincident,
1714    )?;
1715    let resewn = resew_coincident_rims(
1716        &mut selected,
1717        [&split_first, &split_second],
1718        [first, second],
1719        &imprint.cosurface_pairs,
1720        operation,
1721        tolerance,
1722    )?;
1723    diagnostics.count_n("select.coincident_pairs", coincident.len() as u64);
1724    diagnostics.count_n("select.rims_resewn", resewn as u64);
1725    crate::classification::classify_profile_report("select");
1726    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
1727    profile_stage("select_ms", stage_ms);
1728    diagnostics.measure_max("timing.select_ms", stage_ms);
1729    diagnostics.count_n("select.fragments", selected.len() as u64);
1730    // LEGITIMATE-EMPTY RESULTS: an intersect of DISJOINT operands (or a
1731    // subtract whose left operand is entirely CONSUMED by the right) selects
1732    // zero fragments, and assembly used to refuse with "operation produced no
1733    // boundary faces" — 384 of the 474 post-wrapped-band pool errors were
1734    // this, not bugs. Empty is only blessed on TWO independent proofs:
1735    //   (a) the imprint recorded NO section evidence (no accepted pierce
1736    //       seed, no traced SSI branch, no minted piece) — a silently-lost
1737    //       section (the trial-489 class) always leaves upstream evidence
1738    //       even when every downstream sub-segment is clipped away, while
1739    //       pure material disjointness leaves none (verified: 489's
1740    //       recreated silent-miss state keeps erroring; per-face surface
1741    //       samples ALONE missed its 235 mm³ pocket, which is why (a) is
1742    //       required and sampling alone was rejected);
1743    //   (b) the operands' surface samples agree (every fragment test point
1744    //       classified against the other solid):
1745    //       intersect — no sample of either operand strictly inside the
1746    //       other; subtract — no left-operand sample strictly outside the
1747    //       right.
1748    // Contact/graze pairs (evidence exists, material still disjoint) stay
1749    // errors — conservative by design. Escape hatch: BREP_EMPTY_BOOLEAN=0
1750    // restores the unconditional error.
1751    if selected.is_empty()
1752        && !imprint.section_evidence
1753        && std::env::var("BREP_EMPTY_BOOLEAN").as_deref() != Ok("0")
1754    {
1755        let strictly_inside = |samples: &[Vec3], other: &BrepSolid| -> Result<bool, KernelRefusal> {
1756            for &sample in samples {
1757                if classify_point(sample, other, tolerance).or_refuse(KernelStage::Validate, "classify_point")?.class == PointClass::In {
1758                    return Ok(true);
1759                }
1760            }
1761            Ok(false)
1762        };
1763        let outside_witness = |samples: &[Vec3], other: &BrepSolid| -> Result<bool, KernelRefusal> {
1764            for &sample in samples {
1765                if classify_point(sample, other, tolerance).or_refuse(KernelStage::Validate, "classify_point")?.class == PointClass::Out {
1766                    return Ok(true);
1767                }
1768            }
1769            Ok(false)
1770        };
1771        let legitimate = match operation {
1772            BooleanOperation::Intersect => {
1773                !strictly_inside(&a_surface_samples, second)?
1774                    && !strictly_inside(&b_surface_samples, first)?
1775            }
1776            BooleanOperation::Subtract => !outside_witness(&a_surface_samples, second)?,
1777            BooleanOperation::Union => false,
1778        };
1779        if legitimate {
1780            diagnostics.event(
1781                DiagnosticSeverity::Info,
1782                KernelStage::Select,
1783                "boolean.empty_result",
1784                format!("{operation:?} of witnessed-non-overlapping operands is empty"),
1785            );
1786            fit_scope.close().report_into(&mut diagnostics);
1787            polyline_scope.close().report_into(&mut diagnostics);
1788            diagnostics.measure_max(
1789                "timing.total_ms",
1790                operation_started.elapsed().as_secs_f64() * 1_000.0,
1791            );
1792            return Ok(KernelOutcome {
1793                value: BrepSolid {
1794                    mass_properties_cache: Default::default(),
1795                    id: 0,
1796                    vertices: Vec::new(),
1797                    edges: Vec::new(),
1798                    shells: Vec::new(),
1799                    genus: 0,
1800                },
1801                diagnostics,
1802            });
1803        }
1804    }
1805    let solids = [(0, &split_first), (1, &split_second)]
1806        .into_iter()
1807        .collect::<HashMap<_, _>>();
1808    let dump = BooleanStageDump::open();
1809    if let Some(dump) = &dump {
1810        dump.write("first", &split_first);
1811        dump.write("second", &split_second);
1812        dump.write("selected", &selected);
1813    }
1814    let stage_started = Instant::now();
1815    let solid = assemble_fragments(selected, &solids, &imprint, tolerance)?;
1816    if let Some(dump) = &dump {
1817        dump.write("assembled", &solid);
1818    }
1819    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
1820    profile_stage("assemble_ms", stage_ms);
1821    diagnostics.measure_max("timing.assemble_ms", stage_ms);
1822    let stage_started = Instant::now();
1823    if std::env::var("BREP_DEBUG_PREMERGE_VALIDATE").is_ok() {
1824        let issues = solid.validate();
1825        eprintln!(
1826            "pre-merge validation: {} issue(s){}",
1827            issues.len(),
1828            issues
1829                .first()
1830                .map(|issue| format!(" — first: {}", issue.message))
1831                .unwrap_or_default()
1832        );
1833    }
1834    let solid = if options.merge_coplanar_faces
1835        && std::env::var("BREP_COPLANAR_MERGE").as_deref() != Ok("0")
1836    {
1837        let merged = merge_same_surface_faces_excluding(
1838            &solid,
1839            tolerance,
1840            &options.keep_unmerged_name_substrs,
1841        )?;
1842        // Face merging can make previously separate collinear boundary
1843        // segments incident to the same pair of faces. Run continuation
1844        // cleanup again, matching the post-merge normalization performed by
1845        // the former assembly pipeline.
1846        merge_curve_continuation_edges(&merged, tolerance).map_err(|refusal| KernelRefusal { stage: KernelStage::Validate, ..refusal })?
1847    } else {
1848        solid
1849    };
1850    if let Some(dump) = &dump {
1851        dump.write("merged", &solid);
1852    }
1853    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
1854    profile_stage("face_merge_ms", stage_ms);
1855    diagnostics.measure_max("timing.face_merge_ms", stage_ms);
1856    let validation = solid.validate_detailed(&policy);
1857    diagnostics.count_n("validate.issues", validation.issues.len() as u64);
1858    diagnostics.count_n(
1859        "validate.wire_warnings",
1860        validation.wire_warnings.len() as u64,
1861    );
1862    diagnostics.measure_max("validate.max_pcurve_error", validation.max_pcurve_error);
1863    for warning in &validation.wire_warnings {
1864        diagnostics.event(
1865            DiagnosticSeverity::Warning,
1866            KernelStage::Validate,
1867            "validate.uv_wire",
1868            warning.message.clone(),
1869        );
1870    }
1871    for issue in &validation.issues {
1872        diagnostics.event(
1873            DiagnosticSeverity::Error,
1874            KernelStage::Validate,
1875            "validate.brep",
1876            issue.message.clone(),
1877        );
1878    }
1879    fit_scope.close().report_into(&mut diagnostics);
1880    polyline_scope.close().report_into(&mut diagnostics);
1881    diagnostics.measure_max(
1882        "timing.total_ms",
1883        operation_started.elapsed().as_secs_f64() * 1_000.0,
1884    );
1885    if !validation.issues.is_empty() {
1886        return Err(KernelRefusal::new(
1887            RefusalClass::InvalidResultTopology { issues: validation.issues.len() as u32 },
1888            KernelStage::Validate,
1889            format!(
1890            "boolean_operation: invalid result: {:?}",
1891            validation.issues
1892        )));
1893    }
1894    // Optional semantic oracle (off by default, no perf hit): under
1895    // BREP_DEBUG_BOOL, cross-check the well-formed result against the CSG
1896    // point-membership expectation and scan for residual coincident-but-unwelded
1897    // geometry. Purely diagnostic and NON-rejecting — a statistical check must
1898    // never fail a valid op (see oracle.rs) — so it only ever warns.
1899    if std::env::var("BREP_DEBUG_BOOL").is_ok() {
1900        if let Ok(report) =
1901            crate::oracle::boolean_semantic_disagreement(first, second, operation, &solid, 200)
1902        {
1903            if report.is_flagged() {
1904                eprintln!(
1905                    "[oracle] semantic disagreement rate {:.4} ({} of {} decidable points); sample {:?}",
1906                    report.disagreement_rate,
1907                    report.disagreements.len(),
1908                    report.considered,
1909                    report.sample_disagreement()
1910                );
1911            }
1912        }
1913        let fusable_band = crate::tolerance::assembler_weld(policy.model);
1914        let fusables = crate::oracle::boolean_residual_fusables(&solid, fusable_band);
1915        if !fusables.is_empty() {
1916            eprintln!(
1917                "[oracle] {} residual fusable(s) after weld; sample {:?}",
1918                fusables.len(),
1919                fusables.first()
1920            );
1921        }
1922    }
1923    Ok(KernelOutcome {
1924        value: solid,
1925        diagnostics,
1926    })
1927}
1928