Skip to main content

brep_kernel/csg/boolean/
mod.rs

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