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, merge_same_surface_faces_scoped,
19    project_point_to_curve, project_point_to_surface, solid_signed_volume, solid_signed_volume_for_sign, 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    /// On by default since the 2026-10-10 census read the 94-case manifest
71    /// unchanged with it; `BREP_UNTOUCHED_PASSTHROUGH` overrides either way
72    /// (`0` off, anything else on).
73    #[serde(default = "default_true")]
74    pub untouched_passthrough: bool,
75    /// On by default since the 2026-10-10 census;
76    /// `BREP_DISJOINT_FAST_PATH` overrides either way (`0` off, anything
77    /// else on).
78    #[serde(default = "default_true")]
79    pub disjoint_fast_path: bool,
80    /// OFF by default: the 94-case census read unchanged with it, but the
81    /// kernel battery's `offset_subtract_fixture` pins a result whose
82    /// untouched planar face carries an open trim joint of 8.7e-4 from its
83    /// coarse polyline operand, which the whole-solid joints pass closes and
84    /// the scoped pass leaves as the operand had it; and the row buys no
85    /// measurable time (the record of 2026-10-10). `BREP_TOUCHED_ASSEMBLY`
86    /// overrides either way (`0` off, anything else on).
87    #[serde(default)]
88    pub touched_assembly: bool,
89    /// On by default since the 2026-10-11 census (the whole kernel battery,
90    /// the 94-case manifest and `hlr_tests` green with it on, every manifest
91    /// fragment the sector decides agreeing with the ray cast);
92    /// `BREP_SECTOR_CLASS` overrides either way (`0`/`off` the escape hatch,
93    /// `check` for the differential run that logs disagreements to
94    /// `BREP_SECTOR_CENSUS`, anything else on). Appended at the end of the
95    /// struct (2026-10-11).
96    #[serde(default = "default_true")]
97    pub sector_classification: bool,
98}
99
100/// [`BooleanOptions::touched_assembly`] under its environment override.
101fn touched_assembly(options: &BooleanOptions) -> bool {
102    match std::env::var("BREP_TOUCHED_ASSEMBLY").as_deref() {
103        Ok("0") => false,
104        Ok(_) => true,
105        Err(_) => options.touched_assembly,
106    }
107}
108
109/// [`BooleanOptions::disjoint_fast_path`] under its environment override.
110fn disjoint_fast_path(options: &BooleanOptions) -> bool {
111    match std::env::var("BREP_DISJOINT_FAST_PATH").as_deref() {
112        Ok("0") => false,
113        Ok(_) => true,
114        Err(_) => options.disjoint_fast_path,
115    }
116}
117
118/// [`BooleanOptions::untouched_passthrough`] under its environment override.
119fn untouched_passthrough(options: &BooleanOptions) -> bool {
120    match std::env::var("BREP_UNTOUCHED_PASSTHROUGH").as_deref() {
121        Ok("0") => false,
122        Ok(_) => true,
123        Err(_) => options.untouched_passthrough,
124    }
125}
126
127fn default_tolerance() -> f64 {
128    1e-7
129}
130
131fn default_true() -> bool {
132    true
133}
134
135impl Default for BooleanOptions {
136    fn default() -> Self {
137        Self {
138            tolerance: default_tolerance(),
139            tolerances: None,
140            imprint: ImprintOptions::default(),
141            merge_coplanar_faces: true,
142            keep_unmerged_name_substrs: Vec::new(),
143            untouched_passthrough: true,
144            disjoint_fast_path: true,
145            touched_assembly: false,
146            sector_classification: true,
147        }
148    }
149}
150
151mod select;
152mod sector;
153mod assemble;
154mod operand_state;
155mod rim;
156mod coincident;
157mod touch;
158
159use rim::*;
160use select::*;
161use coincident::*;
162use touch::TouchCensus;
163pub(crate) use assemble::{
164    assemble_fragments, assemble_open_fragments, commit_nearby_edge_endpoints,
165    edge_interior_lies_on, finalize_assembled_solid,
166};
167use assemble::{assemble_fragments_scoped, AssemblyScope};
168// Currently referenced only inside `assemble`, so the re-export is unused.
169#[allow(unused_imports)]
170pub(crate) use assemble::apply_assembly_heal_chain;
171
172pub fn boolean_operation(
173    first: &BrepSolid,
174    second: &BrepSolid,
175    operation: BooleanOperation,
176    options: &BooleanOptions,
177) -> Result<BrepSolid, KernelRefusal> {
178    match boolean_operation_with_diagnostics(first, second, operation, options) {
179        Ok(outcome) => Ok(outcome.value),
180        Err(error) => {
181            // Perturbation-fallback lane (Simulation of Simplicity). The exact
182            // arrangement only errs on a DEGENERACY here (coincident /
183            // near-tangent carrier faces make it structurally inconsistent) —
184            // e.g. a partial torus whose equatorial circles lie exactly in a box
185            // face. Retry once on a rigidly perturbed second operand; accept only
186            // an oracle-clean, volume-consistent result. See
187            // `perturbation_retry`.
188            if std::env::var("BREP_NO_PERTURB").as_deref() == Ok("1")
189                || !error.class.perturbation_eligible()
190            {
191                return Err(error);
192            }
193            // OPERAND-CONSISTENCY GATE. A rigid nudge lifts a coincidence
194            // BETWEEN the operands; it cannot reconcile an operand with
195            // itself. A planar face whose straight boundary sits farther off
196            // its plane than the assembly weld — the shape the STEP importer's
197            // planar split repairs, so it reaches here only on a body built
198            // some other way — fails the exact arrangement for every rung
199            // alike: the 2026-09-12 mesh-import report paid twenty ~7 s rungs
200            // for one 3.4e-3 gap before the import split existed. Name the
201            // gap and stop. The predicate is deliberately the splitter's own,
202            // not a pcurve-error band: vendor bodies whose fitted edges stand
203            // a few 1e-4 off curved faces are legitimate input (per-entity
204            // tolerances) and keep every rung.
205            // OPERAND-CLOSURE GATE. A boolean of a body that is not a closed
206            // shell has no solid answer, exact or nudged: its inside is not
207            // defined, and its volume depends on the reference point. The
208            // slimmed fuzz fixtures (13, 14 and 15, operand A cut from a larger
209            // part by a box filter) carry 54, 97 and 66 edges that bound one
210            // face; every one of their 20 rungs refused on the exact lane, and
211            // 15's identity was read from an intersection with the open shell.
212            // Only consulted on the failure path, so nothing that succeeds
213            // today changes. Escape hatch: BREP_OPEN_OPERAND_GATE=0.
214            if std::env::var("BREP_OPEN_OPERAND_GATE").as_deref() != Ok("0") {
215                if let Some((operand, open)) = [first, second]
216                    .iter()
217                    .enumerate()
218                    .find_map(|(operand, solid)| {
219                        let open = one_use_edges(solid);
220                        (open > 0).then_some((operand, open))
221                    })
222                {
223                    return Err(error.with_message(|message| format!(
224                        "{message}; operand {operand} is not a closed shell: {open} of its edges bound \
225                         only one face, so no boolean of it encloses a solid and no perturbation \
226                         can close it"
227                    )));
228                }
229            }
230            if let Some((operand, face_id, deviation, weld)) =
231                planar_operand_off_its_plane(first, second, options)
232            {
233                return Err(error.with_message(|message| format!(
234                    "{message}; operand {operand} face {face_id} is a plane whose straight \
235                     boundary sits {deviation:.3e} off it (assembly weld {weld:.1e}), which \
236                     no perturbation can reconcile — the body's planes must contain their \
237                     boundary before it can take part in an exact boolean"
238                )));
239            }
240            // INTERNAL-TANGENCY PINCH GATE. Perturbation is only legitimate
241            // where the exact arrangement is UNSTABLE but its ANSWER is not:
242            // the nearby transversal case must carry the same topology the
243            // degenerate one does. An internal tangency in a DIFFERENCE breaks
244            // exactly that premise — the exact `A − B` pinches to zero
245            // thickness along the contact, so an epsilon either tears the wall
246            // open (a through-slot where the exact answer has intact material)
247            // or leaves a sub-micron web. Both are within epsilon in Hausdorff
248            // distance and in volume, so neither the oracle (boundary skip
249            // band) nor the 1% CSG volume bound can see them, and the lane
250            // would ship the tolerant-kernel sliver this kernel refuses. Only
251            // consulted on the failure path, so nothing that succeeds today can
252            // change; the honest exact-path error is returned instead.
253            // Escape hatch for tamper-verification: BREP_PINCH_GATE=0.
254            if matches!(operation, BooleanOperation::Subtract)
255                && std::env::var("BREP_PINCH_GATE").as_deref() != Ok("0")
256                && subtract_pinches_at_internal_tangency(first, second, options.tolerance)
257                    .unwrap_or(false)
258            {
259                return Err(error.with_message(|message| format!(
260                    "{message}; internal tangency: the operands touch tangentially with \
261                     co-directed normals, so the exact difference pinches to zero thickness \
262                     (non-manifold, unrepresentable in a boundary model) — refusing rather \
263                     than returning a perturbed sliver"
264                )));
265            }
266            // TANGENT-NODE GATE, same doctrine as the pinch gate above and for
267            // the same reason. A 4-valent tangent node is refused because the
268            // kernel cannot imprint it, and perturbation is only legitimate
269            // where the exact arrangement is UNSTABLE but its ANSWER is not:
270            // the nudged configuration resolves the kiss one way or the other
271            // and the kernel has no claim on which. Until 2026-09-13 the ladder
272            // could not produce a candidate here at all; with `fit_polyline`
273            // measuring its own result the equal-radius torus pair's LAST rung
274            // (3.0e-3 of scale) assembles, validates, and passes the semantic
275            // oracle at 399 samples with a zero disagreement rate, reading
276            // 299.4998500 against the ideal union's 299.6 — a 0.03% error that
277            // is the nudge's, not the kernel's answer. Shipping that would
278            // answer a question this class exists to decline, so the class does
279            // not take the ladder. Escape hatch for measuring it:
280            // BREP_TANGENT_NODE_GATE=0.
281            if matches!(error.class, RefusalClass::TangentNodeSingularity)
282                && std::env::var("BREP_TANGENT_NODE_GATE").as_deref() != Ok("0")
283            {
284                return Err(error);
285            }
286            match perturbation_retry(first, second, operation, options) {
287                Ok(solid) => Ok(solid),
288                Err(refused) if refused.is_empty() => Err(error),
289                Err(refused) => {
290                    Err(error.with_message(|message| format!("{message}; {}", refused.join("; "))))
291                }
292            }
293        }
294    }
295}
296
297/// Grid resolution (per parameter direction, endpoints included) used to probe a
298/// face pair for a tangential contact. Endpoints and the midpoint are both
299/// sampled, so a contact sitting on a periodic seam (a cylinder's `u = 0`
300/// ruling) and one sitting at a face's parametric centre are both hit exactly.
301const PINCH_PROBE_STEPS: usize = 8;
302
303/// `|n_a × n_b|` bound below which two unit normals count as parallel — the same
304/// bound `csg::imprint`'s pair classifier uses (`PAIR_ANGULAR_TOLERANCE`).
305const PINCH_ANGULAR_TOLERANCE: f64 = 1e-4;
306
307/// Parameter-space step (fraction of the domain span) taken around a contact
308/// sample to prove the contact is LOWER-DIMENSIONAL — a tangency curve/point
309/// rather than a cosurface patch.
310const PINCH_SEPARATION_STEP: f64 = 1e-2;
311
312/// Would the exact `first − second` PINCH to zero thickness at a tangential
313/// contact?
314///
315/// True when some face of `first` and some face of `second` touch tangentially
316/// (surfaces within the contact band, normals parallel) with CO-DIRECTED outward
317/// normals, at a contact that is lower-dimensional (the surfaces separate as you
318/// step away from it).
319///
320/// Co-directed outward normals at a tangency mean one solid lies locally INSIDE
321/// the other: subtracting leaves material on both sides of the contact that
322/// meets there at zero thickness — a non-manifold pinch no boundary model can
323/// represent. Anti-directed normals are the harmless EXTERNAL tangency (a
324/// cylinder resting against a wall): the difference simply keeps `first` intact.
325/// The lower-dimensionality requirement excludes a CO-SURFACE contact (a pocket
326/// wall flush with an outer wall — co-directed normals, but the surfaces stay
327/// coincident in every direction), where the difference is perfectly well
328/// behaved.
329///
330/// Conservative by construction: it only reports a pinch it can actually witness
331/// on the probe grid, and a miss just leaves the perturbation lane to its
332/// existing gates.
333fn subtract_pinches_at_internal_tangency(
334    first: &BrepSolid,
335    second: &BrepSolid,
336    tolerance: f64,
337) -> Result<bool, KernelRefusal> {
338    // Same contact band the imprint pair classifier uses for its sampled
339    // "these carriers touch" verdict.
340    let contact = (tolerance * 100.0).max(1e-12);
341    let probes_a = first
342        .shells
343        .iter()
344        .flat_map(|shell| &shell.faces)
345        .map(PinchProbe::of)
346        .collect::<Result<Vec<_>, _>>()?;
347    let probes_b = second
348        .shells
349        .iter()
350        .flat_map(|shell| &shell.faces)
351        .map(PinchProbe::of)
352        .collect::<Result<Vec<_>, _>>()?;
353    for probe_a in &probes_a {
354        for probe_b in &probes_b {
355            // Sampled-hull cull, so the (failure-path-only) cost stays linear in
356            // the faces that actually touch rather than quadratic in every face.
357            // The hull is built from the probe grid, so it can under-cover a
358            // curved carrier between samples: pad it by a percent of the pair's
359            // size before culling. Over-keeping a pair only costs a probe that
360            // finds nothing.
361            let pad = 1e-2 * probe_a.extent().max(probe_b.extent());
362            if probe_a.separation(probe_b) > contact + pad {
363                continue;
364            }
365            // CO-SURFACE contacts are 2-dimensional, not tangencies: two flush
366            // walls (a pocket's side coincident with an outer wall, a cylinder
367            // cap lying in a box face) have co-directed normals wherever the
368            // solids nest, yet the difference there is perfectly well behaved.
369            // Only a LOWER-dimensional contact pinches, so drop the pair the
370            // pair classifier calls cosurface before probing it.
371            if classify_surface_pair(
372                &probe_a.face.surface,
373                &probe_b.face.surface,
374                tolerance,
375                PINCH_ANGULAR_TOLERANCE,
376            ).or_refuse(KernelStage::Validate, "csg.boolean.mod")?
377            .relation
378                == SurfacePairRelation::Cosurface
379            {
380                continue;
381            }
382            if faces_touch_with_codirected_normals(probe_a, probe_b, contact)? {
383                return Ok(true);
384            }
385        }
386    }
387    Ok(false)
388}
389
390/// A face's probe grid plus the grid's bounding box, built once per face so the
391/// pair loop only pays for a hull comparison.
392struct PinchProbe<'a> {
393    face: &'a FaceRecord,
394    /// `(u, v, point)` on the face's carrier surface.
395    samples: Vec<(f64, f64, Vec3)>,
396    minimum: [f64; 3],
397    maximum: [f64; 3],
398}
399
400impl<'a> PinchProbe<'a> {
401    fn of(face: &'a FaceRecord) -> Result<Self, KernelRefusal> {
402        let [u0, u1] = face.surface.domain_u().or_refuse(KernelStage::Validate, "domain_u")?;
403        let [v0, v1] = face.surface.domain_v().or_refuse(KernelStage::Validate, "domain_v")?;
404        let steps = PINCH_PROBE_STEPS as f64;
405        let mut samples = Vec::with_capacity((PINCH_PROBE_STEPS + 1).pow(2));
406        let mut minimum = [f64::INFINITY; 3];
407        let mut maximum = [f64::NEG_INFINITY; 3];
408        for i in 0..=PINCH_PROBE_STEPS {
409            let u = u0 + (u1 - u0) * i as f64 / steps;
410            for j in 0..=PINCH_PROBE_STEPS {
411                let v = v0 + (v1 - v0) * j as f64 / steps;
412                let Ok(point) = face.surface.evaluate(u, v) else {
413                    continue;
414                };
415                for (axis, value) in [point.x, point.y, point.z].into_iter().enumerate() {
416                    minimum[axis] = minimum[axis].min(value);
417                    maximum[axis] = maximum[axis].max(value);
418                }
419                samples.push((u, v, point));
420            }
421        }
422        Ok(Self {
423            face,
424            samples,
425            minimum,
426            maximum,
427        })
428    }
429
430    /// Largest side of the sampled hull (0 when nothing sampled).
431    fn extent(&self) -> f64 {
432        (0..3)
433            .map(|axis| self.maximum[axis] - self.minimum[axis])
434            .fold(0.0f64, f64::max)
435    }
436
437    /// Axis-aligned gap between the two sampled hulls (0 when they overlap).
438    fn separation(&self, other: &Self) -> f64 {
439        let mut gap: f64 = 0.0;
440        for axis in 0..3 {
441            gap = gap.max(self.minimum[axis] - other.maximum[axis]);
442            gap = gap.max(other.minimum[axis] - self.maximum[axis]);
443        }
444        gap
445    }
446}
447
448/// One face pair of [`subtract_pinches_at_internal_tangency`]: probe both
449/// surfaces on a grid, keep samples that land on the other surface inside BOTH
450/// trims with parallel co-directed outward normals, and accept only where the
451/// contact provably separates nearby.
452fn faces_touch_with_codirected_normals(
453    probe_a: &PinchProbe<'_>,
454    probe_b: &PinchProbe<'_>,
455    contact: f64,
456) -> Result<bool, KernelRefusal> {
457    for (probe, other) in [(probe_a, probe_b), (probe_b, probe_a)] {
458        let source = probe.face;
459        let target = other.face;
460        for &(u, v, point) in &probe.samples {
461            let projection = project_point_to_surface(&target.surface, point).or_refuse(KernelStage::Validate, "project_point_to_surface")?;
462            if projection.distance > contact {
463                continue;
464            }
465            // A pole (collapsed du × dv) has no reliable orientation here;
466            // skipping it keeps the predicate conservative.
467            let (Ok(source_normal), Ok(target_normal)) = (
468                source.surface.normal(u, v),
469                target.surface.normal(projection.u, projection.v),
470            ) else {
471                continue;
472            };
473            let source_outward = outward_normal(source_normal, source.same_sense);
474            let target_outward = outward_normal(target_normal, target.same_sense);
475            if source_outward.cross(target_outward).length() > PINCH_ANGULAR_TOLERANCE
476                || source_outward.dot(target_outward) <= 0.0
477            {
478                continue;
479            }
480            if parameter_point_in_face(source, Vec2 { x: u, y: v }, 1e-6).or_refuse(KernelStage::Validate, "parameter_point_in_face")?
481                == PolygonClass::Outside
482                || parameter_point_in_face(
483                    target,
484                    Vec2 {
485                        x: projection.u,
486                        y: projection.v,
487                    },
488                    1e-6,
489                ).or_refuse(KernelStage::Validate, "csg.boolean.mod")? == PolygonClass::Outside
490            {
491                continue;
492            }
493            if contact_separates_locally(source, target, u, v, contact)? {
494                return Ok(true);
495            }
496        }
497    }
498    Ok(false)
499}
500
501fn outward_normal(normal: Vec3, same_sense: bool) -> Vec3 {
502    if same_sense {
503        normal
504    } else {
505        normal.scale(-1.0)
506    }
507}
508
509/// Does the contact at `source(u, v)` LEAVE the contact band when you step away
510/// from it in parameter space? A tangency curve or point does (stepping across
511/// the tangency separates the surfaces quadratically); a cosurface patch does
512/// not (it stays coincident in every direction). Steps are clamped into the
513/// domain, so a sample on a seam or a domain edge probes only inward.
514fn contact_separates_locally(
515    source: &FaceRecord,
516    target: &FaceRecord,
517    u: f64,
518    v: f64,
519    contact: f64,
520) -> Result<bool, KernelRefusal> {
521    let [u0, u1] = source.surface.domain_u().or_refuse(KernelStage::Validate, "domain_u")?;
522    let [v0, v1] = source.surface.domain_v().or_refuse(KernelStage::Validate, "domain_v")?;
523    let step_u = (u1 - u0) * PINCH_SEPARATION_STEP;
524    let step_v = (v1 - v0) * PINCH_SEPARATION_STEP;
525    for (probe_u, probe_v) in [
526        ((u + step_u).min(u1), v),
527        ((u - step_u).max(u0), v),
528        (u, (v + step_v).min(v1)),
529        (u, (v - step_v).max(v0)),
530    ] {
531        if (probe_u - u).abs() < f64::EPSILON && (probe_v - v).abs() < f64::EPSILON {
532            continue;
533        }
534        let Ok(point) = source.surface.evaluate(probe_u, probe_v) else {
535            continue;
536        };
537        // A generous multiple of the band: the step must clear it decisively,
538        // never on projection noise.
539        if project_point_to_surface(&target.surface, point).or_refuse(KernelStage::Validate, "project_point_to_surface")?.distance > contact * 10.0 {
540            return Ok(true);
541        }
542    }
543    Ok(false)
544}
545
546/// Deterministic perturbation-fallback (Simulation of Simplicity), the textbook
547/// CAD answer to an exact/near-tangent arrangement degeneracy, adopted after six
548/// incremental arrangement/imprint/fragment fixes for the near-tangent class were
549/// each falsified, rather than attempting a seventh. Rigidly TRANSLATE the second operand by a tiny
550/// epsilon so the degenerate contact becomes a clean transversal crossing, redo
551/// the boolean, and accept the result ONLY if it is (a) topologically valid —
552/// guaranteed by `assemble`'s own gate returning `Ok` — (b) semantically equal to
553/// the exact CSG of the ORIGINAL operands (the point-classification oracle), and
554/// (c) volume-consistent with the CSG inequality bounds. A wrong perturbed result
555/// is rejected on (b)/(c), so this lane preserves the engine's fail-safe property:
556/// it can only turn a degeneracy-error into a CORRECT solid, never a wrong one.
557///
558/// INVARIANT — no recursion: this calls `boolean_operation_with_diagnostics`
559/// (the EXACT path), never `boolean_operation`, so the fallback can never
560/// re-enter itself. A future refactor must preserve that.
561///
562/// (d) THE IDENTITY. Where the exact lane answers one of the other operations
563/// on the same operands, inclusion–exclusion names the volume this result
564/// must have, and a rung that misses it by more than the case gate's own drift
565/// band (`VOLUME_DRIFT_REL` of that volume, `VOLUME_DRIFT_ABS` floor) is
566/// refused by name. (c)'s 1% of the operands' total cannot see an error in a
567/// small removed volume: on the helmet's cutter − helmet (2026-09-15) the rung
568/// at fraction 3e-4 moved the helmet 5.2e-3, 2.3% of its size, passed the
569/// oracle at 1 in 336, and read 999.999899266 against V(cutter) − V(cutter ∩
570/// helmet) = 999.999905792: 6.9% too much material removed. Where no other
571/// operation clears on the exact lane there is no identity to hold, and the
572/// rung is held to (e) instead. Escape hatch for measuring it:
573/// BREP_PERTURB_IDENTITY=0 (which also lifts (e)).
574///
575/// (e) THE MIRROR. Where no exact-lane boolean of the operands clears in
576/// either order, the rung is held to itself moved the other way: the same
577/// operation, or failing that any sibling reading, on the second operand moved
578/// by −offset, with V(first) and V(second) from the mass integrator and the
579/// operation's sign, names the volume the mirrored rung encloses. A rigid
580/// translation changes the answer to first order in the offset, so half the
581/// difference between the rung's body and its mirror is the body's
582/// first-order translation error, and it must clear the same drift band. A
583/// rung whose mirror clears no boolean at all has nothing to check it and is
584/// refused by name. On 22_sag_boundary_identity_t164 (2026-09-15) the four
585/// shipped rescues at offset 9.7e-4 disagreed with each other through V(∩) by
586/// 4.3e-2 (the difference moves the part; the others move the sphere), against
587/// a bar of 5e-6. Escape hatch: BREP_PERTURB_MIRROR=0.
588///
589/// (d) and (e) run before (b), the oracle, which costs more than either.
590///
591/// (f) THE EARLY EXIT. Along one direction the mirror's error grows with the
592/// offset (`MirrorTrend` states the law and its measurement), so a direction
593/// whose two refused rungs read one sign, growing, is not tried at larger
594/// offsets, and the refusal names it. booleanIssue's refusal walked 20 rungs,
595/// each with its mirror, in 40.5 s against the case's 30 s. Escape hatch:
596/// BREP_MIRROR_EARLY_EXIT=0.
597///
598/// `Err` carries one sentence per rung refused on (d), empty when none was.
599fn perturbation_retry(
600    first: &BrepSolid,
601    second: &BrepSolid,
602    operation: BooleanOperation,
603    options: &BooleanOptions,
604) -> Result<BrepSolid, Vec<String>> {
605    // Generic translation directions: each has a substantial component on ALL
606    // three axes, so whatever axis the coincidence normal lies along (an
607    // equatorial plane flush with a box face, a cap plane flush with a side
608    // face, …) at least one direction has a component that lifts the contact off
609    // exact tangency. Axis-aligned directions are deliberately excluded — a nudge
610    // parallel to the coincident plane leaves the degeneracy in place (verified:
611    // pure ±X / ±Z never rescue the flush-torus case). Fixed, not hash-seeded
612    // as the lane was first sketched: the seeding's only purpose was to rule out
613    // RNG/clock nondeterminism, which fixed vectors already satisfy, and four
614    // diverse directions cover any axis-aligned normal.
615    const DIRECTIONS: [[f64; 3]; 4] = [
616        [0.4034, 0.7973, 0.4491],
617        [0.7973, 0.4491, 0.4034],
618        [0.4491, 0.4034, 0.7973],
619        [0.5774, -0.5774, 0.5774],
620    ];
621    // Fractions of operand scale, ordered SMALLEST-FIRST so the accepted solid
622    // carries the least geometric offset. Sized above the near-tangent sliver
623    // dead zone and below local feature size; the ladder + accept-gate make the
624    // exact magnitude non-critical (a rung that lands in a dead zone simply fails
625    // the gate and the next rung is tried).
626    const FRACTIONS: [f64; 5] = PERTURBATION_FRACTIONS;
627
628    let debug = std::env::var("BREP_DEBUG_BOOL").is_ok();
629    let scale = crate::tolerance::solid_scale(first)
630        .max(crate::tolerance::solid_scale(second))
631        .max(1.0);
632
633    // Original operand volumes for the CSG inequality backstop (the oracle skips
634    // points near boundaries, so a wrong thin shell hugging the flush face is
635    // invisible to it but shows up here).
636    let _caller = crate::mass_caller("boolean.perturbation_rescue");
637    let va = solid_signed_volume(first).ok().map(f64::abs);
638    let vb = solid_signed_volume(second).ok().map(f64::abs);
639    let hold_identity = std::env::var("BREP_PERTURB_IDENTITY").as_deref() != Ok("0");
640    // (e) is held only with (d): `BREP_PERTURB_MIRROR=0` alone ships a rung
641    // with no identity on (a)–(c), as before.
642    let hold_mirror = hold_identity && std::env::var("BREP_PERTURB_MIRROR").as_deref() != Ok("0");
643    // (f) A direction whose mirror errors say no larger offset can clear is
644    // closed. Escape hatch: `BREP_MIRROR_EARLY_EXIT=0` walks every rung.
645    let early_exit = std::env::var("BREP_MIRROR_EARLY_EXIT").as_deref() != Ok("0");
646    let mut trends = [MirrorTrend::default(); 4];
647    // Measured once, on the first rung that clears (a)–(c).
648    let mut identity: Option<Option<InclusionExclusion>> = None;
649    let mut refused = Vec::new();
650    // `BREP_DEBUG_PERTURB_LADDER=1`: every rung's disposition on stderr, and
651    // for every rung that builds, its miss against the identity, measured
652    // before the ladder starts. Changes no decision.
653    let ladder = std::env::var("BREP_DEBUG_PERTURB_LADDER").is_ok();
654    let ladder_start = web_time::Instant::now();
655    if ladder {
656        let measured = inclusion_exclusion_volume(first, second, operation, options, va, vb, false);
657        match &measured {
658            Some(identity) => eprintln!(
659                "[ladder] identity {} = {:.9} from the exact lane's {} ({:.9}); V(first) {:?} V(second) {:?}",
660                identity.formula, identity.expected, identity.reading, identity.read, va, vb
661            ),
662            None => eprintln!("[ladder] no identity: no exact-lane boolean of these operands clears in either order"),
663        }
664        identity = Some(measured);
665    }
666    let rung_miss = |identity: &Option<Option<InclusionExclusion>>, candidate: &BrepSolid| -> String {
667        let volume = body_volume(candidate);
668        match (identity, volume) {
669            (Some(Some(identity)), Some(volume)) => format!(
670                "volume {volume:.9} miss {:+.3e} bar {:.3e}",
671                volume - identity.expected,
672                crate::VOLUME_DRIFT_ABS.max(crate::VOLUME_DRIFT_REL * identity.expected.abs())
673            ),
674            (_, Some(volume)) => format!("volume {volume:.9}"),
675            _ => "no volume".to_string(),
676        }
677    };
678
679    for &fraction in &FRACTIONS {
680        let magnitude = scale * fraction;
681        for (direction, dir) in DIRECTIONS.iter().enumerate() {
682            if trends[direction].closed {
683                if ladder {
684                    eprintln!("[ladder] dir={dir:?} fraction={fraction:.1e} offset {magnitude:.3e}: not tried, its direction is closed");
685                }
686                continue;
687            }
688            let offset = [dir[0] * magnitude, dir[1] * magnitude, dir[2] * magnitude];
689            let translate = match AffineTransform::new([
690                1.0, 0.0, 0.0, offset[0], //
691                0.0, 1.0, 0.0, offset[1], //
692                0.0, 0.0, 1.0, offset[2], //
693                0.0, 0.0, 0.0, 1.0,
694            ]) {
695                Ok(transform) => transform,
696                Err(_) => continue,
697            };
698            let moved = match crate::transform_brep(second, translate, false) {
699                Ok(solid) => solid,
700                Err(_) => continue,
701            };
702            let rung = format!("dir={dir:?} fraction={fraction:.1e} offset {magnitude:.3e}");
703            let rung_start = web_time::Instant::now();
704            let candidate =
705                match boolean_operation_with_diagnostics(first, &moved, operation, options) {
706                    Ok(outcome) => outcome.value,
707                    Err(error) => {
708                        if ladder {
709                            eprintln!("[ladder] {rung}: exact lane refused {:?}", error.class);
710                        }
711                        continue;
712                    }
713                };
714            let built = rung_start.elapsed();
715            // A perturbation must not "rescue" a case into emptiness.
716            if candidate.shells.is_empty() {
717                if ladder {
718                    eprintln!("[ladder] {rung}: empty");
719                }
720                continue;
721            }
722            // (c) Volume must satisfy the CSG inequality for the ORIGINAL
723            // operands (generous 1% relative slack: catches gross wrongness —
724            // doubled/halved bodies, missing lobes — without rejecting the
725            // legitimate sub-micron offset).
726            if let (Some(va), Some(vb)) = (va, vb) {
727                if let Ok(vr) = solid_signed_volume(&candidate) {
728                    if !volume_within_csg_bounds(operation, va, vb, vr.abs()) {
729                        if ladder {
730                            eprintln!("[ladder] {rung}: outside the CSG bound, {}", rung_miss(&identity, &candidate));
731                        }
732                        continue;
733                    }
734                }
735            }
736            // (d) and (e) run before (b): each costs a volume, and (e) one
737            // exact-lane boolean, where the oracle classifies 400 points. A
738            // rung ships only when every gate clears, so the order decides
739            // which gate names a refusal, never which rung ships.
740            let bounded = rung_start.elapsed();
741            // (d) The exact lane's inclusion–exclusion identity.
742            if hold_identity {
743                let identity = identity.get_or_insert_with(|| {
744                    let identity =
745                        inclusion_exclusion_volume(first, second, operation, options, va, vb, false);
746                    if debug && identity.is_none() {
747                        eprintln!(
748                            "[perturb] no exact-lane boolean of these operands clears in either \
749                             order; no identity to hold"
750                        );
751                    }
752                    identity
753                });
754                if let Some(identity) = identity {
755                    let expected = identity.expected;
756                    let bar = crate::VOLUME_DRIFT_ABS.max(crate::VOLUME_DRIFT_REL * expected.abs());
757                    let volume = body_volume(&candidate);
758                    let miss = volume.map(|volume| (volume - expected).abs());
759                    if !miss.is_some_and(|miss| miss <= bar) {
760                        let sentence = format!(
761                            "perturbation rung dir={dir:?} fraction={fraction:.1e} \
762                             (offset {magnitude:.3e}) refused: its body reads {} against the \
763                             inclusion–exclusion identity {} = {expected:.9} (the exact lane's \
764                             {} reads {:.9}), a miss of {} beyond the volume drift bar {bar:.3e}",
765                            volume.map_or("no volume".to_string(), |volume| format!("{volume:.9}")),
766                            identity.formula,
767                            identity.reading,
768                            identity.read,
769                            miss.map_or("unmeasured".to_string(), |miss| format!("{miss:.3e}")),
770                        );
771                        if debug {
772                            eprintln!("[perturb] {sentence}");
773                        }
774                        if ladder {
775                            eprintln!(
776                                "[ladder] {rung}: refused by the identity, volume {} miss {} bar {bar:.3e}",
777                                volume.map_or("none".to_string(), |volume| format!("{volume:.9}")),
778                                volume.map_or("unmeasured".to_string(), |volume| format!("{:+.3e}", volume - expected)),
779                            );
780                        }
781                        refused.push(sentence);
782                        continue;
783                    }
784                } else if hold_mirror {
785                    // (e) No exact reading of these operands: the rung's
786                    // mirror.
787                    let mirror_start = web_time::Instant::now();
788                    let mirrored = AffineTransform::new([
789                        1.0, 0.0, 0.0, -offset[0], //
790                        0.0, 1.0, 0.0, -offset[1], //
791                        0.0, 0.0, 1.0, -offset[2], //
792                        0.0, 0.0, 0.0, 1.0,
793                    ])
794                    .ok()
795                    .and_then(|transform| crate::transform_brep(second, transform, false).ok());
796                    let mirror = mirrored.as_ref().and_then(|mirrored| {
797                        inclusion_exclusion_volume(first, mirrored, operation, options, va, vb, true)
798                    });
799                    let mirror_time = mirror_start.elapsed();
800                    let volume = body_volume(&candidate);
801                    let mut measured = None;
802                    let verdict = match (&mirror, volume) {
803                        (Some(mirror), Some(volume)) => {
804                            let bar = crate::VOLUME_DRIFT_ABS.max(crate::VOLUME_DRIFT_REL * mirror.expected.abs());
805                            let miss = 0.5 * (volume - mirror.expected).abs();
806                            measured = Some((0.5 * (volume - mirror.expected), bar));
807                            if ladder {
808                                eprintln!(
809                                    "[ladder] {rung}: mirror at −offset reads {} = {:.9} (the mirrored {} {:.9}); body {volume:.9}, first-order translation error {:+.3e} bar {bar:.3e}",
810                                    mirror.formula, mirror.expected, mirror.reading, mirror.read, 0.5 * (volume - mirror.expected)
811                                );
812                            }
813                            (miss > bar).then(|| format!(
814                                "perturbation rung dir={dir:?} fraction={fraction:.1e} \
815                                 (offset {magnitude:.3e}) refused: no exact-lane boolean of the \
816                                 operands clears, so the rung is held to its mirror: its body reads \
817                                 {volume:.9} with the second operand moved +offset and {} = {:.9} \
818                                 with it moved −offset (the mirrored {} reads {:.9}), a first-order \
819                                 translation error of {miss:.3e} beyond the volume drift bar {bar:.3e}",
820                                mirror.formula, mirror.expected, mirror.reading, mirror.read
821                            ))
822                        }
823                        _ => Some(format!(
824                            "perturbation rung dir={dir:?} fraction={fraction:.1e} \
825                             (offset {magnitude:.3e}) refused: no exact-lane boolean of the operands \
826                             clears, and none clears with the second operand moved −offset either, \
827                             so nothing checks the rung's body{}",
828                            volume.map_or(String::new(), |volume| format!(" ({volume:.9})"))
829                        )),
830                    };
831                    match verdict {
832                        None => trends[direction].cleared(),
833                        Some(sentence) => {
834                            if debug {
835                                eprintln!("[perturb] {sentence}");
836                            }
837                            if ladder {
838                                eprintln!(
839                                    "[ladder] {rung}: refused by its mirror (rung {:.0} ms: built {:.0}, bound {:.0}, mirror {:.0} ms; ladder {:.1} s)",
840                                    rung_start.elapsed().as_secs_f64() * 1e3,
841                                    built.as_secs_f64() * 1e3,
842                                    (bounded - built).as_secs_f64() * 1e3,
843                                    mirror_time.as_secs_f64() * 1e3,
844                                    ladder_start.elapsed().as_secs_f64()
845                                );
846                            }
847                            refused.push(sentence);
848                            // (f) Two refused rungs of one sign, growing, close
849                            // the direction.
850                            let closure = measured.and_then(|(error, bar)| {
851                                let earlier = trends[direction].refused(magnitude, error, early_exit)?;
852                                Some((earlier, error, bar))
853                            });
854                            if let Some(((earlier_offset, earlier_error), error, bar)) = closure {
855                                let closed = format!(
856                                    "perturbation direction dir={dir:?} closed after offset {magnitude:.3e}: its \
857                                     rungs' first-order translation errors read {earlier_error:+.3e} at offset \
858                                     {earlier_offset:.3e} and {error:+.3e} at {magnitude:.3e}, one sign and \
859                                     growing, so no larger offset along it comes back to the volume drift bar \
860                                     {bar:.3e}"
861                                );
862                                if debug {
863                                    eprintln!("[perturb] {closed}");
864                                }
865                                if ladder {
866                                    eprintln!("[ladder] {closed}");
867                                }
868                                refused.push(closed);
869                            }
870                            continue;
871                        }
872                    }
873                }
874            }
875            // (b) Semantic agreement with the exact CSG of the ORIGINAL
876            // operands. Reject a vacuous verdict (skip-band swallowed every
877            // sample so nothing was actually checked).
878            match crate::oracle::boolean_semantic_disagreement(
879                first, second, operation, &candidate, 400,
880            ) {
881                Ok(report) if report.considered >= 30 && !report.is_flagged() => {
882                    if ladder {
883                        eprintln!(
884                            "[ladder] {rung}: accepted (oracle {} considered, rate {:.4}), {}",
885                            report.considered, report.disagreement_rate, rung_miss(&identity, &candidate)
886                        );
887                    }
888                    if debug {
889                        eprintln!(
890                            "[perturb] rescued {operation:?}: dir={dir:?} fraction={fraction:.1e} \
891                             magnitude={magnitude:.3e} offset={offset:?} \
892                             (oracle considered={} rate={:.4})",
893                            report.considered, report.disagreement_rate
894                        );
895                    }
896                    return Ok(candidate);
897                }
898                report => {
899                    if ladder {
900                        let verdict = match &report {
901                            Ok(report) => format!("oracle {} considered, rate {:.4}", report.considered, report.disagreement_rate),
902                            Err(error) => format!("oracle error {error:?}"),
903                        };
904                        eprintln!("[ladder] {rung}: refused by the oracle ({verdict}), {}", rung_miss(&identity, &candidate));
905                    }
906                    continue;
907                }
908            }
909        }
910    }
911    if debug {
912        eprintln!("[perturb] ladder exhausted for {operation:?}; no rung produced a clean result");
913    }
914    Err(refused)
915}
916
917/// The perturbation ladder's offsets, as fractions of the operands' scale,
918/// smallest first.
919const PERTURBATION_FRACTIONS: [f64; 5] = [3e-5, 1e-4, 3e-4, 1e-3, 3e-3];
920
921/// (f) One ladder direction's rungs refused by their mirrors.
922///
923/// Along one direction a rung's signed first-order translation error, half
924/// its body less its mirror's reading, reads s(δ) = kδ + r: a slope k from the
925/// translation, and a remainder r from the degeneracy the rung steps over,
926/// which does not grow with the offset. Two refused rungs at δ₁ < δ₂ with one
927/// sign and |s(δ₂)| ≥ |s(δ₁)| fix k's sign as theirs, so every larger offset
928/// reads farther from zero than s(δ₂), which is already beyond the drift bar:
929/// the direction is closed. Rungs of one sign that shrink toward zero, or that
930/// change sign, say nothing about k, and the direction stays open; a rung whose
931/// mirror clears starts the direction's record again.
932///
933/// Measured 2026-09-15 (`BREP_DEBUG_PERTURB_LADDER=1` prints s). On
934/// 22_sag_boundary_identity_t164, all four operations and all four
935/// directions, s/δ keeps one sign and holds to 3.6% over the ladder's 100× of
936/// offset (r ≈ 0; −22.23 .. −22.34 on the intersection's first direction). On
937/// booleanIssue's flush partial torus union, s/δ holds to 0.5% and 8.5% on two
938/// directions, and to 1.2% on a third past its smallest rung, whose remainder
939/// reads −43.4 against the slope's −24.5. The first direction's smallest rung
940/// reads −1.891e-2 where its slope, +2.3 .. +2.6, gives +3.5e-3 (r ≈ −2.2e-2),
941/// and its next measured rung +3.510e-2: a change of sign, which keeps it open.
942/// A closed direction's skipped rung would have had to fall to the bar, 8.3e2 ..
943/// 1.5e6 times below the smallest refused |s| on those rows, to have cleared.
944#[derive(Clone, Copy, Default)]
945struct MirrorTrend {
946    /// The last rung refused by its mirror: its offset and signed error.
947    last: Option<(f64, f64)>,
948    closed: bool,
949}
950
951impl MirrorTrend {
952    /// Records a rung refused by its mirror at `offset` with signed error
953    /// `error`. When it and the direction's last refused rung close the
954    /// direction, and `close` allows it, the direction is closed and the
955    /// earlier rung's (offset, signed error) returned.
956    fn refused(&mut self, offset: f64, error: f64, close: bool) -> Option<(f64, f64)> {
957        let earlier = self
958            .last
959            .filter(|&(_, earlier)| earlier.signum() == error.signum() && error.abs() >= earlier.abs())
960            .filter(|_| close);
961        self.last = Some((offset, error));
962        self.closed |= earlier.is_some();
963        earlier
964    }
965
966    /// A rung whose mirror clears: the direction's record starts again.
967    fn cleared(&mut self) {
968        self.last = None;
969    }
970}
971
972/// Non-degenerate edges a solid's faces use exactly once.
973fn one_use_edges(solid: &BrepSolid) -> usize {
974    let mut uses: HashMap<u64, usize> = HashMap::default();
975    for coedge in solid
976        .shells
977        .iter()
978        .flat_map(|shell| &shell.faces)
979        .flat_map(|face| &face.loops)
980        .flat_map(|loop_record| &loop_record.coedges)
981    {
982        *uses.entry(coedge.edge_id).or_default() += 1;
983    }
984    solid
985        .edges
986        .iter()
987        .filter(|edge| !edge.degenerate && uses.get(&edge.id) == Some(&1))
988        .count()
989}
990
991/// A body's enclosed volume, zero for an empty one.
992fn body_volume(solid: &BrepSolid) -> Option<f64> {
993    if solid.shells.is_empty() {
994        return Some(0.0);
995    }
996    solid_signed_volume(solid).ok().map(f64::abs)
997}
998
999/// What the exact lane's other operations say `operation` on these operands
1000/// must enclose.
1001struct InclusionExclusion {
1002    /// The rescued operation's volume in terms of V(first), V(second) and
1003    /// V(first ∩ second).
1004    formula: &'static str,
1005    /// The exact-lane boolean that supplied V(first ∩ second).
1006    reading: &'static str,
1007    /// That boolean's enclosed volume.
1008    read: f64,
1009    /// The volume `formula` gives.
1010    expected: f64,
1011}
1012
1013/// The volume `operation` on these operands must have, by inclusion–exclusion.
1014/// Any one exact-lane boolean of the same two operands fixes V(first ∩ second),
1015/// and with the operands' own volumes that fixes all four operations. The
1016/// readings are tried intersection, union, then difference, each in both
1017/// operand orders: the exact lane is not order-symmetric (the helmet's cutter
1018/// ∩ helmet refuses where helmet ∩ cutter clears). The rescued operation itself,
1019/// in its own order, is skipped, since it is the one that refused, unless
1020/// `own_first` asks for it to be read first (a rung's mirror, where it is a
1021/// different pair of operands). `None` when no reading clears or an operand has
1022/// no volume.
1023fn inclusion_exclusion_volume(
1024    first: &BrepSolid,
1025    second: &BrepSolid,
1026    operation: BooleanOperation,
1027    options: &BooleanOptions,
1028    va: Option<f64>,
1029    vb: Option<f64>,
1030    own_first: bool,
1031) -> Option<InclusionExclusion> {
1032    use BooleanOperation::{Intersect, Subtract, Union};
1033    let (va, vb) = (va?, vb?);
1034    let own = match operation {
1035        Intersect => (Intersect, false, "first ∩ second"),
1036        Union => (Union, false, "first ∪ second"),
1037        Subtract => (Subtract, false, "first − second"),
1038    };
1039    let readings = [
1040        (Intersect, false, "first ∩ second"),
1041        (Intersect, true, "second ∩ first"),
1042        (Union, false, "first ∪ second"),
1043        (Union, true, "second ∪ first"),
1044        (Subtract, false, "first − second"),
1045        (Subtract, true, "second − first"),
1046    ];
1047    let own_reading = own_first.then_some(own);
1048    let leading = usize::from(own_first);
1049    for (index, (other, swapped, reading)) in own_reading.into_iter().chain(readings).enumerate() {
1050        if index >= leading && !swapped && std::mem::discriminant(&other) == std::mem::discriminant(&operation) {
1051            continue;
1052        }
1053        let (x, y) = if swapped { (second, first) } else { (first, second) };
1054        let Some(read) = boolean_operation_with_diagnostics(x, y, other, options)
1055            .ok()
1056            .and_then(|outcome| body_volume(&outcome.value))
1057        else {
1058            continue;
1059        };
1060        let intersection = match (other, swapped) {
1061            (Intersect, _) => read,
1062            (Union, _) => va + vb - read,
1063            (Subtract, false) => va - read,
1064            (Subtract, true) => vb - read,
1065        };
1066        let (formula, expected) = match operation {
1067            Intersect => ("V(first ∩ second)", intersection),
1068            Union => ("V(first) + V(second) − V(first ∩ second)", va + vb - intersection),
1069            Subtract => ("V(first) − V(first ∩ second)", va - intersection),
1070        };
1071        return Some(InclusionExclusion { formula, reading, read, expected });
1072    }
1073    None
1074}
1075
1076/// The worst planar face of either operand whose STRAIGHT boundary sits
1077/// farther off its plane than the assembly weld radius:
1078/// `(operand index, face id, gap, weld)`. Mirrors the STEP importer's planar
1079/// split predicate (`step_import::builder::planar_split`) on a finished
1080/// solid: a plane, a single loop of straight non-degenerate edges, and a
1081/// boundary vertex beyond the weld. `None` means no such face, so a failure
1082/// may still be a genuine degeneracy worth a perturbation rung. Only
1083/// consulted on the failure path.
1084fn planar_operand_off_its_plane(
1085    first: &BrepSolid,
1086    second: &BrepSolid,
1087    options: &BooleanOptions,
1088) -> Option<(u8, u64, f64, f64)> {
1089    let policy = options
1090        .tolerances
1091        .clone()
1092        .unwrap_or_else(|| KernelTolerances::for_pair(first, second, options.tolerance));
1093    let weld = crate::tolerance::assembler_weld(policy.model);
1094    let mut worst: Option<(u8, u64, f64)> = None;
1095    for (operand, solid) in [(0u8, first), (1u8, second)] {
1096        let vertex_point = |id: u64| solid.vertices.iter().find(|v| v.id == id).map(|v| v.point);
1097        for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
1098            let Some(crate::AnalyticSurface::Plane {
1099                origin,
1100                u_dir,
1101                v_dir,
1102                ..
1103            }) = face.surface.analytic()
1104            else {
1105                continue;
1106            };
1107            let [loop_record] = face.loops.as_slice() else {
1108                continue;
1109            };
1110            let Ok(normal) = u_dir.cross(*v_dir).normalized() else {
1111                continue;
1112            };
1113            let mut off_plane = 0.0f64;
1114            let mut straight = true;
1115            for coedge in &loop_record.coedges {
1116                let Some(edge) = solid.edges.iter().find(|edge| edge.id == coedge.edge_id) else {
1117                    straight = false;
1118                    break;
1119                };
1120                let (Some(start), Some(end)) =
1121                    (vertex_point(edge.start_vertex_id), vertex_point(edge.end_vertex_id))
1122                else {
1123                    straight = false;
1124                    break;
1125                };
1126                let chord = end.sub(start);
1127                let length = chord.length();
1128                if edge.degenerate || length <= weld {
1129                    straight = false;
1130                    break;
1131                }
1132                let direction = chord.scale(1.0 / length);
1133                for fraction in [0.25, 0.5, 0.75] {
1134                    let Ok(point) = edge.curve.evaluate(edge.t0 + (edge.t1 - edge.t0) * fraction) else {
1135                        straight = false;
1136                        break;
1137                    };
1138                    let delta = point.sub(start);
1139                    let along = delta.dot(direction);
1140                    if delta.sub(direction.scale(along)).length() > weld
1141                        || along < -weld
1142                        || along > length + weld
1143                    {
1144                        straight = false;
1145                        break;
1146                    }
1147                }
1148                if !straight {
1149                    break;
1150                }
1151                off_plane = off_plane.max(start.sub(*origin).dot(normal).abs());
1152            }
1153            if straight && off_plane > weld && worst.is_none_or(|(_, _, gap)| off_plane > gap) {
1154                worst = Some((operand, face.id, off_plane));
1155            }
1156        }
1157    }
1158    worst.map(|(operand, face_id, gap)| (operand, face_id, gap, weld))
1159}
1160
1161/// CSG volume inequality bounds (no extra boolean needed) with 1% relative slack.
1162/// `va`, `vb`, `vr` are absolute volumes of operand A, operand B, and the result.
1163fn volume_within_csg_bounds(operation: BooleanOperation, va: f64, vb: f64, vr: f64) -> bool {
1164    let slack = 1e-2 * (va + vb).max(1.0);
1165    match operation {
1166        // max(A,B) ≤ A∪B ≤ A+B
1167        BooleanOperation::Union => vr >= va.max(vb) - slack && vr <= va + vb + slack,
1168        // A−B ≤ A (and ≥ 0)
1169        BooleanOperation::Subtract => vr <= va + slack && vr >= -slack,
1170        // A∩B ≤ min(A,B)
1171        BooleanOperation::Intersect => vr <= va.min(vb) + slack && vr >= -slack,
1172    }
1173}
1174
1175// ---------------------------------------------------------------------------
1176// N-ary booleans (Golovanov T4.4)
1177//
1178// Union / intersect / subtract of N solids in ONE imprint+fragment pass.  This
1179// is a NEW ADDITIVE entry point: the binary `boolean_operation` above is left
1180// byte-identical.  The pipeline mirrors the binary one but generalizes the two
1181// operand-specific stages to N operands:
1182//
1183//   1. Imprint EVERY unordered pair (i, j) so every mutual intersection edge is
1184//      present on both operands' faces, and accumulate all pairs into ONE global
1185//      imprint (operand indices remapped to 0..N, piece/vertex ids made globally
1186//      unique, per-face piece lists and per-edge split parameters merged).
1187//   2. Split + fragment each operand by that single global imprint, so each
1188//      operand's faces are cut by ALL of its intersections at once.
1189//   3. Select fragments in one pass: the per-fragment keep decision generalizes
1190//      from "vs one other solid" to "vs the SET of other solids" (see
1191//      `nary_keep`).  Because every pair is imprinted, a fragment's interior lies
1192//      wholly inside or wholly outside each other operand, so a single interior
1193//      test point per (fragment, other-solid) is an exact membership verdict.
1194//   4. Assemble with the same machinery (`assemble_fragments`), then merge and
1195//      validate exactly as the binary path does.
1196// ---------------------------------------------------------------------------
1197
1198/// Reverse a fragment's orientation (surface sense + every coedge), the same
1199/// transform the binary Subtract path applies to the second operand's kept
1200/// fragments.  Extracted so the n-ary selector can reuse it verbatim.
1201fn reverse_fragment(fragment: &mut FaceFragmentRecord) -> Result<(), KernelRefusal> {
1202    fragment.same_sense = !fragment.same_sense;
1203    for loop_record in &mut fragment.loops {
1204        loop_record.coedges.reverse();
1205        for coedge in &mut loop_record.coedges {
1206            coedge.forward = !coedge.forward;
1207            coedge.pcurve = coedge.pcurve.reversed().or_refuse(KernelStage::Validate, "reversed")?;
1208        }
1209    }
1210    Ok(())
1211}
1212
1213/// Imprint every unordered pair of operands and fold the pairwise
1214/// `ImprintResultRecord`s into ONE global imprint whose operand indices are the
1215/// operands' global positions (0..N).  Piece and vertex ids are re-based to be
1216/// globally unique; `by_face` piece lists and `edge_splits` parameter lists are
1217/// MERGED per key so `fragment_face` (which takes the first `by_face` match) and
1218/// `apply_edge_splits` (which collects one entry per edge) see every cut.
1219fn build_nary_imprint(
1220    operands: &[BrepSolid],
1221    options: &ImprintOptions,
1222) -> Result<ImprintResultRecord, KernelRefusal> {
1223    let mut section_evidence = false;
1224    let mut vertices: Vec<ImprintVertex> = Vec::new();
1225    let mut pieces: Vec<ImprintPieceRecord> = Vec::new();
1226    let mut by_face: HashMap<(u8, u64), Vec<u64>> = HashMap::default();
1227    let mut edge_splits: HashMap<(u8, u64), Vec<f64>> = HashMap::default();
1228    let mut next_vertex_id: u64 = 1;
1229    let mut next_piece_id: u64 = 1;
1230
1231    for i in 0..operands.len() {
1232        for j in (i + 1)..operands.len() {
1233            let pair = build_imprints(&operands[i], &operands[j], options)?;
1234            let remap = |operand: u8| -> u8 {
1235                if operand == 0 {
1236                    i as u8
1237                } else {
1238                    j as u8
1239                }
1240            };
1241
1242            section_evidence = section_evidence || pair.section_evidence;
1243            let mut vertex_map: HashMap<u64, u64> = HashMap::default();
1244            for vertex in &pair.vertices {
1245                let global = next_vertex_id;
1246                next_vertex_id += 1;
1247                vertex_map.insert(vertex.id, global);
1248                vertices.push(ImprintVertex {
1249                    id: global,
1250                    point: vertex.point,
1251                });
1252            }
1253
1254            let mut piece_map: HashMap<u64, u64> = HashMap::default();
1255            for piece in &pair.pieces {
1256                let global = next_piece_id;
1257                next_piece_id += 1;
1258                piece_map.insert(piece.id, global);
1259                let mut mapped = piece.clone();
1260                mapped.id = global;
1261                mapped.start_vertex_id = *vertex_map
1262                    .get(&piece.start_vertex_id)
1263                    .ok_or_else(|| KernelRefusal::internal(KernelStage::Intersect, "nary.imprint", "n-ary imprint: piece references unknown vertex"))?;
1264                mapped.end_vertex_id = *vertex_map
1265                    .get(&piece.end_vertex_id)
1266                    .ok_or_else(|| KernelRefusal::internal(KernelStage::Intersect, "nary.imprint", "n-ary imprint: piece references unknown vertex"))?;
1267                for pcurve in &mut mapped.pcurves {
1268                    pcurve.operand = remap(pcurve.operand);
1269                }
1270                mapped.support_faces = [
1271                    FaceKey {
1272                        operand: remap(piece.support_faces[0].operand),
1273                        face_id: piece.support_faces[0].face_id,
1274                    },
1275                    FaceKey {
1276                        operand: remap(piece.support_faces[1].operand),
1277                        face_id: piece.support_faces[1].face_id,
1278                    },
1279                ];
1280                pieces.push(mapped);
1281            }
1282
1283            for entry in &pair.by_face {
1284                let list = by_face
1285                    .entry((remap(entry.operand), entry.face_id))
1286                    .or_default();
1287                for piece_id in &entry.piece_ids {
1288                    let global = *piece_map.get(piece_id).ok_or_else(|| {
1289                        KernelRefusal::internal(KernelStage::Intersect, "nary.imprint", "n-ary imprint: by_face references unknown piece")
1290                    })?;
1291                    list.push(global);
1292                }
1293            }
1294
1295            for split in &pair.edge_splits {
1296                edge_splits
1297                    .entry((remap(split.operand), split.edge_id))
1298                    .or_default()
1299                    .extend(split.parameters.iter().copied());
1300            }
1301        }
1302    }
1303
1304    Ok(ImprintResultRecord {
1305        vertices,
1306        pieces,
1307        tangent_nodes: Vec::new(),
1308        barrier_edges: Vec::new(),
1309        section_evidence,
1310        cosurface_pairs: Vec::new(),
1311        tangent_sections: Vec::new(),
1312        by_face: by_face
1313            .into_iter()
1314            .map(|((operand, face_id), piece_ids)| FaceImprints {
1315                operand,
1316                face_id,
1317                piece_ids,
1318            })
1319            .collect(),
1320        edge_splits: edge_splits
1321            .into_iter()
1322            .map(|((operand, edge_id), parameters)| EdgeSplitRecord {
1323                operand,
1324                edge_id,
1325                parameters,
1326            })
1327            .collect(),
1328    })
1329}
1330
1331/// N-ary union / intersect / subtract of `operands` in ONE imprint+fragment
1332/// pass (Golovanov T4.4).  Additive: does not touch the binary path.
1333///
1334/// - `Union`   : the boundary of `∪ operands`.
1335/// - `Intersect`: the boundary of `∩ operands`.
1336/// - `Subtract`: `operands[0]` minus the union of `operands[1..]`.
1337///
1338/// `N = 1` returns the sole operand; `N = 2` runs the same general pipeline and
1339/// reproduces the binary result (same volume, valid).  Returns a clear `Err`
1340/// when the operands cannot assemble into a closed, valid solid.
1341pub fn boolean_operation_nary(
1342    operands: &[BrepSolid],
1343    operation: BooleanOperation,
1344) -> Result<BrepSolid, KernelRefusal> {
1345    if operands.is_empty() {
1346        return Err(KernelRefusal::input(KernelStage::Collect, "operands", "boolean_operation_nary: no operands provided"));
1347    }
1348    if operands.len() == 1 {
1349        return Ok(operands[0].clone());
1350    }
1351    if operands.len() > 255 {
1352        return Err(KernelRefusal::input(KernelStage::Collect, "operands", "boolean_operation_nary: at most 255 operands supported"));
1353    }
1354    let debug = std::env::var("BREP_DEBUG_BOOL").is_ok();
1355    let options = BooleanOptions::default();
1356
1357    // One tolerance policy for the whole set: the largest-scale operand's policy
1358    // dominates (every derived band is monotonic in scale), matching how the
1359    // binary path takes `for_pair` = max of the two scales.
1360    let mut policy = KernelTolerances::for_solid(&operands[0], options.tolerance);
1361    for operand in &operands[1..] {
1362        let candidate = KernelTolerances::for_solid(operand, options.tolerance);
1363        if candidate.sew_search > policy.sew_search {
1364            policy = candidate;
1365        }
1366    }
1367    policy.check().or_input(KernelStage::Collect, "tolerances")?;
1368    let tolerance = policy.model;
1369
1370    // Per-operand fuse-first healing, exactly as the binary path (a no-op on
1371    // clean input, so clean operands are untouched).
1372    let mut healed = operands.to_vec();
1373    for operand in &mut healed {
1374        crate::heal::heal_operands(operand, &policy)?;
1375        // Then refit any inherited trim off the pcurve construction contract
1376        // whose edge lies on every carrier (a no-op without one).
1377        crate::heal::refit_inherited_trims(operand, &policy)?;
1378        // Same operand normalization as the binary path: seam edges for
1379        // seamless full-period band faces.
1380        normalize_operand_band_seams(operand)?;
1381    }
1382
1383    // 1. Imprint every pair into one global imprint.
1384    let mut imprint_options = options.imprint.clone();
1385    imprint_options.tolerance = tolerance;
1386    let imprint = build_nary_imprint(&healed, &imprint_options)?;
1387
1388    // 2. Split every operand's edges, then fragment its faces, by that imprint.
1389    let mut split = Vec::with_capacity(healed.len());
1390    for (index, operand) in healed.iter().enumerate() {
1391        split.push(apply_edge_splits(operand, index as u8, &imprint)?);
1392    }
1393    let mut fragments = Vec::with_capacity(split.len());
1394    for (index, solid) in split.iter().enumerate() {
1395        fragments.push(fragment_solid(solid, index as u8, &imprint)?);
1396    }
1397
1398    // 3. Select fragments in one pass against the set of other operands.
1399    let selected = select_fragments_nary(&fragments, &healed, operation, tolerance, debug)?;
1400
1401    // 4. Assemble + normalize + validate, mirroring the binary tail.
1402    let solids: HashMap<u8, &BrepSolid> = split
1403        .iter()
1404        .enumerate()
1405        .map(|(index, solid)| (index as u8, solid))
1406        .collect();
1407    let solid = assemble_fragments(selected, &solids, &imprint, tolerance)?;
1408    let solid = if options.merge_coplanar_faces
1409        && std::env::var("BREP_COPLANAR_MERGE").as_deref() != Ok("0")
1410    {
1411        let merged = merge_same_surface_faces_excluding(
1412            &solid,
1413            tolerance,
1414            &options.keep_unmerged_name_substrs,
1415        )?;
1416        merge_curve_continuation_edges(&merged, tolerance).map_err(|refusal| KernelRefusal { stage: KernelStage::Validate, ..refusal })?
1417    } else {
1418        solid
1419    };
1420    let validation = solid.validate_detailed(&policy);
1421    if !validation.issues.is_empty() {
1422        return Err(KernelRefusal::new(
1423            RefusalClass::InvalidResultTopology { issues: validation.issues.len() as u32 },
1424            KernelStage::Validate,
1425            format!(
1426            "boolean_operation_nary: invalid result: {:?}",
1427            validation.issues
1428        )));
1429    }
1430    if debug {
1431        if let Ok(report) =
1432            crate::oracle::boolean_semantic_disagreement_nary(&healed, operation, &solid, 300)
1433        {
1434            if report.is_flagged() {
1435                eprintln!(
1436                    "[oracle] n-ary semantic disagreement rate {:.4} ({} of {} decidable points); sample {:?}",
1437                    report.disagreement_rate,
1438                    report.disagreements.len(),
1439                    report.considered,
1440                    report.sample_disagreement()
1441                );
1442            }
1443        }
1444    }
1445    Ok(solid)
1446}
1447
1448/// TANGENT-NODE ATTRIBUTION.
1449///
1450/// The imprint's second-order filter decides which tangential contacts are
1451/// isolated NODES the arrangement can carve, and marches those instead of
1452/// refusing. It cannot decide a THIRD-order singularity — where the transverse
1453/// branch is tangent to a contact curve, every second-order quantity vanishes
1454/// together and the contact reads as a clean saddle at every stopping rule
1455/// (`imprint/tangent_contact.rs`). The assembly is the authority on whether an
1456/// admitted node was really imprintable, so a tear downstream of one is
1457/// reported AGAINST THE NODE rather than as an anonymous degeneracy: same
1458/// refusal class the whole tangent-node family carries, naming the point.
1459///
1460/// Only classes that are ALREADY perturbation-eligible are re-attributed, so a
1461/// refusal that could never take the ladder still cannot.
1462///
1463/// It does decide WHICH refusals are retried, though, and since 2026-09-13 that
1464/// matters: `boolean_operation`'s failure arm gates `TangentNodeSingularity` out
1465/// of the perturbation ladder (the TANGENT-NODE GATE there) and reads the class
1466/// AFTER this rewrite. So a tear downstream of an ADMITTED node — whatever class
1467/// the pipeline actually raised — declines the ladder along with the tangent-node
1468/// family proper. That is the conservative direction and the same claim the class
1469/// makes (a tear after a node is the node proving unimprintable). Anything added
1470/// to this rewrite widens that gate: read the gate when you touch the
1471/// attribution.
1472fn attribute_to_tangent_node(error: KernelRefusal, nodes: &[Vec3]) -> KernelRefusal {
1473    let Some(node) = nodes.first() else {
1474        return error;
1475    };
1476    if !error.class.perturbation_eligible()
1477        || matches!(error.class, RefusalClass::TangentNodeSingularity)
1478    {
1479        return error;
1480    }
1481    KernelRefusal::new(
1482        RefusalClass::TangentNodeSingularity,
1483        error.stage,
1484        format!(
1485            "boolean: unsupported singular/tangent-node surface intersection \
1486             at the tangent node at ({:.6},{:.6},{:.6}): the section through it did not \
1487             assemble into a closed shell",
1488            node.x, node.y, node.z
1489        ),
1490    )
1491}
1492
1493/// The tangent-curve lane's attribution: an ASSEMBLY refusal whose faces the
1494/// imprint found touching along a CURVE is reported against that contact, by
1495/// its faces and its ends, in the tangent-node class — the arrangement's own
1496/// message (an open loop, a one-use edge) is the symptom, the contact is the
1497/// cause, and the class keeps the refusal out of the perturbation ladder
1498/// exactly as an admitted node's tear is kept out
1499/// (`attribute_to_tangent_node`). The original message is carried along so
1500/// nothing the stage said is hidden.
1501///
1502/// REACH: the faces the refusal names, and only those. `origins` maps the
1503/// assembled result's face ids (minted one per selected fragment, from 1, in
1504/// order) back to operand faces; the ids are read off the message's
1505/// `face <id>` (validate's open-loop issues) and `on faces [<id> '…`
1506/// (one-use edges) entries, and the rewrite happens only when one of them is
1507/// a face the section INVOLVES (`TangentSectionRecord::faces`: the carriers'
1508/// faces and the faces across the edges its ends lie on). A refusal naming no
1509/// face, or only faces the contact does not touch — a model with a benign
1510/// recorded contact (a blind pocket touching a wall) that fails elsewhere —
1511/// keeps its class and enters the ladder as it always did. With
1512/// `ImprintOptions::tangent_curve_lane` off the sections are empty and
1513/// nothing is rewritten.
1514fn attribute_to_tangent_section(
1515    error: KernelRefusal,
1516    sections: &[crate::imprint::TangentSectionRecord],
1517    origins: &[FaceKey],
1518) -> KernelRefusal {
1519    if sections.is_empty()
1520        || !error.class.perturbation_eligible()
1521        || matches!(error.class, RefusalClass::TangentNodeSingularity)
1522    {
1523        return error;
1524    }
1525    let named = refusal_result_faces(&error.message)
1526        .into_iter()
1527        .filter_map(|id| origins.get(id.checked_sub(1)? as usize).copied())
1528        .collect::<Vec<FaceKey>>();
1529    let Some(section) = sections
1530        .iter()
1531        .find(|section| named.iter().any(|face| section.faces.contains(face)))
1532    else {
1533        return error;
1534    };
1535    // A clipped end sits on a trim at round-off, and `-0.000000` names no
1536    // place: print what six decimals can show.
1537    let at = |point: Vec3| {
1538        let shown = |value: f64| if value.abs() < 5e-7 { 0.0 } else { value };
1539        format!("({:.6},{:.6},{:.6})", shown(point.x), shown(point.y), shown(point.z))
1540    };
1541    let shape = if section.planar {
1542        format!(
1543            "touch along a tangent curve from {} to {} (length {:.6}, {} stations{}) which the \
1544             imprint does not build yet",
1545            at(section.start),
1546            at(section.end),
1547            section.length,
1548            section.stations,
1549            if section.closed { ", closed" } else { "" }
1550        )
1551    } else {
1552        format!(
1553            "touch along a curve through {} with neither carrier planar, a curved/curved \
1554             tangent contact the lane does not trace yet",
1555            at(section.start)
1556        )
1557    };
1558    let involved = named
1559        .iter()
1560        .filter(|face| section.faces.contains(face))
1561        .map(|face| format!("{}/{}", face.operand, face.face_id))
1562        .collect::<Vec<_>>()
1563        .join(", ");
1564    KernelRefusal::new(
1565        RefusalClass::TangentNodeSingularity,
1566        error.stage,
1567        format!(
1568            "boolean: unsupported tangent-curve surface intersection between faces {}/{} and {}/{}: \
1569             the carriers {shape}; the refusal names face(s) {involved} the contact involves; {}",
1570            section.first.operand, section.first.face_id, section.second.operand, section.second.face_id,
1571            error.message
1572        ),
1573    )
1574}
1575
1576/// The RESULT face ids an assembly refusal names: every `face <id>` (the
1577/// validator's "loop … of face <id> is open …") and every `<id> '` entry of an
1578/// `on faces [...]` list (the one-use edge census). Nothing else in the
1579/// message is read.
1580fn refusal_result_faces(message: &str) -> Vec<u64> {
1581    let mut ids = Vec::new();
1582    let digits_after = |text: &str| -> Option<u64> {
1583        let end = text.find(|c: char| !c.is_ascii_digit()).unwrap_or(text.len());
1584        text[..end].parse().ok()
1585    };
1586    for (index, _) in message.match_indices("face ") {
1587        if let Some(id) = digits_after(&message[index + "face ".len()..]) {
1588            ids.push(id);
1589        }
1590    }
1591    if let Some(start) = message.find("on faces [") {
1592        let rest = &message[start + "on faces [".len()..];
1593        let list = &rest[..rest.find(']').unwrap_or(rest.len())];
1594        for entry in list.split(", ") {
1595            let entry = entry.trim_start();
1596            if let Some(id) = digits_after(entry) {
1597                if entry[id.to_string().len()..].starts_with(" '") {
1598                    ids.push(id);
1599                }
1600            }
1601        }
1602    }
1603    ids.sort_unstable();
1604    ids.dedup();
1605    ids
1606}
1607
1608pub fn boolean_operation_with_diagnostics(
1609    first: &BrepSolid,
1610    second: &BrepSolid,
1611    operation: BooleanOperation,
1612    options: &BooleanOptions,
1613) -> Result<KernelOutcome<BrepSolid>, KernelRefusal> {
1614    let closed_inputs = matches!(std::env::var("BREP_CLOSED_OPERAND_INPUT").as_deref(), Err(_) | Ok("1") | Ok("on"));
1615    boolean_operation_with_diagnostics_impl(first, second, operation, options, closed_inputs)
1616}
1617
1618fn boolean_operation_with_diagnostics_impl(
1619    first: &BrepSolid,
1620    second: &BrepSolid,
1621    operation: BooleanOperation,
1622    options: &BooleanOptions,
1623    closed_inputs: bool,
1624) -> Result<KernelOutcome<BrepSolid>, KernelRefusal> {
1625    // OPERAND-KIND GATE. A body that DECLARES itself a Sheet has no
1626    // inside, so a union or subtract with it has no solid answer; the
1627    // survey (record 2026-10-12, table 2) measured the arrangement
1628    // returning a validating solid of volume 3.5 when the sheet's opening
1629    // lay outside the other operand. Read from the declaration only,
1630    // before any arrangement work (nothing infers a kind from the edge
1631    // census; an undeclared open body is the closure gate's below).
1632    // Intersect is not gated here.
1633    if matches!(operation, BooleanOperation::Union | BooleanOperation::Subtract) {
1634        if let Some(operand) = [first, second]
1635            .iter()
1636            .position(|solid| solid.kind == crate::BodyKind::Sheet)
1637        {
1638            return Err(KernelRefusal::input(
1639                KernelStage::Collect,
1640                "sheet_operand",
1641                format!(
1642                    "boolean: operand {operand} is a Sheet, which encloses no solid; union and \
1643                     subtract of a sheet are refused (slice and trim are not built)"
1644                ),
1645            ));
1646        }
1647    }
1648    // Inside/outside is undefined for an open operand, even if its opening
1649    // misses the other operand and the constructed result happens to validate.
1650    // Count existing edge uses, without modifying/healing either operand.
1651    // Keep failures on their original path and preserve their class/evidence;
1652    // the existing perturbation gate already diagnoses open failed operands.
1653    let mut outcome = boolean_operation_with_diagnostics_core(first, second, operation, options)?;
1654    let closure_started = closed_inputs.then(Instant::now);
1655    if closed_inputs {
1656        for (operand, solid) in [first, second].into_iter().enumerate() {
1657            let open = one_use_edges(solid);
1658            if open > 0 {
1659                return Err(KernelRefusal::input(KernelStage::Collect, "operand_closure", format!(
1660                    "boolean: operand {operand} is not a closed shell: {open} of its edges bound only one face, so its inside is undefined"
1661                )));
1662            }
1663        }
1664    }
1665    if let Some(started) = closure_started {
1666        let ms = started.elapsed().as_secs_f64() * 1000.0;
1667        profile_stage("operand_closure_ms", ms);
1668        outcome.diagnostics.measure_max("timing.operand_closure_ms", ms);
1669    }
1670    Ok(outcome)
1671}
1672
1673fn boolean_operation_with_diagnostics_core(
1674    first: &BrepSolid, second: &BrepSolid, operation: BooleanOperation, options: &BooleanOptions,
1675) -> Result<KernelOutcome<BrepSolid>, KernelRefusal> {
1676
1677    // The translation control (`BREP_TRANSLATION_CONTROL`, `brep/recentre.rs`):
1678    // the outermost entry runs the operation on translated operands.
1679    if let Some(offset) = crate::recentre::control_offset_at_entry() {
1680        return boolean_operation_with_diagnostics_at_offset(first, second, operation, options, offset);
1681    }
1682    // The recentre guard: operands far from the origin in their own
1683    // units run at the origin.
1684    let mut bounds = operand_bounds(first);
1685    bounds.include(operand_bounds(second));
1686    if let Some(offset) = crate::recentre::guard_offset(bounds) {
1687        crate::recentre::profile_fired("boolean", offset, bounds);
1688        let mut outcome = boolean_translated(first, second, operation, options, offset, crate::recentre::EntryKind::Guard)?;
1689        outcome.diagnostics.count_n("recentre.fired", 1);
1690        outcome.diagnostics.measure_max("recentre.distance", offset.length());
1691        return Ok(outcome);
1692    }
1693    let mut tangent_nodes = Vec::new();
1694    let (mut outcome, soundness_candidates) =
1695        boolean_pipeline(first, second, operation, options, &mut tangent_nodes)
1696            .map_err(|error| attribute_to_tangent_node(error, &tangent_nodes))?;
1697    // The assembly's single exit. A boolean can hand back a body whose walls
1698    // pass through each other while `validate()` is silent and the volume
1699    // measures right, which is exactly the failure a regularized boolean must
1700    // not report as success. A crossing the OPERANDS already had is theirs and
1701    // passes through — the defect came in with the input and refusing it here
1702    // would blame the wrong operation — so the operands are only scanned when
1703    // the result is flagged, and the common case pays for one scan. The
1704    // acceptance's refusal is typed at its origin (an `UnsoundResult` naming
1705    // the defect and the faces); it was classed `Internal` here while
1706    // `accept_sound_against` still returned text. Timed as its own stage: the
1707    // record's phase split read only the pipeline's `timing.*` measures, and
1708    // on a 108-face disjoint union this scan was 185 of 226 ms end to end.
1709    let stage_started = Instant::now();
1710    let accepted = match &soundness_candidates {
1711        // Pass-through: the faces the operation touched, their
1712        // neighbours and every face whose box reaches the other operand.
1713        Some(candidates) => crate::accept_sound_against_scoped(&[first, second], outcome.value, "boolean", candidates),
1714        None => crate::accept_sound_against(&[first, second], outcome.value, "boolean"),
1715    };
1716    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
1717    profile_stage("soundness_ms", stage_ms);
1718    outcome.diagnostics.measure_max("timing.soundness_ms", stage_ms);
1719    outcome.value = accepted?;
1720    Ok(outcome)
1721}
1722
1723/// [`boolean_operation_with_diagnostics`] with both operands translated by
1724/// `offset` for the operation and the result translated back. Refusal
1725/// messages name the translated coordinates.
1726pub fn boolean_operation_with_diagnostics_at_offset(
1727    first: &BrepSolid,
1728    second: &BrepSolid,
1729    operation: BooleanOperation,
1730    options: &BooleanOptions,
1731    offset: Vec3,
1732) -> Result<KernelOutcome<BrepSolid>, KernelRefusal> {
1733    boolean_translated(first, second, operation, options, offset, crate::recentre::EntryKind::Control)
1734}
1735
1736/// The translated run behind the control and the guard.
1737fn boolean_translated(
1738    first: &BrepSolid,
1739    second: &BrepSolid,
1740    operation: BooleanOperation,
1741    options: &BooleanOptions,
1742    offset: Vec3,
1743    kind: crate::recentre::EntryKind,
1744) -> Result<KernelOutcome<BrepSolid>, KernelRefusal> {
1745    let _entry = crate::recentre::TranslatedEntry::open(kind);
1746    let translate = |solid: &BrepSolid, offset: Vec3| {
1747        crate::recentre::translate_solid(solid, offset).map_err(|message| {
1748            KernelRefusal::internal(
1749                KernelStage::Collect,
1750                "translation_control",
1751                format!("boolean: the translation control could not move an operand: {message}"),
1752            )
1753        })
1754    };
1755    let moved_first = translate(first, offset)?;
1756    let moved_second = translate(second, offset)?;
1757    let outcome = boolean_operation_with_diagnostics(&moved_first, &moved_second, operation, options)?;
1758    let value = translate(&outcome.value, offset.scale(-1.0))?;
1759    Ok(KernelOutcome { value, diagnostics: outcome.diagnostics })
1760}
1761
1762/// Stage wall-clock line on stderr when `BREP_PROFILE` is set — the same switch
1763/// `imprint.profile` and `assemble.*_ms` use — so a REFUSED boolean, whose
1764/// `KernelDiagnostics` never reach a caller, still shows where its time went.
1765fn profile_stage(code: &str, ms: f64) {
1766    if std::env::var("BREP_PROFILE").is_ok() {
1767        eprintln!("boolean.{code}={ms:.2}");
1768    }
1769}
1770
1771/// `BREP_BOOLEAN_DUMP` names a directory. Every binary boolean then writes its
1772/// stages there as JSON — the two split operands, the selected fragments, the
1773/// assembled solid and the merged one — numbered per process, so a probe can
1774/// read a face's loops at each stage without replaying the document.
1775struct BooleanStageDump {
1776    directory: std::path::PathBuf,
1777    serial: usize,
1778}
1779
1780impl BooleanStageDump {
1781    fn open() -> Option<Self> {
1782        static SERIAL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
1783        let directory = std::env::var("BREP_BOOLEAN_DUMP").ok().filter(|value| !value.is_empty())?;
1784        let serial = SERIAL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1785        Some(Self { directory: directory.into(), serial })
1786    }
1787
1788    fn write<T: Serialize + ?Sized>(&self, stage: &str, value: &T) {
1789        let path = self.directory.join(format!("{:03}-{stage}.json", self.serial));
1790        match serde_json::to_string(value) {
1791            Ok(text) => {
1792                if let Err(error) = std::fs::write(&path, text) {
1793                    eprintln!("BREP_BOOLEAN_DUMP {}: {error}", path.display());
1794                }
1795            }
1796            Err(error) => eprintln!("BREP_BOOLEAN_DUMP {stage}: {error}"),
1797        }
1798    }
1799}
1800
1801/// The tolerance policy a binary boolean of `first` and `second` runs under.
1802fn boolean_policy(
1803    first: &BrepSolid,
1804    second: &BrepSolid,
1805    options: &BooleanOptions,
1806) -> Result<KernelTolerances, KernelRefusal> {
1807    let policy = options
1808        .tolerances
1809        .unwrap_or_else(|| KernelTolerances::for_pair(first, second, options.tolerance));
1810    policy.check().or_input(KernelStage::Collect, "tolerances")?;
1811    Ok(policy)
1812}
1813
1814/// The operands as the imprint reads them: healed, with their band seams
1815/// normalized.
1816fn prepared_operands(
1817    first: &BrepSolid,
1818    second: &BrepSolid,
1819    policy: &KernelTolerances,
1820) -> Result<(BrepSolid, BrepSolid), KernelRefusal> {
1821    // Fuse-first operand healing (Lever A): before any intersection touches the
1822    // operands, snap each one's near-coincident / off-plane vertices to exact
1823    // and re-anchor the incident edge curves onto them, so noisy
1824    // near-degenerate input (points that should coincide but drifted, vertices
1825    // a few microns off a planar face) cannot tip a fixed downstream band over
1826    // the edge.  Healing is per-operand, validate-gated, and — because a clean
1827    // operand has no vertices within `heal_tol` and none off its planes — a
1828    // no-op that leaves the boolean output byte-identical on clean inputs.
1829    let stage_started = Instant::now();
1830    let mut first_owned = first.clone();
1831    let mut second_owned = second.clone();
1832    crate::heal::heal_operands(&mut first_owned, policy)?;
1833    crate::heal::heal_operands(&mut second_owned, policy)?;
1834    profile_stage("heal.operands_ms", stage_started.elapsed().as_secs_f64() * 1_000.0);
1835    // Then refit, on each operand's own carriers, any inherited trim off the
1836    // pcurve construction contract whose edge lies on every incident carrier
1837    // (`heal::refit_inherited_trims`; a byte-identical no-op without one).
1838    let refit_started = Instant::now();
1839    crate::heal::refit_inherited_trims(&mut first_owned, policy)?;
1840    crate::heal::refit_inherited_trims(&mut second_owned, policy)?;
1841    profile_stage("heal.refit_ms", refit_started.elapsed().as_secs_f64() * 1_000.0);
1842    if std::env::var("BREP_PROFILE").is_ok() {
1843        let (hits, misses) = crate::reading_memo::counts();
1844        eprintln!("boolean.reading_memo hits={hits} misses={misses}");
1845    }
1846    profile_stage("heal_ms", stage_started.elapsed().as_secs_f64() * 1_000.0);
1847    // Seamless full-period band faces (STEP import) break the seam-aware
1848    // imprint/arrangement machinery; normalize them to the seam-carrying
1849    // topology native periodic faces use. No-op on operands without such
1850    // faces. See `insert_periodic_band_seam_edges`.
1851    normalize_operand_band_seams(&mut first_owned)?;
1852    normalize_operand_band_seams(&mut second_owned)?;
1853    Ok((first_owned, second_owned))
1854}
1855
1856fn boolean_imprint_options(options: &BooleanOptions, tolerance: f64) -> ImprintOptions {
1857    let mut imprint_options = options.imprint.clone();
1858    imprint_options.tolerance = tolerance;
1859    imprint_options
1860}
1861
1862/// The two operands a binary boolean's arrangement reads: prepared as the
1863/// boolean prepares them, with every boundary edge split where the imprint's
1864/// sections meet it. These are the `NNN-first` and `NNN-second` stages
1865/// `BREP_BOOLEAN_DUMP` writes, not the operands at the boolean's entry.
1866///
1867/// A boolean must read the same on these as on the operands it split them
1868/// from: the splits are vertices its own imprint puts on those edges anyway.
1869/// Where it does not, an imprint lane is deciding on what the input happens to
1870/// have split, and that is a defect.
1871pub fn boolean_split_operands(
1872    first: &BrepSolid,
1873    second: &BrepSolid,
1874    options: &BooleanOptions,
1875) -> Result<(BrepSolid, BrepSolid), KernelRefusal> {
1876    let policy = boolean_policy(first, second, options)?;
1877    let (first, second) = prepared_operands(first, second, &policy)?;
1878    let imprint = build_imprints(&first, &second, &boolean_imprint_options(options, policy.model))?;
1879    let (split_first, _) = apply_edge_splits_with_map(&first, 0, &imprint)?;
1880    let (split_second, _) = apply_edge_splits_with_map(&second, 1, &imprint)?;
1881    Ok((split_first, split_second))
1882}
1883
1884/// Conservative bounds of an operand: its vertices and every face's control
1885/// net (a NURBS lies in the hull of its controls). A control net that cannot
1886/// be read bounds everything, so nothing is left out for want of a reading.
1887fn operand_bounds(solid: &BrepSolid) -> crate::Aabb {
1888    let mut bounds = crate::Aabb::from_points(solid.vertices.iter().map(|vertex| vertex.point));
1889    for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
1890        match crate::Aabb::from_surface_controls(&face.surface) {
1891            Ok(face_bounds) => bounds.include(face_bounds),
1892            Err(_) => {
1893                return crate::Aabb {
1894                    minimum: Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
1895                    maximum: Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY),
1896                }
1897            }
1898        }
1899    }
1900    bounds
1901}
1902
1903/// The result faces the soundness scan reads under pass-through: the
1904/// examined faces (touched and their neighbours) and every other face whose
1905/// control-net box reaches the OTHER operand's bounds within `band`. A face
1906/// outside both is an operand's own, clear of everything the operation added.
1907fn soundness_candidates(
1908    solid: &BrepSolid,
1909    scope: &AssemblyScope,
1910    bounds: [crate::Aabb; 2],
1911    band: f64,
1912) -> HashSet<u64> {
1913    let mut candidates = HashSet::default();
1914    for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
1915        if scope.examined.contains(&face.id) {
1916            candidates.insert(face.id);
1917            continue;
1918        }
1919        let Some(&operand) = scope.operand_of.get(&face.id) else {
1920            candidates.insert(face.id);
1921            continue;
1922        };
1923        let other = bounds[usize::from(operand == 0)];
1924        match crate::Aabb::from_surface_controls(&face.surface) {
1925            Ok(face_bounds) if !face_bounds.intersects(other, band) => {}
1926            _ => {
1927                candidates.insert(face.id);
1928            }
1929        }
1930    }
1931    candidates
1932}
1933
1934/// The empty solid a legitimately empty boolean returns.
1935fn empty_solid() -> BrepSolid {
1936    BrepSolid {
1937        mass_properties_cache: Default::default(),
1938        id: 0,
1939        vertices: Vec::new(),
1940        edges: Vec::new(),
1941        shells: Vec::new(),
1942        genus: 0,
1943        kind: crate::BodyKind::Solid,
1944    }
1945}
1946
1947/// The two operands as one solid, shell by shell: ids minted afresh in the
1948/// assembler's order (the first operand's faces, then the second's), the
1949/// second operand's face names suffixed where they clash with the first's
1950/// as the assembler suffixes a split face, geometry copied byte for byte,
1951/// genus the sum. Nothing is welded: the operands are disjoint.
1952fn concatenate_disjoint(first: &BrepSolid, second: &BrepSolid) -> BrepSolid {
1953    let mut vertices = Vec::new();
1954    let mut edges = Vec::new();
1955    let mut shells = Vec::new();
1956    let mut used_face_names = HashSet::<String>::default();
1957    let (mut next_face_id, mut next_loop_id, mut next_coedge_id) = (1u64, 1u64, 1u64);
1958    for operand in [first, second] {
1959        let vertex_base = vertices.len() as u64;
1960        let edge_base = edges.len() as u64;
1961        let mut vertex_ids: HashMap<u64, u64> = HashMap::default();
1962        for (index, vertex) in operand.vertices.iter().enumerate() {
1963            let id = vertex_base + index as u64 + 1;
1964            vertex_ids.insert(vertex.id, id);
1965            vertices.push(VertexRecord { id, point: vertex.point });
1966        }
1967        let mut edge_ids: HashMap<u64, u64> = HashMap::default();
1968        for (index, edge) in operand.edges.iter().enumerate() {
1969            let id = edge_base + index as u64 + 1;
1970            edge_ids.insert(edge.id, id);
1971            edges.push(EdgeRecord {
1972                id,
1973                curve: edge.curve.clone(),
1974                t0: edge.t0,
1975                t1: edge.t1,
1976                start_vertex_id: vertex_ids.get(&edge.start_vertex_id).copied().unwrap_or(edge.start_vertex_id),
1977                end_vertex_id: vertex_ids.get(&edge.end_vertex_id).copied().unwrap_or(edge.end_vertex_id),
1978                degenerate: edge.degenerate,
1979                name: edge.name.clone(),
1980            });
1981        }
1982        for shell in &operand.shells {
1983            let mut faces = Vec::with_capacity(shell.faces.len());
1984            for face in &shell.faces {
1985                let loops = face
1986                    .loops
1987                    .iter()
1988                    .map(|loop_record| {
1989                        let coedges = loop_record
1990                            .coedges
1991                            .iter()
1992                            .map(|coedge| {
1993                                let id = next_coedge_id;
1994                                next_coedge_id += 1;
1995                                CoedgeRecord {
1996                                    id,
1997                                    edge_id: edge_ids.get(&coedge.edge_id).copied().unwrap_or(coedge.edge_id),
1998                                    forward: coedge.forward,
1999                                    pcurve: coedge.pcurve.clone(),
2000                                }
2001                            })
2002                            .collect();
2003                        let id = next_loop_id;
2004                        next_loop_id += 1;
2005                        LoopRecord { id, coedges }
2006                    })
2007                    .collect();
2008                let name = face.name.clone().map(|base| {
2009                    if used_face_names.insert(base.clone()) {
2010                        base
2011                    } else {
2012                        let mut suffix = 1usize;
2013                        loop {
2014                            let candidate = format!("{base}_{suffix}");
2015                            if used_face_names.insert(candidate.clone()) {
2016                                break candidate;
2017                            }
2018                            suffix += 1;
2019                        }
2020                    }
2021                });
2022                faces.push(FaceRecord {
2023                    id: next_face_id,
2024                    surface: face.surface.clone(),
2025                    same_sense: face.same_sense,
2026                    loops,
2027                    name,
2028                });
2029                next_face_id += 1;
2030            }
2031            shells.push(ShellRecord { id: shells.len() as u64 + 1, faces });
2032        }
2033    }
2034    BrepSolid {
2035        mass_properties_cache: Default::default(),
2036        id: 1,
2037        vertices,
2038        edges,
2039        shells,
2040        genus: first.genus + second.genus,
2041        kind: crate::BodyKind::Solid,
2042    }
2043}
2044
2045/// The binary boolean. The second value is the soundness scan's candidate
2046/// faces under pass-through (`None` reads the whole result).
2047fn boolean_pipeline(
2048    first: &BrepSolid,
2049    second: &BrepSolid,
2050    operation: BooleanOperation,
2051    options: &BooleanOptions,
2052    tangent_nodes: &mut Vec<Vec3>,
2053) -> Result<(KernelOutcome<BrepSolid>, Option<HashSet<u64>>), KernelRefusal> {
2054    let replaced_operands = operand_state::replace_operands(first, second, operation, options);
2055    let (first, second) = match &replaced_operands {
2056        Some((first, second)) => (first, second),
2057        None => (first, second),
2058    };
2059    let policy = boolean_policy(first, second, options)?;
2060    let tolerance = policy.model;
2061    // DISJOINT FAST PATH. A box test on the control-net hulls, not "no
2062    // section evidence": a tool inside the other operand also leaves no
2063    // evidence and must stay on the pipeline, where a union is the outer
2064    // operand, a subtract the outer with a void shell and an intersect the
2065    // tool. Operands clear of each other by more than the search band can
2066    // touch nothing, so nothing is prepared, imprinted, assembled or
2067    // scanned: a union is the two operands side by side, a subtract the
2068    // first operand, an intersect the legitimate-empty result below. The
2069    // soundness scan is skipped as well (an empty candidate set): the
2070    // operands cannot cross each other, and a crossing an operand carried in
2071    // passes through the full acceptance unrepaired exactly the same.
2072    if disjoint_fast_path(options)
2073        && !operand_bounds(first).intersects(operand_bounds(second), policy.sew_search)
2074    {
2075        let operation_started = Instant::now();
2076        let mut diagnostics = KernelDiagnostics::default();
2077        diagnostics.count_n(
2078            "collect.faces",
2079            first.shells.iter().chain(&second.shells).map(|shell| shell.faces.len() as u64).sum(),
2080        );
2081        diagnostics.count("boolean.disjoint_fast_path");
2082        diagnostics.measure_max("tolerance.model", policy.model);
2083        diagnostics.measure_max("tolerance.sew_search", policy.sew_search);
2084        let value = match operation {
2085            BooleanOperation::Union => concatenate_disjoint(first, second),
2086            BooleanOperation::Subtract => first.clone(),
2087            BooleanOperation::Intersect => {
2088                diagnostics.event(
2089                    DiagnosticSeverity::Info,
2090                    KernelStage::Select,
2091                    "boolean.empty_result",
2092                    format!("{operation:?} of witnessed-non-overlapping operands is empty"),
2093                );
2094                empty_solid()
2095            }
2096        };
2097        let stage_ms = operation_started.elapsed().as_secs_f64() * 1_000.0;
2098        profile_stage("disjoint_fast_path_ms", stage_ms);
2099        diagnostics.measure_max("timing.total_ms", stage_ms);
2100        return Ok((KernelOutcome { value, diagnostics }, Some(HashSet::default())));
2101    }
2102    let heal_started = Instant::now();
2103    let (first_owned, second_owned) = prepared_operands(first, second, &policy)?;
2104    let heal_ms = heal_started.elapsed().as_secs_f64() * 1_000.0;
2105    let first = &first_owned;
2106    let second = &second_owned;
2107
2108    let mut diagnostics = KernelDiagnostics::default();
2109    // Operand preparation ran before `timing.total_ms` opens and was absent
2110    // from every phase split; it is 1.6 of a disjoint box union's 7.2 ms.
2111    diagnostics.measure_max("timing.heal_ms", heal_ms);
2112    // Every pcurve fit made while this scope is open — the imprint's sections,
2113    // the assembler's rebuilt trims, the merge's re-fits — is tallied, so the
2114    // outcome can say whether every curved trim reached its floor instead of
2115    // letting a sample-ceiling exit pass for a met one (`pcurve.unmet_floor`).
2116    let fit_scope = crate::PcurveFitScope::open();
2117    // And every POLYLINE fit: a marched section's interpolant is measured
2118    // against the tolerance it was given, so `fit.polyline.unmet_tolerance`
2119    // reports a section that could not reach it (`geometry/fit.rs`).
2120    let polyline_scope = crate::PolylineFitScope::open();
2121    let operation_started = Instant::now();
2122    diagnostics.count_n(
2123        "collect.faces",
2124        first
2125            .shells
2126            .iter()
2127            .chain(&second.shells)
2128            .map(|shell| shell.faces.len() as u64)
2129            .sum(),
2130    );
2131    diagnostics.measure_max("tolerance.model", policy.model);
2132    diagnostics.measure_max("tolerance.sew_search", policy.sew_search);
2133
2134    let imprint_options = boolean_imprint_options(options, tolerance);
2135    let stage_started = Instant::now();
2136    let imprint = build_imprints(first, second, &imprint_options)?;
2137    tangent_nodes.extend_from_slice(&imprint.tangent_nodes);
2138    if std::env::var("BREP_DEBUG_BOOL").is_ok() {
2139        for piece in &imprint.pieces {
2140            let start = piece.curve.evaluate(piece.t0);
2141            let end = piece.curve.evaluate(piece.t1);
2142            eprintln!(
2143                "piece {} supports={:?} t=[{:.6},{:.6}] start={:?} end={:?}",
2144                piece.id, piece.support_faces, piece.t0, piece.t1, start, end
2145            );
2146        }
2147        for entry in &imprint.by_face {
2148            eprintln!(
2149                "by_face operand={} face={} pieces={:?}",
2150                entry.operand, entry.face_id, entry.piece_ids
2151            );
2152        }
2153        for split in &imprint.edge_splits {
2154            eprintln!(
2155                "edge_split operand={} edge={} params={:?}",
2156                split.operand, split.edge_id, split.parameters
2157            );
2158        }
2159    }
2160    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
2161    profile_stage("imprint_ms", stage_ms);
2162    diagnostics.measure_max("timing.imprint_ms", stage_ms);
2163    diagnostics.count_n("intersect.pieces", imprint.pieces.len() as u64);
2164    diagnostics.count_n("intersect.vertices", imprint.vertices.len() as u64);
2165    diagnostics.count_n("intersect.edge_splits", imprint.edge_splits.len() as u64);
2166    let (march_swap_rescues, clip_swap_rescues) = crate::imprint::take_rescue_firings();
2167    diagnostics.count_n("intersect.march_swap_rescue", march_swap_rescues);
2168    diagnostics.count_n("intersect.clip_swap_rescue", clip_swap_rescues);
2169    let touch = TouchCensus::build([first, second], &imprint);
2170    touch.report(&mut diagnostics);
2171    let stage_started = Instant::now();
2172    let (split_first, split_map_first) = apply_edge_splits_with_map(first, 0, &imprint)?;
2173    let (split_second, split_map_second) = apply_edge_splits_with_map(second, 1, &imprint)?;
2174    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
2175    profile_stage("edge_split_ms", stage_ms);
2176    diagnostics.measure_max("timing.edge_split_ms", stage_ms);
2177    let stage_started = Instant::now();
2178    crate::classification::classify_profile_begin();
2179    let fragments_a = fragment_solid(&split_first, 0, &imprint)?;
2180    let fragments_b = fragment_solid(&split_second, 1, &imprint)?;
2181    crate::classification::classify_profile_report("fragment");
2182    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
2183    profile_stage("fragment_ms", stage_ms);
2184    diagnostics.measure_max("timing.fragment_ms", stage_ms);
2185    diagnostics.count_n(
2186        "fragment.candidates",
2187        (fragments_a.len() + fragments_b.len()) as u64,
2188    );
2189    touch.report_fragments(&[&fragments_a, &fragments_b], &mut diagnostics);
2190    // Operand surface samples for the LEGITIMATE-EMPTY adjudication below:
2191    // every fragment test point is a 3D point ON its operand's boundary and
2192    // inside its face trim, so they witness where the operands' materials sit
2193    // relative to each other without extra geometry work.
2194    let a_surface_samples: Vec<Vec3> = fragments_a
2195        .iter()
2196        .flat_map(|fragment| {
2197            std::iter::once(fragment.test_point).chain(fragment.extra_test_points.iter().copied())
2198        })
2199        .collect();
2200    let b_surface_samples: Vec<Vec3> = fragments_b
2201        .iter()
2202        .flat_map(|fragment| {
2203            std::iter::once(fragment.test_point).chain(fragment.extra_test_points.iter().copied())
2204        })
2205        .collect();
2206    let stage_started = Instant::now();
2207    let mut barrier_edges: HashSet<(u8, u64)> = imprint
2208        .pieces
2209        .iter()
2210        .filter_map(|piece| piece.shared_edge.map(|(operand, edge_id, _)| (operand, edge_id)))
2211        .collect();
2212    barrier_edges.extend(imprint.barrier_edges.iter().copied());
2213    // The barrier set is keyed on ORIGINAL edge ids, but a barrier edge the
2214    // imprint also SPLIT (rotated equator-tangency: the overlapped cap ring
2215    // gains a junction at the sphere-seam crossing) reaches fragment
2216    // selection as its minted sub-edge ids. Remap the barrier through the
2217    // split ledger — keeping the originals too for unsplit references.
2218    for (operand, split_map) in [(0u8, &split_map_first), (1u8, &split_map_second)] {
2219        let minted: Vec<(u8, u64)> = barrier_edges
2220            .iter()
2221            .filter(|(barrier_operand, _)| *barrier_operand == operand)
2222            .filter_map(|(_, edge_id)| split_map.get(edge_id))
2223            .flat_map(|sub_ids| sub_ids.iter().map(|sub_id| (operand, *sub_id)))
2224            .collect();
2225        barrier_edges.extend(minted);
2226    }
2227    crate::classification::classify_profile_begin();
2228    let coincident = coincident_pairs(
2229        &fragments_a,
2230        &fragments_b,
2231        first,
2232        second,
2233        &imprint.cosurface_pairs,
2234        tolerance,
2235    )?;
2236    let sector_mode = sector::sector_mode(options);
2237    let mut select_census = sector::SelectCensus::default();
2238    let mut selected = select_fragments(
2239        fragments_a,
2240        fragments_b,
2241        first,
2242        second,
2243        operation,
2244        tolerance,
2245        &barrier_edges,
2246        &imprint,
2247        &coincident,
2248        sector_mode,
2249        &mut select_census,
2250    )?;
2251    let resewn = resew_coincident_rims(
2252        &mut selected,
2253        [&split_first, &split_second],
2254        [first, second],
2255        &imprint.cosurface_pairs,
2256        operation,
2257        tolerance,
2258    )?;
2259    diagnostics.count_n("select.coincident_pairs", coincident.len() as u64);
2260    diagnostics.count_n("select.rims_resewn", resewn as u64);
2261    crate::classification::classify_profile_report("select");
2262    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
2263    profile_stage("select_ms", stage_ms);
2264    diagnostics.measure_max("timing.select_ms", stage_ms);
2265    diagnostics.count_n("select.fragments", selected.len() as u64);
2266    // One census line per selection as well.
2267    diagnostics.count_n("select.sector_eligible", select_census.eligible);
2268    diagnostics.count_n("select.sector_primary", select_census.sector_primary);
2269    diagnostics.count_n("select.sector_fallback", select_census.sector_fallback);
2270    diagnostics.count_n("select.sector_disagree", select_census.disagree);
2271    diagnostics.count_n("select.ray_casts", select_census.ray_casts);
2272    diagnostics.count_n("select.on_votes", select_census.on_votes);
2273    diagnostics.count_n("select.overturns", select_census.overturns);
2274    if sector_mode != sector::SectorMode::Off || std::env::var_os("BREP_SECTOR_CENSUS").is_some() {
2275        sector::sector_census_line(|| {
2276            format!(
2277                "selection mode={sector_mode:?} select_ms={stage_ms:.3} selected={} eligible={} primary={} fallback={} disagree={} ray_casts={} on_votes={} overturns={}",
2278                selected.len(),
2279                select_census.eligible,
2280                select_census.sector_primary,
2281                select_census.sector_fallback,
2282                select_census.disagree,
2283                select_census.ray_casts,
2284                select_census.on_votes,
2285                select_census.overturns
2286            )
2287        });
2288    }
2289    // LEGITIMATE-EMPTY RESULTS: an intersect of DISJOINT operands (or a
2290    // subtract whose left operand is entirely CONSUMED by the right) selects
2291    // zero fragments, and assembly used to refuse with "operation produced no
2292    // boundary faces" — 384 of the 474 post-wrapped-band pool errors were
2293    // this, not bugs. Empty is only blessed on TWO independent proofs:
2294    //   (a) the imprint recorded NO section evidence (no accepted pierce
2295    //       seed, no traced SSI branch, no minted piece) — a silently-lost
2296    //       section (the trial-489 class) always leaves upstream evidence
2297    //       even when every downstream sub-segment is clipped away, while
2298    //       pure material disjointness leaves none (verified: 489's
2299    //       recreated silent-miss state keeps erroring; per-face surface
2300    //       samples ALONE missed its 235 mm³ pocket, which is why (a) is
2301    //       required and sampling alone was rejected);
2302    //   (b) the operands' surface samples agree (every fragment test point
2303    //       classified against the other solid):
2304    //       intersect — no sample of either operand strictly inside the
2305    //       other; subtract — no left-operand sample strictly outside the
2306    //       right.
2307    // Contact/graze pairs (evidence exists, material still disjoint) stay
2308    // errors — conservative by design. Escape hatch: BREP_EMPTY_BOOLEAN=0
2309    // restores the unconditional error.
2310    if selected.is_empty()
2311        && !imprint.section_evidence
2312        && std::env::var("BREP_EMPTY_BOOLEAN").as_deref() != Ok("0")
2313    {
2314        let strictly_inside = |samples: &[Vec3], other: &BrepSolid| -> Result<bool, KernelRefusal> {
2315            for &sample in samples {
2316                if classify_point(sample, other, tolerance).or_refuse(KernelStage::Validate, "classify_point")?.class == PointClass::In {
2317                    return Ok(true);
2318                }
2319            }
2320            Ok(false)
2321        };
2322        let outside_witness = |samples: &[Vec3], other: &BrepSolid| -> Result<bool, KernelRefusal> {
2323            for &sample in samples {
2324                if classify_point(sample, other, tolerance).or_refuse(KernelStage::Validate, "classify_point")?.class == PointClass::Out {
2325                    return Ok(true);
2326                }
2327            }
2328            Ok(false)
2329        };
2330        let legitimate = match operation {
2331            BooleanOperation::Intersect => {
2332                !strictly_inside(&a_surface_samples, second)?
2333                    && !strictly_inside(&b_surface_samples, first)?
2334            }
2335            BooleanOperation::Subtract => !outside_witness(&a_surface_samples, second)?,
2336            BooleanOperation::Union => false,
2337        };
2338        if legitimate {
2339            diagnostics.event(
2340                DiagnosticSeverity::Info,
2341                KernelStage::Select,
2342                "boolean.empty_result",
2343                format!("{operation:?} of witnessed-non-overlapping operands is empty"),
2344            );
2345            fit_scope.close().report_into(&mut diagnostics);
2346            polyline_scope.close().report_into(&mut diagnostics);
2347            diagnostics.measure_max(
2348                "timing.total_ms",
2349                operation_started.elapsed().as_secs_f64() * 1_000.0,
2350            );
2351            return Ok((
2352                KernelOutcome {
2353                    value: empty_solid(),
2354                    diagnostics,
2355                },
2356                None,
2357            ));
2358        }
2359    }
2360    let solids = [(0, &split_first), (1, &split_second)]
2361        .into_iter()
2362        .collect::<HashMap<_, _>>();
2363    let dump = BooleanStageDump::open();
2364    if let Some(dump) = &dump {
2365        dump.write("first", &split_first);
2366        dump.write("second", &split_second);
2367        dump.write("selected", &selected);
2368    }
2369    let stage_started = Instant::now();
2370    // Result face `k` is minted from `selected[k - 1]` (one face per fragment,
2371    // ids from 1 in order): the map the tangent-section attribution reads a
2372    // refusal's face ids through.
2373    let assembled_origins: Vec<FaceKey> = if imprint.tangent_sections.is_empty() {
2374        Vec::new()
2375    } else {
2376        selected
2377            .iter()
2378            .map(|fragment| FaceKey { operand: fragment.operand, face_id: fragment.source_face_id })
2379            .collect()
2380    };
2381    let attribute = |error: KernelRefusal| {
2382        attribute_to_tangent_section(error, &imprint.tangent_sections, &assembled_origins)
2383    };
2384    let (solid, scope) = if untouched_passthrough(options) {
2385        assemble_fragments_scoped(selected, &solids, &imprint, tolerance, &touch, touched_assembly(options))
2386            .map_err(attribute)?
2387    } else {
2388        (assemble_fragments(selected, &solids, &imprint, tolerance).map_err(attribute)?, None)
2389    };
2390    if let Some(scope) = &scope {
2391        diagnostics.count_n("passthrough.faces_examined", scope.examined.len() as u64);
2392    }
2393    if let Some(dump) = &dump {
2394        dump.write("assembled", &solid);
2395    }
2396    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
2397    profile_stage("assemble_ms", stage_ms);
2398    diagnostics.measure_max("timing.assemble_ms", stage_ms);
2399    let stage_started = Instant::now();
2400    if std::env::var("BREP_DEBUG_PREMERGE_VALIDATE").is_ok() {
2401        let issues = solid.validate();
2402        eprintln!(
2403            "pre-merge validation: {} issue(s){}",
2404            issues.len(),
2405            issues
2406                .first()
2407                .map(|issue| format!(" — first: {}", issue.message))
2408                .unwrap_or_default()
2409        );
2410    }
2411    let solid = if options.merge_coplanar_faces
2412        && std::env::var("BREP_COPLANAR_MERGE").as_deref() != Ok("0")
2413    {
2414        let merged = match &scope {
2415            // Every pair merges as before (an operand's own coplanar
2416            // neighbours included); the closing validation reads the
2417            // examined faces and the merged ones.
2418            Some(scope) => merge_same_surface_faces_scoped(
2419                &solid,
2420                tolerance,
2421                &options.keep_unmerged_name_substrs,
2422                &scope.examined,
2423            )?,
2424            None => merge_same_surface_faces_excluding(
2425                &solid,
2426                tolerance,
2427                &options.keep_unmerged_name_substrs,
2428            )?,
2429        };
2430        // Face merging can make previously separate collinear boundary
2431        // segments incident to the same pair of faces. Run continuation
2432        // cleanup again, matching the post-merge normalization performed by
2433        // the former assembly pipeline.
2434        merge_curve_continuation_edges(&merged, tolerance).map_err(|refusal| KernelRefusal { stage: KernelStage::Validate, ..refusal })?
2435    } else {
2436        solid
2437    };
2438    if let Some(dump) = &dump {
2439        dump.write("merged", &solid);
2440    }
2441    let stage_ms = stage_started.elapsed().as_secs_f64() * 1_000.0;
2442    profile_stage("face_merge_ms", stage_ms);
2443    diagnostics.measure_max("timing.face_merge_ms", stage_ms);
2444    let validation = solid.validate_detailed_scoped(&policy, scope.as_ref().map(|scope| &scope.examined));
2445    diagnostics.count_n("validate.issues", validation.issues.len() as u64);
2446    diagnostics.count_n(
2447        "validate.wire_warnings",
2448        validation.wire_warnings.len() as u64,
2449    );
2450    diagnostics.measure_max("validate.max_pcurve_error", validation.max_pcurve_error);
2451    for warning in &validation.wire_warnings {
2452        diagnostics.event(
2453            DiagnosticSeverity::Warning,
2454            KernelStage::Validate,
2455            "validate.uv_wire",
2456            warning.message.clone(),
2457        );
2458    }
2459    for issue in &validation.issues {
2460        diagnostics.event(
2461            DiagnosticSeverity::Error,
2462            KernelStage::Validate,
2463            "validate.brep",
2464            issue.message.clone(),
2465        );
2466    }
2467    fit_scope.close().report_into(&mut diagnostics);
2468    polyline_scope.close().report_into(&mut diagnostics);
2469    diagnostics.measure_max(
2470        "timing.total_ms",
2471        operation_started.elapsed().as_secs_f64() * 1_000.0,
2472    );
2473    if !validation.issues.is_empty() {
2474        return Err(KernelRefusal::new(
2475            RefusalClass::InvalidResultTopology { issues: validation.issues.len() as u32 },
2476            KernelStage::Validate,
2477            format!(
2478            "boolean_operation: invalid result: {:?}",
2479            validation.issues
2480        )));
2481    }
2482    // Optional semantic oracle (off by default, no perf hit): under
2483    // BREP_DEBUG_BOOL, cross-check the well-formed result against the CSG
2484    // point-membership expectation and scan for residual coincident-but-unwelded
2485    // geometry. Purely diagnostic and NON-rejecting — a statistical check must
2486    // never fail a valid op (see oracle.rs) — so it only ever warns.
2487    if std::env::var("BREP_DEBUG_BOOL").is_ok() {
2488        if let Ok(report) =
2489            crate::oracle::boolean_semantic_disagreement(first, second, operation, &solid, 200)
2490        {
2491            if report.is_flagged() {
2492                eprintln!(
2493                    "[oracle] semantic disagreement rate {:.4} ({} of {} decidable points); sample {:?}",
2494                    report.disagreement_rate,
2495                    report.disagreements.len(),
2496                    report.considered,
2497                    report.sample_disagreement()
2498                );
2499            }
2500        }
2501        let fusable_band = crate::tolerance::assembler_weld(policy.model);
2502        let fusables = crate::oracle::boolean_residual_fusables(&solid, fusable_band);
2503        if !fusables.is_empty() {
2504            eprintln!(
2505                "[oracle] {} residual fusable(s) after weld; sample {:?}",
2506                fusables.len(),
2507                fusables.first()
2508            );
2509        }
2510    }
2511    let candidates = scope.as_ref().map(|scope| {
2512        let candidates = soundness_candidates(
2513            &solid,
2514            scope,
2515            [operand_bounds(first), operand_bounds(second)],
2516            policy.sew_search,
2517        );
2518        diagnostics.count_n("passthrough.soundness_candidates", candidates.len() as u64);
2519        candidates
2520    });
2521    Ok((
2522        KernelOutcome {
2523            value: solid,
2524            diagnostics,
2525        },
2526        candidates,
2527    ))
2528}
2529