Skip to main content

brep_kernel/csg/imprint/
driver.rs

1use crate::{KernelRefusal, KernelStage, OrRefuse, RefusalClass, MAX_FIT_STATIONS};
2
3/// The degree a marched section is interpolated at (2026-09-27). The march
4/// puts every station on both carriers; the fit decides how far the curve
5/// strays BETWEEN them, and a cubic's O(h^4) there left `BadBoolean`'s
6/// sphere×bore rim 2.5e-7 off the bore with a mean bias of −1.2e-8 that both
7/// trims inherited (+1.14e-6 on the solid). A quintic through the same
8/// stations reads 8.5e-9 and +6e-11 with the same knots and control-point
9/// count. More stations bought the same accuracy on 2026-09-26 and were
10/// reverted for their cost: the watertight tessellator samples every interior
11/// knot, so every scan downstream scales with station count, and a degree does
12/// not add knots. `BREP_SECTION_FIT_DEGREE=3` restores the cubic. The gate on
13/// the mid-span refinement, as a multiple of the fit tolerance (2026-09-27): a
14/// marched section whose unrefined fit misses a carrier between stations by
15/// more than this is UNDERSAMPLED — the 20° cylinder crossing of
16/// `fillet-skew-cylinder-seam-split-20deg` sits 4e-5..8e-5 off, 470 times the
17/// tolerance, at any degree — and only such a section is given true
18/// intersection points until its mid-spans hold. Refining every section cost
19/// more downstream than it returned (the tessellator samples every knot).
20/// `BREP_SECTION_REFINE_GATE` sets the multiple (`on` for
21/// [`SECTION_REFINE_GATE`]). OFF BY DEFAULT: at k = 100 the sequential
22/// blend/direct_edit/imprint/boolean lib set costs +64 % and
23/// `oblique_multi_rim_cone_cap_tearing_push_refuses` goes red; k = 1000 costs
24/// +32 % and leaves the skew union +5.3e-4 off. This global switch stays off.
25/// Independently, a pair of grazing crossings on one trim edge requests a
26/// fit-floor check for that face pair below: those two nearby junctions and
27/// the intervening cap must survive the fit.
28fn section_refine_gate() -> Option<f64> {
29    static GATE: std::sync::OnceLock<Option<f64>> = std::sync::OnceLock::new();
30    *GATE.get_or_init(|| match std::env::var("BREP_SECTION_REFINE_GATE") {
31        Ok(value) if value == "on" => Some(SECTION_REFINE_GATE),
32        Ok(value) => value.parse::<f64>().ok().filter(|gate| *gate >= 0.0),
33        Err(_) => None,
34    })
35}
36
37/// Why a section goes to [`refine_section_against_carriers`].
38#[derive(Clone, Copy, Debug, PartialEq)]
39pub(super) enum SectionRefineRoute {
40    /// `BREP_SECTION_REFINE_GATE`: every section, at the variable's multiple.
41    Global,
42    /// A recovered fit reproduces its march points; check it between them.
43    Recovered,
44    /// A pair of grazing crossings on one trim edge.
45    Grazing,
46    /// The standing fit missed the tolerance it was asked for. The polyline
47    /// deviation can overstate the carrier miss (a band-raised floor reads the
48    /// input's noise), so the refiner's own carrier measure decides: a section
49    /// whose mid-spans already hold the gate leaves at round 0 as `BelowGate`,
50    /// unchanged. Measured 2026-10-04: the StationCeiling fits it reaches ship
51    /// 4.7e-5 to 2.1e-4 off the true section; the refiner brings them to
52    /// 1e-8..3e-8.
53    UnmetRequest,
54    /// A standing fit that MET its request on no other route, whose kept
55    /// stations all lie on both carriers within the fit tolerance, but whose
56    /// curve stands further off the carriers BETWEEN them than the local gate
57    /// ([`carrier_miss_route`]). The polyline it met is the march's chords; an
58    /// interpolant through consistent stations can still ring between them.
59    /// Measured 2026-10-04 (08356963b, 94 cases): 23 of 285 fast-path sections,
60    /// 13 distinct pairs, read their mid-spans up to 6.03e-6 off while every
61    /// kept station read within 1e-7; fixture 28 t217 pair 118x474 read 1.59e-6
62    /// between stations 1.8e-9 off.
63    CarrierMiss,
64}
65
66/// The carrier-miss route for a fit that met its request on no other route:
67/// refine at the local gate when its mid-spans miss the carriers by more than
68/// the fit tolerance WHILE every kept station is within it. Stations off a
69/// carrier are the input's (moving them is the reserved relocation ruling),
70/// so such a section keeps the fast path and is not reported as refined. An
71/// unreadable reading (NaN) never routes.
72pub(super) fn carrier_miss_route(station_upper: f64, midspan_upper: f64, fit_tolerance: f64) -> Option<(f64, SectionRefineRoute)> {
73    (station_upper.is_finite() && midspan_upper.is_finite() && station_upper <= fit_tolerance && midspan_upper > fit_tolerance)
74        .then_some((1.0, SectionRefineRoute::CarrierMiss))
75}
76
77/// The carrier readings [`carrier_miss_route`] decides on: the worst of both
78/// carriers over EVERY point of the section's run (the march's original
79/// stations, kept or dropped by the fit, so a fit through consistent kept
80/// stations cannot hide an inconsistent dropped one), and at the fit's
81/// mid-spans. Projector distances, so upper readings; NaN as soon as any is
82/// unreadable. The mid-span read is the route's TRIGGER, not a bound on the
83/// curve's maximum; [`knot_interior_carrier_upper`] is the finer read.
84pub(super) fn fast_path_carrier_readings(fit: &PolylineFit, run: &[Vec3], first: &NurbsSurface, second: &NurbsSurface) -> (f64, f64) {
85    let read = |surface: &NurbsSurface, point: Vec3| project_point_to_surface(surface, point).map_or(f64::NAN, |projection| projection.distance);
86    let mut station = 0.0f64;
87    for point in run {
88        let (a, b) = (read(first, *point), read(second, *point));
89        station = if a.is_nan() || b.is_nan() || station.is_nan() { f64::NAN } else { station.max(a).max(b) };
90    }
91    (station, midspan_carrier_upper(fit, first, second))
92}
93
94/// The worst of BOTH carriers at the fit's mid-spans, each carrier read on its
95/// own and checked finite BEFORE any max (`f64::max` would drop a NaN from one
96/// carrier and pass the other's reading): NaN as soon as either is unreadable.
97pub(super) fn midspan_carrier_upper(fit: &PolylineFit, first: &NurbsSurface, second: &NurbsSurface) -> f64 {
98    let read = |surface: &NurbsSurface, point: Vec3| project_point_to_surface(surface, point).map_or(f64::NAN, |projection| projection.distance);
99    let mut worst = 0.0f64;
100    for span in 0..fit.parameters.len().saturating_sub(1) {
101        let mid = 0.5 * (fit.parameters[span] + fit.parameters[span + 1]);
102        let (a, b) = match fit.curve.evaluate(mid) {
103            Ok(point) => (read(first, point), read(second, point)),
104            Err(_) => (f64::NAN, f64::NAN),
105        };
106        if !(a.is_finite() && b.is_finite()) || worst.is_nan() {
107            return f64::NAN;
108        }
109        worst = worst.max(a).max(b);
110    }
111    worst
112}
113
114/// The curve's worst distance from both carriers at the quarter points of
115/// every one of its own knot spans (its native pieces, where an interpolant
116/// is extremal between knots), NaN as soon as any read is unreadable. Sampled
117/// and projector-based: an upper reading at its points, not a certified
118/// maximum of the curve.
119pub(super) fn knot_interior_carrier_upper(curve: &NurbsCurve, first: &NurbsSurface, second: &NurbsSurface) -> f64 {
120    let read = |surface: &NurbsSurface, point: Vec3| project_point_to_surface(surface, point).map_or(f64::NAN, |projection| projection.distance);
121    let Ok([t0, t1]) = curve.domain() else { return f64::NAN };
122    let mut knots: Vec<f64> = curve.knots.iter().copied().filter(|knot| *knot >= t0 && *knot <= t1).collect();
123    knots.dedup();
124    let mut worst = 0.0f64;
125    for pair in knots.windows(2) {
126        if pair[1] <= pair[0] {
127            continue;
128        }
129        for fraction in [0.25, 0.5, 0.75] {
130            let value = match curve.evaluate(pair[0] + (pair[1] - pair[0]) * fraction) {
131                Ok(point) => {
132                    let (a, b) = (read(first, point), read(second, point));
133                    if a.is_finite() && b.is_finite() { a.max(b) } else { f64::NAN }
134                }
135                Err(_) => f64::NAN,
136            };
137            worst = if worst.is_nan() || !value.is_finite() { f64::NAN } else { worst.max(value) };
138        }
139    }
140    worst
141}
142
143/// The refine route and its gate (a multiple of the fit tolerance) for one
144/// section, or `None` for the fast path.
145///
146/// A standing fit that MET its request takes exactly the routes that existed
147/// before: the global gate alone when set, else a recovered fit, else a grazing
148/// pair (read lazily, only when the first two do not apply), else nothing — it
149/// goes straight to `process_curve`, untouched.
150///
151/// A fit that MISSED its request is always refined, at the local gate 1, or at
152/// a tighter global gate. A looser global gate does not mask the miss: seam 60
153/// was a recovered fit missing its 1e-7 request, and `BREP_SECTION_REFINE_GATE`
154/// above 1 used to let it through. Its route stays `UnmetRequest` under any
155/// global gate, so its cost reads apart from the global gate's.
156pub(super) fn section_refine_route(
157    global: Option<f64>,
158    recovered: bool,
159    grazing: impl FnOnce() -> bool,
160    met_requested: bool,
161) -> Option<(f64, SectionRefineRoute)> {
162    if !met_requested {
163        return Some((global.map_or(1.0, |gate| gate.min(1.0)), SectionRefineRoute::UnmetRequest));
164    }
165    global
166        .map(|gate| (gate, SectionRefineRoute::Global))
167        .or_else(|| recovered.then_some((1.0, SectionRefineRoute::Recovered)))
168        .or_else(|| grazing().then_some((1.0, SectionRefineRoute::Grazing)))
169}
170
171/// The multiple `BREP_SECTION_REFINE_GATE=on` uses: the smallest measured
172/// that trips the skew union's undersampled sections and leaves fixture 22 as
173/// it was (k = 10 flips it).
174const SECTION_REFINE_GATE: f64 = 100.0;
175
176/// A clipped run's END span can be a sliver: the clip endpoint lands beside a
177/// march station, a spacing ratio to the run's median of 0.011 on
178/// `anotherBooleanFail`, and a global interpolant rings next to such a jump —
179/// the mid-spans near the ends miss their carriers by 1e-3, and bisecting them
180/// only makes the spacing more uneven (2026-09-27). The reduced witness
181/// (`imprint/tests.rs` `sliver_end_evening`) says what makes the jump ring: a
182/// sliver end ON both carriers fits to 3e-8, while the same end 2.7e-4 off
183/// the torus — the vendor's edge curves sit that far off the tori they
184/// bound, and the run's end is solved on such an edge — rings at 2e-3 with
185/// the interior at 3e-13 (2026-09-30). Below
186/// `SLIVER_END_FRACTION` of the median spacing the station beside the
187/// endpoint is RE-SOLVED at the middle of the merged gap as a fresh
188/// intersection point (the default, `BREP_SECTION_SLIVER_END=resolve`) or
189/// dropped (`drop`); `off` restores the unevened fit for a before/after
190/// reading. Returns how many ends were evened.
191///
192/// ON BY DEFAULT since 2026-09-30 (lane K6), behind the measurement in
193/// [`even_sliver_ends_measured`]: only a section whose fit shows the ringing signature
194/// ([`ringing_signature`]) is evened, and the evened fit stands only where it reads closer to
195/// the carriers by [`SLIVER_END_GAIN`]. The ringing is what a marched section carries into the
196/// result: on `anotherBooleanFail` the sphere sections leave the analytic vendor torus by
197/// 3.1e-3 within 1% of their span (measured against the STEP file's own `TOROIDAL_SURFACE`,
198/// not the projector), the cut torus faces depart from their edges by 2.6e-4 and the result
199/// shell's vector-area closure reads 1.88e-4 against a 3.83e-5 bar. Evened, the sections sit
200/// 5e-8 off, the shell closes at 1.7e-6 and the boolean takes 2.3 s instead of 9.1 s. The
201/// 2026-09-27 shelving reason was three fixture-25 exact-value pins moving under a blanket
202/// `resolve`; a blanket default also refused fixture 28's reversed subtract and broke fixture
203/// 26's idempotence, which is why the signature gates it. Fixture 25's sections do not show
204/// the signature at the shipped contrast and its pins stand.
205fn even_sliver_ends(
206    section: &mut Vec<Vec3>,
207    first: &NurbsSurface,
208    second: &NurbsSurface,
209    tolerance: f64,
210) -> Result<usize, String> {
211    static MODE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
212    let mode = MODE.get_or_init(|| {
213        std::env::var("BREP_SECTION_SLIVER_END").unwrap_or_else(|_| "resolve".to_string())
214    });
215    if !(mode == "drop" || mode == "resolve") {
216        return Ok(0);
217    }
218    let mut evened = 0;
219    // One sliver at a time: each removal changes the spans it sits between.
220    for _ in 0..SLIVER_END_ROUNDS {
221        let count = section.len();
222        if count < 6 {
223            break;
224        }
225        let mut chords: Vec<f64> = section.windows(2).map(|pair| pair[1].sub(pair[0]).length()).collect();
226        let lengths = chords.clone();
227        chords.sort_by(f64::total_cmp);
228        let median = chords[chords.len() / 2];
229        let spans = lengths.len();
230        let near_ends = [0, 1, 2, spans - 3, spans - 2, spans - 1];
231        let Some(&sliver) = near_ends
232            .iter()
233            .filter(|&&span| lengths[span] < sliver_end_fraction() * median)
234            .min_by(|a, b| lengths[**a].total_cmp(&lengths[**b]))
235        else {
236            break;
237        };
238        // The sliver's bounding stations are `sliver` and `sliver + 1`; a run
239        // endpoint is never moved. Of the interior ones, remove the one whose
240        // removal leaves the shorter merged span.
241        let mut candidates = Vec::new();
242        for station in [sliver, sliver + 1] {
243            if station == 0 || station == count - 1 {
244                continue;
245            }
246            let merged = section[station + 1].sub(section[station - 1]).length();
247            candidates.push((station, merged));
248        }
249        let Some(&(station, _)) = candidates.iter().min_by(|a, b| a.1.total_cmp(&b.1)) else {
250            break;
251        };
252        if mode == "resolve" {
253            let (before, after) = (section[station - 1], section[station + 1]);
254            let seed = before.add(after).scale(0.5);
255            let reach = 0.5 * after.sub(before).length();
256            match crate::surface_surface_intersection::refine_to_intersection(first, second, seed, tolerance, reach)? {
257                Some(point) => section[station] = point,
258                None => {
259                    section.remove(station);
260                }
261            }
262        } else {
263            section.remove(station);
264        }
265        evened += 1;
266    }
267    Ok(evened)
268}
269
270/// At 0.25 six of `anotherBooleanFail`'s eight sections stay NoProgress
271/// (worst miss 2.7e-4); at 0.5 all eight are evened and fall below the
272/// refinement gate (worst miss 2.8e-3 -> 5.4e-8).
273const SLIVER_END_FRACTION: f64 = 0.5;
274
275/// How many sliver spans one section may lose, at most, over both ends. Every
276/// `anotherBooleanFail` section reaches this cap (the march lands several
277/// closely spaced stations beside each clip end), and the evened fits read
278/// 2.2e-8..5.4e-8 against the carriers under it; whether a higher cap would
279/// read differently was not measured (lane K6, 2026-09-30).
280const SLIVER_END_ROUNDS: usize = 4;
281
282/// `BREP_SECTION_SLIVER_FRACTION` overrides [`SLIVER_END_FRACTION`] for the
283/// measurement that picks it.
284fn sliver_end_fraction() -> f64 {
285    static FRACTION: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
286    *FRACTION.get_or_init(|| {
287        std::env::var("BREP_SECTION_SLIVER_FRACTION")
288            .ok()
289            .and_then(|value| value.parse::<f64>().ok())
290            .filter(|fraction| *fraction > 0.0 && *fraction < 1.0)
291            .unwrap_or(SLIVER_END_FRACTION)
292    })
293}
294
295fn section_fit_degree() -> usize {
296    static DEGREE: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
297    *DEGREE.get_or_init(|| {
298        std::env::var("BREP_SECTION_FIT_DEGREE")
299            .ok()
300            .and_then(|value| value.parse::<usize>().ok())
301            .filter(|degree| (1..=7).contains(degree))
302            .unwrap_or(5)
303    })
304}
305
306/// A marched section is held to the fit tolerance against the CARRIERS, not
307/// only against the polyline it was fitted through (2026-09-26). The march
308/// refines every station onto both surfaces to `tolerance * 0.01`, but the
309/// cubic interpolant between stations is measured by `fit_polyline` against the
310/// polyline's own chords, which are further from the true curve than the
311/// interpolant is — so a systematic bias of the interpolant (2.8e-8 mean,
312/// 4.7e-7 worst, inside the bore on `BadBoolean`'s sphere/bore rim at 135
313/// stations) passed every check the fit could make, and both trims inherited it
314/// (+1.14e-6 on the solid's volume, against two independent readings agreeing
315/// to 1e-8). The check below evaluates the fitted curve at every mid-span,
316/// measures it against both carriers, and where it misses `fit tolerance *
317/// SECTION_MIDSPAN_FRACTION` inserts a TRUE intersection point there and
318/// refits. The interpolant's bias falls as h^4, so one round usually suffices.
319/// A round that neither halves the worst miss nor lowers the count of spans
320/// missing is not in that regime — a carrier's C0 crease puts a corner in the
321/// section (fixture 25's pierce solid), where every round only doubles the
322/// stations — so that round is discarded and the loop stops. It is also capped
323/// in rounds and stations, and names how it left.
324/// `BREP_SECTION_REFINE_GATE=off` switches it off, for the before/after reading
325/// and nothing else.
326pub(super) const SECTION_MIDSPAN_FRACTION: f64 = 0.1;
327const SECTION_MIDSPAN_ROUNDS: usize = 4;
328/// A round earns another if it cuts the worst mid-span miss by this factor
329/// (h^4 promises 16 for a halved spacing) OR lowers the count of spans
330/// missing: near the floor a few isolated spans hold the worst while the count
331/// falls, and on a corner the count GROWS round on round.
332const SECTION_MIDSPAN_PROGRESS: f64 = 0.5;
333
334/// Squared distance from `point` to the segment `from`-`to`.
335fn distance_to_segment_sq(point: Vec3, from: Vec3, to: Vec3) -> f64 {
336    let d = to.sub(from);
337    let l2 = d.dot(d);
338    let t = if l2 > 0.0 { (point.sub(from).dot(d) / l2).clamp(0.0, 1.0) } else { 0.0 };
339    let q = from.add(d.scale(t));
340    point.sub(q).dot(point.sub(q))
341}
342
343/// How a mid-span refinement left (`SECTION_MIDSPAN_*`), for the pair trace.
344#[derive(Clone, Copy, Debug, PartialEq)]
345pub(super) enum MidspanExit {
346    /// Every mid-span within the floor of both carriers.
347    Converged,
348    /// The unrefined fit's worst mid-span is within the gate: the section is
349    /// sampled well enough that its degree carries it, and stations would
350    /// cost more downstream than they return.
351    BelowGate,
352    /// Some mid-span still misses and no inserted point could improve it
353    /// (the Newton failed, wandered off the span, or landed on a station
354    /// already present).
355    Stalled,
356    /// A round neither halved the worst miss nor lowered the count of spans
357    /// missing: not the h^4 regime, so the section has a corner the
358    /// interpolant cannot follow. That round's fit is discarded.
359    NoProgress,
360    RoundBudget,
361    StationCeiling,
362}
363
364/// Measure `fit` at every mid-span against both carriers; return the worst
365/// distance, the count of spans that miss `floor` and, for each of them the
366/// Newton could place, the refined intersection point to insert (with the
367/// span's index).
368fn midspan_misses(
369    fit: &PolylineFit,
370    first: &NurbsSurface,
371    second: &NurbsSurface,
372    floor: f64,
373    tolerance: f64,
374) -> Result<(f64, usize, Vec<(usize, Vec3)>), String> {
375    let mut worst = 0.0f64;
376    let mut missing = 0usize;
377    let mut inserts = Vec::new();
378    for span in 0..fit.parameters.len().saturating_sub(1) {
379        let mid = 0.5 * (fit.parameters[span] + fit.parameters[span + 1]);
380        let point = fit.curve.evaluate(mid)?;
381        let off = project_point_to_surface(first, point)?
382            .distance
383            .max(project_point_to_surface(second, point)?.distance);
384        worst = worst.max(off);
385        if off <= floor {
386            continue;
387        }
388        missing += 1;
389        // The refined point must stay on THIS span: half its chord is the reach.
390        let reach = 0.5 * fit.kept[span + 1].sub(fit.kept[span]).length();
391        if let Some(refined) = crate::surface_surface_intersection::refine_to_intersection(
392            first, second, point, tolerance, reach,
393        )? {
394            inserts.push((span, refined));
395        }
396    }
397    Ok((worst, missing, inserts))
398}
399
400/// Every mid-span miss of `fit` against both carriers, measured and nothing
401/// else: what [`even_sliver_ends_measured`] reads before and after evening.
402/// It refines nothing, so a section without a sliver end pays only the
403/// projections.
404fn midspan_misses_only(fit: &PolylineFit, first: &NurbsSurface, second: &NurbsSurface) -> Result<Vec<f64>, String> {
405    let mut misses = Vec::with_capacity(fit.parameters.len());
406    for span in 0..fit.parameters.len().saturating_sub(1) {
407        let mid = 0.5 * (fit.parameters[span] + fit.parameters[span + 1]);
408        let point = fit.curve.evaluate(mid)?;
409        let off = project_point_to_surface(first, point)?
410            .distance
411            .max(project_point_to_surface(second, point)?.distance);
412        misses.push(off);
413    }
414    Ok(misses)
415}
416
417/// The signature of a global interpolant ringing on a sliver end span: the
418/// worst mid-span miss sits in one of the [`SLIVER_END_SPANS`] spans at either
419/// end, is over the fit `floor`, AND stands [`SLIVER_RING_CONTRAST`] times
420/// above the median of the interior spans. A section sampled too coarsely for the carriers misses
421/// everywhere at once and does not show this; a section with fewer than two
422/// interior spans offers no evidence either way. Returns the end-span worst
423/// and the interior median when the signature is present.
424fn ringing_signature(misses: &[f64], floor: f64) -> Option<(f64, f64)> {
425    let spans = misses.len();
426    if spans < 2 * SLIVER_END_SPANS + 2 {
427        return None;
428    }
429    let ends = misses[..SLIVER_END_SPANS]
430        .iter()
431        .chain(&misses[spans - SLIVER_END_SPANS..])
432        .copied()
433        .fold(0.0f64, f64::max);
434    let mut interior: Vec<f64> = misses[SLIVER_END_SPANS..spans - SLIVER_END_SPANS].to_vec();
435    interior.sort_by(f64::total_cmp);
436    let median = interior[interior.len() / 2];
437    let worst = misses.iter().copied().fold(0.0f64, f64::max);
438    // Below the fit floor there is nothing to even: a 1e-8 miss on a planar
439    // section passed the contrast on fixture 26 and flipped its idempotence
440    // reading while changing nothing a bar could see.
441    (ends > floor.max(sliver_min_miss()) && ends >= worst && ends > sliver_ring_contrast() * median.max(f64::MIN_POSITIVE))
442        .then_some((ends, median))
443}
444
445/// How many spans at each end of a section [`even_sliver_ends`] may even —
446/// the same three it inspects — and where [`ringing_signature`] looks.
447const SLIVER_END_SPANS: usize = 3;
448
449/// The ringing signature's contrast: the end spans' worst mid-span miss over
450/// the interior spans' median. A converged march leaves its interior at
451/// 1e-9 and a sliver end rings at 1e-4..1e-3, so the ratio is 1e5 and up:
452/// `anotherBooleanFail`'s eight sections read 1.3e5..2.2e6 (interior
453/// 6e-10..2.6e-9, ends 1.6e-4..2.8e-3). A section whose interior itself
454/// misses is under-sampled rather than ringing, and evening its end moves a
455/// junction the assembler then cannot repair: fixture 28's pair 822×118
456/// (interior 1.5e-6, ends 1.4e-3, contrast 930) refused the reversed
457/// subtract whenever it was evened, while its pairs 474×118 (1.7e4) and
458/// 842×118 (1.4e5) are evened and build. Fixture 25's two sections read
459/// 2.2e3 and 5.9e3 (interior 2e-8) and keep their stations and their pins.
460/// 3e4 sits at least 4× from the nearest reading on either side
461/// (2026-09-30).
462const SLIVER_RING_CONTRAST: f64 = 3.0e4;
463
464/// `BREP_SECTION_SLIVER_CONTRAST` overrides [`SLIVER_RING_CONTRAST`] for the
465/// measurement that picks it.
466fn sliver_ring_contrast() -> f64 {
467    static CONTRAST: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
468    *CONTRAST.get_or_init(|| {
469        std::env::var("BREP_SECTION_SLIVER_CONTRAST")
470            .ok()
471            .and_then(|value| value.parse::<f64>().ok())
472            .filter(|contrast| *contrast >= 1.0)
473            .unwrap_or(SLIVER_RING_CONTRAST)
474    })
475}
476
477/// `BREP_SECTION_SLIVER_MIN_MISS` raises the absolute floor under the end
478/// spans' worst miss, for measurement only (the shipped floor is the fit
479/// tolerance).
480fn sliver_min_miss() -> f64 {
481    static MIN: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
482    *MIN.get_or_init(|| {
483        std::env::var("BREP_SECTION_SLIVER_MIN_MISS")
484            .ok()
485            .and_then(|value| value.parse::<f64>().ok())
486            .filter(|miss| *miss >= 0.0)
487            .unwrap_or(0.0)
488    })
489}
490
491/// How a sliver-end evening left, for the census line.
492#[derive(Clone, Copy, Debug, PartialEq)]
493pub(super) enum SliverEvening {
494    /// No end span was shorter than the fraction, or the hatch is `off`.
495    NoSliver,
496    /// A sliver end exists but the unevened fit does not show the ringing
497    /// signature ([`ringing_signature`]): its stations stand.
498    NotRinging { slivers: usize, worst: f64 },
499    /// The evened section's fit reads at least [`SLIVER_END_GAIN`] times
500    /// closer to the carriers at its worst mid-span: the evened fit stands.
501    Accepted { slivers: usize, before: f64, after: f64, interior: f64 },
502    /// Evening did not earn its keep: the unevened fit stands, the stations
503    /// untouched.
504    Rejected { slivers: usize, before: f64, after: f64, interior: f64 },
505}
506
507/// A sliver-end evening must earn its keep: the evened fit stands only when
508/// its worst mid-span miss against both carriers falls by at least this
509/// factor. Measured on 2026-09-30 against the blanket default: `resolve` on
510/// every section that HAS a sliver end refused the reversed subtract of
511/// fixture 28 (`march_swap_rescue_recovers_reversed_subtract_t217`, an open
512/// loop) and made fixture 26's union read differently before and after
513/// operand splitting, while on `anotherBooleanFail` (2.8e-3 → 5e-8) and
514/// fixture 25 (2.0e-5 / 3.4e-5 → 9.2e-7) it cut the miss by 20× to 5e4×. A
515/// section whose evened fit is not clearly better keeps its stations.
516const SLIVER_END_GAIN: f64 = 2.0;
517
518/// [`even_sliver_ends`] with the measurement that decides whether it stands:
519/// fit the section as marched; if its mid-spans show the ringing signature
520/// ([`ringing_signature`]), even its sliver ends, fit again, and keep the
521/// evened fit only when its worst mid-span wins by [`SLIVER_END_GAIN`].
522/// Returns the section, its fit, the recovery flag of the fit that stands and
523/// the outcome.
524pub(super) fn even_sliver_ends_measured(
525    section: Vec<Vec3>,
526    first: &NurbsSurface,
527    second: &NurbsSurface,
528    tolerance: f64,
529    fit_tolerance: f64,
530    maximum_fit_points: usize,
531    local_fit: bool,
532) -> Result<(Vec<Vec3>, PolylineFit, bool, SliverEvening), String> {
533    let (fit, recovered) = crate::fit::fit_polyline_of_degree_with_recovery(
534        &section,
535        fit_tolerance,
536        maximum_fit_points,
537        local_fit,
538        section_fit_degree(),
539    )?;
540    let mut evened = section.clone();
541    let slivers = even_sliver_ends(&mut evened, first, second, tolerance)?;
542    if slivers == 0 {
543        return Ok((section, fit, recovered, SliverEvening::NoSliver));
544    }
545    let misses = midspan_misses_only(&fit, first, second)?;
546    let Some((before, interior)) = ringing_signature(&misses, fit_tolerance) else {
547        let worst = misses.iter().copied().fold(0.0f64, f64::max);
548        return Ok((section, fit, recovered, SliverEvening::NotRinging { slivers, worst }));
549    };
550    let (evened_fit, evened_recovered) = crate::fit::fit_polyline_of_degree_with_recovery(
551        &evened,
552        fit_tolerance,
553        maximum_fit_points,
554        local_fit,
555        section_fit_degree(),
556    )?;
557    let after_misses = midspan_misses_only(&evened_fit, first, second)?;
558    let after = after_misses.iter().copied().fold(0.0f64, f64::max);
559    if let Ok(path) = std::env::var("BREP_SECTION_SLIVER_CENSUS") {
560        use std::io::Write;
561        if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
562            let chords = |points: &[Vec3]| points.windows(2).map(|pair| format!("{:.4e}", pair[1].sub(pair[0]).length())).collect::<Vec<_>>().join(" ");
563            let fmt = |values: &[f64]| values.iter().map(|value| format!("{value:.2e}")).collect::<Vec<_>>().join(" ");
564            let _ = writeln!(file, "sliver-end detail: stations {} -> {} kept {} -> {}", section.len(), evened.len(), fit.kept.len(), evened_fit.kept.len());
565            let _ = writeln!(file, "sliver-end detail: chords before: {}", chords(&section));
566            let _ = writeln!(file, "sliver-end detail: chords after:  {}", chords(&evened));
567            let _ = writeln!(file, "sliver-end detail: misses before: {}", fmt(&misses));
568            let _ = writeln!(file, "sliver-end detail: misses after:  {}", fmt(&after_misses));
569            let _ = writeln!(file, "sliver-end detail: ends before: {:?} {:?}", section[0], section[section.len() - 1]);
570            let _ = writeln!(file, "sliver-end detail: ends after:  {:?} {:?}", evened[0], evened[evened.len() - 1]);
571        }
572    }
573    if after * SLIVER_END_GAIN <= before {
574        Ok((evened, evened_fit, evened_recovered, SliverEvening::Accepted { slivers, before, after, interior }))
575    } else {
576        Ok((section, fit, recovered, SliverEvening::Rejected { slivers, before, after, interior }))
577    }
578}
579
580/// Refit `section` until its mid-spans sit within `floor` of both carriers,
581/// inserting true intersection points where they do not. Returns the fit that
582/// stands, the worst mid-span miss it still carries, the unrefined fit's worst
583/// (what the gate read), the rounds spent and the exit.
584/// Keep the single best measured refiner state (root grant 2026-10-04,
585/// accepted-representative sections only): replaced when `worst` is finite and
586/// no larger than the kept one (ties go to the later state). An unreadable
587/// reading is never kept. At most one snapshot is held.
588pub(super) fn keep_best_state<S>(best: &mut Option<(S, f64)>, state: impl FnOnce() -> S, worst: f64) {
589    if worst.is_finite() && best.as_ref().is_none_or(|(_, kept)| worst <= *kept) {
590        *best = Some((state(), worst));
591    }
592}
593
594/// The rollback a budget-type exit (RoundBudget, StationCeiling, Stalled)
595/// takes: on a section where an ACCEPTED representative round ran, the kept
596/// best state when its COMMON reading is strictly lower than the current
597/// state's common reading on the same instrument (both carriers, finite, at the
598/// fit's mid-spans and its own knot-span quarter points). Otherwise `None`:
599/// the exit returns the current state exactly as before, with nothing cloned.
600/// An unreadable current reading never rolls back (it is not hidden).
601pub(super) fn take_rollback<S>(best: &mut Option<(S, f64)>, current_common: f64, representative_ran: bool) -> Option<(S, f64)> {
602    let lower = matches!(best, Some((_, kept)) if representative_ran && current_common.is_finite() && *kept < current_common);
603    if lower { best.take() } else { None }
604}
605
606/// Which fit a refinement RETURNED: a receipt for controls, deciding nothing.
607#[derive(Clone, Copy, Debug, PartialEq)]
608pub(super) enum RefinedFitSource {
609    /// The standing fit the caller passed in.
610    Standing,
611    /// The original full refit of the run, made directly (`full-unmatched`,
612    /// or the retry after an unreadable representative or a seeded stall).
613    FullRefit,
614    /// The seeded (forced-station) call and its own path: a `Fallback` path
615    /// IS the original full refit.
616    Seeded(crate::fit::SeededPath),
617    /// The representative candidate call and its own path: a `Fallback` path
618    /// IS the original full refit.
619    Representatives(crate::fit::SeededPath),
620    /// The best measured state the budget-exit rollback restored.
621    Rollback,
622}
623
624/// The refiner's receipt: whether the run took the representative route at
625/// all, and which fit it returned (None on an error return). Production
626/// callers discard it; only controls read it.
627#[allow(dead_code)]
628#[derive(Clone, Copy, Debug, Default, PartialEq)]
629pub(super) struct RefineReceipt {
630    pub original_consistent: bool,
631    pub returned: Option<RefinedFitSource>,
632}
633
634pub(super) fn refine_section_against_carriers(
635    section: &mut Vec<Vec3>,
636    fit: PolylineFit,
637    first: &NurbsSurface,
638    second: &NurbsSurface,
639    fit_tolerance: f64,
640    tolerance: f64,
641    local_fit: bool,
642    gate: f64,
643) -> Result<(PolylineFit, f64, f64, usize, MidspanExit), String> {
644    // `BREP_SECTION_REPRESENTATIVES=0` restores the seeded path everywhere for
645    // an attribution A/B only; read here, once per call, as before.
646    let representatives = std::env::var("BREP_SECTION_REPRESENTATIVES").as_deref() != Ok("0");
647    refine_section_against_carriers_with(section, fit, first, second, fit_tolerance, tolerance, local_fit, gate, representatives, &mut RefineReceipt::default())
648}
649
650/// [`refine_section_against_carriers`] with the representative switch passed
651/// in (the env hatch's value, so a control can take the seeded route without
652/// touching the process environment) and a receipt of the fit returned.
653#[allow(clippy::too_many_arguments)]
654pub(super) fn refine_section_against_carriers_with(
655    section: &mut Vec<Vec3>,
656    fit: PolylineFit,
657    first: &NurbsSurface,
658    second: &NurbsSurface,
659    fit_tolerance: f64,
660    tolerance: f64,
661    local_fit: bool,
662    gate: f64,
663    representatives: bool,
664    receipt: &mut RefineReceipt,
665) -> Result<(PolylineFit, f64, f64, usize, MidspanExit), String> {
666    // A reused receipt starts clean, so an error return leaves `returned`
667    // None as documented, never a previous call's value.
668    *receipt = RefineReceipt::default();
669    // Which fit `fit` currently is, and which fit `previous` holds.
670    let mut fit_source = RefinedFitSource::Standing;
671    let mut previous_source = RefinedFitSource::Standing;
672    let floor = fit_tolerance * SECTION_MIDSPAN_FRACTION;
673    let mut unrefined = f64::NAN;
674    let mut fit = fit;
675    let mut rounds = 0usize;
676    let mut previous: Option<(PolylineFit, f64, usize)> = None;
677    // Every true intersection point inserted so far: the residual-driven bend
678    // witnesses, which the refit must keep as stations.
679    let mut inserted_points: Vec<Vec3> = Vec::new();
680    // `BREP_SECTION_REFINE_CENSUS`: one `section-refine round:` line per round,
681    // bound to its section by the run's head and tail, so a stall can be read
682    // round by round (debug only; nothing below reads it).
683    let round_census = std::env::var("BREP_SECTION_REFINE_CENSUS").ok();
684    let (head, tail) = (section[0], section[section.len() - 1]);
685    // Whether EVERY original point of the run lies on both carriers within the
686    // fit tolerance (projector upper readings, NaN = no): only such a run takes
687    // the refiner-specific representative candidate below. A run with any
688    // point off a carrier keeps the existing seeded path exactly (moving or
689    // discounting input points is the reserved relocation ruling).
690    // `representatives` false (the `BREP_SECTION_REPRESENTATIVES=0` hatch)
691    // restores the seeded path everywhere for an attribution A/B only.
692    let original_consistent = representatives
693        && section.iter().all(|point| {
694            let a = project_point_to_surface(first, *point).map_or(f64::NAN, |projection| projection.distance);
695            let b = project_point_to_surface(second, *point).map_or(f64::NAN, |projection| projection.distance);
696            a.is_finite() && b.is_finite() && a <= fit_tolerance && b <= fit_tolerance
697        });
698    receipt.original_consistent = original_consistent;
699    let mut refit_path = String::from("standing");
700    let mut refit_seeds = 0usize;
701    let mut refit_seeds_kept = 0usize;
702    let mut refit_seeds_outside_pool = 0usize;
703    let mut refit_pool = 0usize;
704    let mut refit_pool_discarded = 0usize;
705    let mut refit_pool_floor = f64::NAN;
706    let mut refit_seed_min_chord = f64::NAN;
707    // Trace only: what the closest seed pair is made of (kept station or
708    // refiner insert at each end), to place the near pair upstream.
709    let mut refit_seed_min_pair = "n/a";
710    // Trace only: every seed the full refit's pool rejects, by ORIGIN (a march
711    // station of the run, or a refiner insert) and by REASON (dropped by
712    // `station_pool`'s simplification, or kept by it and then conditioned
713    // away as a near-duplicate): [march simplified, march conditioned,
714    // insert simplified, insert conditioned].
715    let mut refit_rejected = [0usize; 4];
716    // Fits made for the round being judged (2 when its seeded fit stalled and
717    // the full refit was tried as well), and that retry's own cost.
718    let mut refit_trials = 1usize;
719    let mut retry_ms = 0.0f64;
720    // Whether the fit judged this round is an ACCEPTED seeded fit (not merely
721    // a seeded call: a seeded call that fell back already IS the full refit).
722    let mut seeded_refit = false;
723    // Whether the fit judged this round is an ACCEPTED representative
724    // candidate, and what that candidate call spent (its own interpolations
725    // and whether it ended on the full refit), kept apart from the fit's
726    // report, which on a fallback must stay the full refit's to the bit.
727    let mut representative_fit = false;
728    let mut refit_cost = crate::fit::RepresentativeCost::default();
729    // Whether an ACCEPTED representative round ran on this section, and the one
730    // best measured (fit, section) state kept for its budget exits.
731    let mut representative_ran = false;
732    let mut best_state: Option<((PolylineFit, Vec<Vec3>), f64)> = None;
733    loop {
734        // On an accepted representative candidate a read or refinement ERROR is
735        // not a refusal: the section takes the original full refit of the same
736        // run instead, as an unreadable read does below.
737        let (mut worst, missing, inserts) = match midspan_misses(&fit, first, second, floor, tolerance) {
738            Ok(reading) => reading,
739            Err(_) if representative_fit => {
740                representative_fit = false;
741                seeded_refit = false;
742                let maximum_points = section.len().min(MAX_FIT_STATIONS);
743                fit = fit_polyline_of_degree(section, fit_tolerance, maximum_points, local_fit, section_fit_degree())?;
744                fit_source = RefinedFitSource::FullRefit;
745                refit_path = String::from("full-after-unreadable-representative");
746                refit_trials += 1;
747                refit_cost.full_refit = true;
748                continue;
749            }
750            Err(error) => return Err(error),
751        };
752        // An ACCEPTED representative candidate is judged on more than its
753        // mid-spans: both carriers must read finite at every mid-span and at
754        // the quarter points of the curve's own knot spans, and its worst is
755        // the worse of the two reads (sampled, projector upper readings; the
756        // carrier target is unchanged). An unreadable read never passes: the
757        // section takes the original full refit of the same run instead.
758        if representative_fit {
759            let interior = knot_interior_carrier_upper(&fit.curve, first, second);
760            let midspans = midspan_carrier_upper(&fit, first, second);
761            if interior.is_finite() && midspans.is_finite() {
762                worst = worst.max(interior).max(midspans);
763            } else {
764                representative_fit = false;
765                seeded_refit = false;
766                let maximum_points = section.len().min(MAX_FIT_STATIONS);
767                fit = fit_polyline_of_degree(section, fit_tolerance, maximum_points, local_fit, section_fit_degree())?;
768                fit_source = RefinedFitSource::FullRefit;
769                refit_path = String::from("full-after-unreadable-representative");
770                refit_trials += 1;
771                refit_cost.full_refit = true;
772                continue;
773            }
774        }
775        if let Some(path) = &round_census {
776            use std::io::Write;
777            if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
778                let _ = writeln!(
779                    file,
780                    "section-refine round: head ({:.9}, {:.9}, {:.9}) tail ({:.9}, {:.9}, {:.9}) round {rounds} fit {refit_path} trials {refit_trials} retry_ms {retry_ms:.3} kept {} seeds {refit_seeds} seeds_kept {refit_seeds_kept} seeds_outside_pool {refit_seeds_outside_pool} seed_min_chord {refit_seed_min_chord:.3e} seed_min_pair {refit_seed_min_pair} rejected_march_simplified {} rejected_march_conditioned {} rejected_insert_simplified {} rejected_insert_conditioned {} pool {refit_pool} pool_discarded {refit_pool_discarded} pool_floor {refit_pool_floor:.3e} exit {:?} deviation {:.3e} floor {:.3e} run {} worst {:.3e} missing {} inserts_offered {} representatives {} witnesses {} candidate_fits {} candidate_full_refit {} candidate_end_offsets {:.3e}/{:.3e} seed_role {}",
781                    head.x, head.y, head.z, tail.x, tail.y, tail.z,
782                    fit.kept.len(),
783                    refit_rejected[0], refit_rejected[1], refit_rejected[2], refit_rejected[3], fit.report.exit, fit.report.deviation, fit.report.floor, section.len(), worst, missing, inserts.len(),
784                    refit_cost.representatives, refit_cost.witnesses, refit_cost.candidate_fits, refit_cost.full_refit,
785                    refit_cost.start_offset, refit_cost.end_offset,
786                    // On the representative path the seeds counted above are
787                    // WITNESSES (not forced); on the seeded path they are forced.
788                    if original_consistent { "witness" } else { "forced" }
789                );
790                // Trace only: where the full refit's band-raised floor comes
791                // from on THIS run, and whether the run's own points are on
792                // the carriers (input-consistent) or not. The band is
793                // `dropped_band`'s reading (largest distance of a point the
794                // pool drops from the segment between its surviving
795                // neighbours); its point is classified as simplified away or
796                // conditioned away, with its carrier distance and its gap to
797                // the nearest run neighbour. Projector distances are upper
798                // readings.
799                let pool = crate::fit::polyline_station_pool(section, fit_tolerance);
800                let simplified = crate::fit::simplify_polyline_indices(section, fit_tolerance);
801                let mut band = (0.0f64, usize::MAX);
802                for pair in pool.windows(2) {
803                    for index in pair[0] + 1..pair[1] {
804                        let off = distance_to_segment_sq(section[index], section[pair[0]], section[pair[1]]).sqrt();
805                        if off > band.0 {
806                            band = (off, index);
807                        }
808                    }
809                }
810                let carrier = |point: Vec3| -> f64 {
811                    let a = project_point_to_surface(first, point).map_or(f64::NAN, |projection| projection.distance);
812                    let b = project_point_to_surface(second, point).map_or(f64::NAN, |projection| projection.distance);
813                    if a.is_nan() || b.is_nan() { f64::NAN } else { a.max(b) }
814                };
815                let mut run_carrier = 0.0f64;
816                for point in section.iter() {
817                    let off = carrier(*point);
818                    run_carrier = if off.is_nan() || run_carrier.is_nan() { f64::NAN } else { run_carrier.max(off) };
819                }
820                let (kind, band_carrier, gap) = if band.1 == usize::MAX {
821                    ("none", f64::NAN, f64::NAN)
822                } else {
823                    let index = band.1;
824                    let kind = if simplified.binary_search(&index).is_ok() { "conditioned" } else { "simplified" };
825                    let mut gap = f64::INFINITY;
826                    if index > 0 {
827                        gap = gap.min(section[index].sub(section[index - 1]).length());
828                    }
829                    if index + 1 < section.len() {
830                        gap = gap.min(section[index + 1].sub(section[index]).length());
831                    }
832                    (kind, carrier(section[index]), gap)
833                };
834                let _ = writeln!(
835                    file,
836                    "section-refine band: head ({:.9}, {:.9}, {:.9}) tail ({:.9}, {:.9}, {:.9}) round {rounds} run {} pool {} tolerance {:.3e} band {:.3e} band_index {} of {} band_kind {kind} band_point_carrier_upper {band_carrier:.3e} band_point_gap {gap:.3e} run_carrier_upper {run_carrier:.3e}",
837                    head.x, head.y, head.z, tail.x, tail.y, tail.z,
838                    section.len(), pool.len(), fit_tolerance, band.0,
839                    if band.1 == usize::MAX { -1 } else { band.1 as i64 }, section.len()
840                );
841            }
842        }
843        if rounds == 0 {
844            unrefined = worst;
845        }
846        // The rollback path's COMMON reading, only once an accepted
847        // representative round has run and only on a run the refit can hold
848        // (<= MAX_FIT_STATIONS): both carriers, finite, at the fit's mid-spans
849        // and its own knot-span quarter points, for EVERY state compared
850        // (representative, standing or full alike). One snapshot, the fit and
851        // the run it was measured on as one coherent pair: its payload is one
852        // PolylineFit and at most MAX_FIT_STATIONS points. Off this path
853        // nothing is read or cloned.
854        let mut current_common = f64::NAN;
855        if representative_ran && section.len() <= MAX_FIT_STATIONS {
856            let interior = knot_interior_carrier_upper(&fit.curve, first, second);
857            let midspans = midspan_carrier_upper(&fit, first, second);
858            current_common = if interior.is_finite() && midspans.is_finite() { interior.max(midspans) } else { f64::NAN };
859            keep_best_state(&mut best_state, || (fit.clone(), section.clone()), current_common);
860        }
861        if worst <= floor {
862            receipt.returned = Some(fit_source);
863            return Ok((fit, worst, unrefined, rounds, MidspanExit::Converged));
864        }
865        if rounds == 0 && worst <= gate {
866            receipt.returned = Some(fit_source);
867            return Ok((fit, worst, unrefined, rounds, MidspanExit::BelowGate));
868        }
869        if let Some((earlier, earlier_worst, earlier_missing)) = previous.take() {
870            // A round that did not earn its keep is discarded whole: the fit
871            // of the last round that did stands (on a corner, the unrefined
872            // fit), not whichever of the two happens to read a smaller max.
873            if worst > earlier_worst * SECTION_MIDSPAN_PROGRESS && missing >= earlier_missing {
874                // A SEEDED round that did not earn its keep is refitted once
875                // with the original full refit of the same denser run, and
876                // that is judged against the same earlier round before the
877                // section stops. The seeded fit keeps fewer stations; on the
878                // helmet's 2036x3009 section that gave back the whole of the
879                // full refit's gain (6.335e-6 stood where 5.596e-7 was
880                // reached). The full refit is the round the refiner made
881                // before the seeded entry existed; only a round that fails
882                // both ends the section, exactly as before.
883                if seeded_refit {
884                    seeded_refit = false;
885                    let maximum_points = section.len().min(MAX_FIT_STATIONS);
886                    let retry_started = round_census.is_some().then(Instant::now);
887                    fit = fit_polyline_of_degree(section, fit_tolerance, maximum_points, local_fit, section_fit_degree())?;
888                    fit_source = RefinedFitSource::FullRefit;
889                    // Trace: one more fit for the SAME round, its cost, and no
890                    // seed counters carried over from the rejected seeded fit.
891                    refit_path = String::from("full-after-seeded-stall");
892                    // Every fit this round made: the candidate's (or the seeded
893                    // call's one) plus this retry.
894                    refit_trials += 1;
895                    retry_ms = retry_started.map_or(0.0, |started| started.elapsed().as_secs_f64() * 1e3);
896                    refit_seeds = 0;
897                    refit_seeds_kept = 0;
898                    refit_seeds_outside_pool = 0;
899                    refit_seed_min_chord = f64::NAN;
900                    refit_seed_min_pair = "n/a";
901                    refit_rejected = [0; 4];
902                    representative_fit = false;
903                    // Keep the candidate's own counts; the retry is the full refit.
904                    refit_cost.full_refit = true;
905                    previous = Some((earlier, earlier_worst, earlier_missing));
906                    continue;
907                }
908                receipt.returned = Some(previous_source);
909                return Ok((earlier, earlier_worst, unrefined, rounds, MidspanExit::NoProgress));
910            }
911        }
912        if inserts.is_empty() {
913            if let Some(((kept_fit, kept_run), kept_common)) = take_rollback(&mut best_state, current_common, representative_ran) {
914                *section = kept_run;
915                receipt.returned = Some(RefinedFitSource::Rollback);
916                return Ok((kept_fit, kept_common, unrefined, rounds, MidspanExit::Stalled));
917            }
918            receipt.returned = Some(fit_source);
919            return Ok((fit, worst, unrefined, rounds, MidspanExit::Stalled));
920        }
921        if rounds >= SECTION_MIDSPAN_ROUNDS {
922            if let Some(((kept_fit, kept_run), kept_common)) = take_rollback(&mut best_state, current_common, representative_ran) {
923                *section = kept_run;
924                receipt.returned = Some(RefinedFitSource::Rollback);
925                return Ok((kept_fit, kept_common, unrefined, rounds, MidspanExit::RoundBudget));
926            }
927            receipt.returned = Some(fit_source);
928            return Ok((fit, worst, unrefined, rounds, MidspanExit::RoundBudget));
929        }
930        if section.len() + inserts.len() > MAX_FIT_STATIONS {
931            if let Some(((kept_fit, kept_run), kept_common)) = take_rollback(&mut best_state, current_common, representative_ran) {
932                *section = kept_run;
933                receipt.returned = Some(RefinedFitSource::Rollback);
934                return Ok((kept_fit, kept_common, unrefined, rounds, MidspanExit::StationCeiling));
935            }
936            receipt.returned = Some(fit_source);
937            return Ok((fit, worst, unrefined, rounds, MidspanExit::StationCeiling));
938        }
939        // Insert each refined point into the run between its span's stations,
940        // on the nearest segment of the run there. Positions are located
941        // against the run BEFORE any insertion of this round, then applied
942        // from the back so earlier indices stay valid. The kept stations are
943        // found by a FORWARD search from the previous one: a closed section
944        // ends on its first station, and a search from the start would put
945        // the last span's far end at index 0 and skip that span forever.
946        let station_index = |point: Vec3, from: usize| -> Option<usize> {
947            section[from..]
948                .iter()
949                .position(|p| p.sub(point).length() <= 1e-15 * (1.0 + point.length()))
950                .map(|offset| from + offset)
951        };
952        let mut placed: Vec<(usize, Vec3)> = Vec::new();
953        for (span, point) in inserts {
954            let Some(lo) = station_index(fit.kept[span], 0) else {
955                continue;
956            };
957            let Some(hi) = station_index(fit.kept[span + 1], lo + 1) else {
958                continue;
959            };
960            if hi <= lo {
961                continue;
962            }
963            if section[lo..=hi].iter().any(|p| p.sub(point).length() <= floor) {
964                continue; // already a station: inserting it again refines nothing
965            }
966            let mut best = (lo, f64::INFINITY);
967            for index in lo..hi {
968                let d2 = distance_to_segment_sq(point, section[index], section[index + 1]);
969                if d2 < best.1 {
970                    best = (index, d2);
971                }
972            }
973            placed.push((best.0 + 1, point));
974        }
975        if placed.is_empty() {
976            if let Some(((kept_fit, kept_run), kept_common)) = take_rollback(&mut best_state, current_common, representative_ran) {
977                *section = kept_run;
978                receipt.returned = Some(RefinedFitSource::Rollback);
979                return Ok((kept_fit, kept_common, unrefined, rounds, MidspanExit::Stalled));
980            }
981            receipt.returned = Some(fit_source);
982            return Ok((fit, worst, unrefined, rounds, MidspanExit::Stalled));
983        }
984        placed.sort_by(|a, b| b.0.cmp(&a.0));
985        for (at, point) in placed {
986            section.insert(at, point);
987            inserted_points.push(point);
988        }
989        // The refit keeps the stations the current fit kept and every point
990        // inserted so far as SEEDS, and grows only where it is measured to
991        // miss (`fit_polyline_seeded_with_path`). It is no longer the whole denser run
992        // from the run's own count: that interpolated every march station the
993        // standing fit had dropped as well, 3x the controls on the skew-cylinder
994        // loop for the same carrier floor. The ceiling is the one the full refit
995        // derives from the same `maximum_points`, and a seeded fit that cannot
996        // meet the floor, or cannot be read, IS the full refit. The carrier
997        // measure above still decides every round.
998        let same = |a: &Vec3, b: &Vec3| a.x.to_bits() == b.x.to_bits() && a.y.to_bits() == b.y.to_bits() && a.z.to_bits() == b.z.to_bits();
999        let mut seeds = Vec::with_capacity(fit.kept.len() + inserted_points.len());
1000        // The previous fit's kept stations alone: the representative candidate's
1001        // starting set (inserts are its witnesses, not forced stations).
1002        let mut kept_seeds = Vec::with_capacity(fit.kept.len());
1003        // The refiner's own inserted true-carrier points: the representative
1004        // candidate's TIGHT witnesses, held to this refiner's carrier floor.
1005        let mut insert_indices = Vec::with_capacity(inserted_points.len());
1006        let mut next_kept = 0usize;
1007        for (index, point) in section.iter().enumerate() {
1008            // The kept stations are a subsequence of the run, in run order
1009            // (insertion only adds points), so a closed run's two equal ends
1010            // each match their own station.
1011            // An inserted point is a tight witness whether or not the previous
1012            // fit also kept it as a station (membership is independent).
1013            let inserted = inserted_points.iter().any(|candidate| same(point, candidate));
1014            if inserted {
1015                insert_indices.push(index);
1016            }
1017            if next_kept < fit.kept.len() && same(point, &fit.kept[next_kept]) {
1018                seeds.push(index);
1019                kept_seeds.push(index);
1020                next_kept += 1;
1021            } else if inserted {
1022                seeds.push(index);
1023            }
1024        }
1025        let maximum_points = section.len().min(MAX_FIT_STATIONS);
1026        // Every standing station must be found as a seed; if the pass missed
1027        // one, the seeded refit would silently lose it, so the section takes the
1028        // original full refit instead.
1029        refit_trials = 1;
1030        retry_ms = 0.0;
1031        let refit_source;
1032        let refit = if next_kept == fit.kept.len() {
1033            let (refit, path) = if original_consistent {
1034                let (refit, path, cost) = crate::fit::fit_polyline_representatives_with_witnesses(
1035                    section,
1036                    &kept_seeds,
1037                    &insert_indices,
1038                    floor,
1039                    fit_tolerance,
1040                    maximum_points,
1041                    local_fit,
1042                    section_fit_degree(),
1043                )?;
1044                // MIXED UNITS, named as such: the candidate's own
1045                // `fit_station_set` interpolations plus one per call of the
1046                // full refit `fit_polyline_of_degree`, which may itself run
1047                // several ladder and recovery interpolations that are NOT
1048                // counted here. Whole-job cost is the matched timing.
1049                refit_cost = cost;
1050                refit_trials = cost.candidate_fits + usize::from(cost.full_refit);
1051                (refit, path)
1052            } else {
1053                refit_cost = crate::fit::RepresentativeCost::default();
1054                crate::fit::fit_polyline_seeded_with_path(
1055                    section,
1056                    &seeds,
1057                    fit_tolerance,
1058                    maximum_points,
1059                    local_fit,
1060                    section_fit_degree(),
1061                )?
1062            };
1063            // Only an ACCEPTED seeded fit can be retried in full: a fallback
1064            // already is the full refit, and retrying it would repeat it.
1065            seeded_refit = matches!(path, crate::fit::SeededPath::Accepted { .. });
1066            representative_fit = original_consistent && seeded_refit;
1067            refit_source = if original_consistent { RefinedFitSource::Representatives(path) } else { RefinedFitSource::Seeded(path) };
1068            representative_ran |= representative_fit;
1069            if round_census.is_some() {
1070                refit_path = if original_consistent { format!("representatives-call:{path:?}") } else { format!("seeded-call:{path:?}") };
1071            }
1072            refit
1073        } else {
1074            seeded_refit = false;
1075            representative_fit = false;
1076            refit_cost = crate::fit::RepresentativeCost::default();
1077            refit_path = String::from("full-unmatched");
1078            refit_source = RefinedFitSource::FullRefit;
1079            fit_polyline_of_degree(section, fit_tolerance, maximum_points, local_fit, section_fit_degree())?
1080        };
1081        if round_census.is_some() {
1082            // Trace only: how many seeds the refit kept. This does NOT tell an
1083            // accepted seeded fit from the fallback (a full refit can keep
1084            // every seed too); the returned `SeededPath`, traced as `fit`, does.
1085            refit_seeds = seeds.len();
1086            refit_seeds_kept = seeds.iter().filter(|&&index| refit.kept.iter().any(|kept| same(kept, &section[index]))).count();
1087            // Seeds the full refit's pool would NOT interpolate (simplified
1088            // or conditioned away): forced stations only the seeded fit keeps.
1089            let pool = crate::fit::polyline_station_pool(section, fit_tolerance);
1090            refit_pool = pool.len();
1091            refit_pool_discarded = section.len() - pool.len();
1092            refit_pool_floor = crate::fit::polyline_station_pool_floor(section, fit_tolerance);
1093            refit_seeds_outside_pool = seeds.iter().filter(|index| !pool.contains(index)).count();
1094            // Seeds are in run order; the closest consecutive pair is what a
1095            // near-duplicate conditioning floor would act on.
1096            refit_seed_min_chord = seeds
1097                .windows(2)
1098                .map(|pair| section[pair[1]].sub(section[pair[0]]).length())
1099                .fold(f64::INFINITY, f64::min);
1100            let is_insert = |index: usize| inserted_points.iter().any(|inserted| same(inserted, &section[index]));
1101            refit_seed_min_pair = seeds
1102                .windows(2)
1103                .min_by(|a, b| {
1104                    section[a[1]].sub(section[a[0]]).length().total_cmp(&section[b[1]].sub(section[b[0]]).length())
1105                })
1106                .map_or("n/a", |pair| match (is_insert(pair[0]), is_insert(pair[1])) {
1107                    (false, false) => "station-station",
1108                    (false, true) => "station-insert",
1109                    (true, false) => "insert-station",
1110                    (true, true) => "insert-insert",
1111                });
1112            // Every rejected seed, classified. `station_pool` starts from this
1113            // same simplification, so a non-member that is not in it was
1114            // simplified away, and one that is in it was conditioned away.
1115            let simplified = crate::fit::simplify_polyline_indices(section, fit_tolerance);
1116            refit_rejected = [0; 4];
1117            for &index in seeds.iter().filter(|index| !pool.contains(index)) {
1118                let conditioned = simplified.binary_search(&index).is_ok();
1119                refit_rejected[2 * usize::from(is_insert(index)) + usize::from(conditioned)] += 1;
1120            }
1121        }
1122        previous = Some((std::mem::replace(&mut fit, refit), worst, missing));
1123        previous_source = std::mem::replace(&mut fit_source, refit_source);
1124        rounds += 1;
1125    }
1126}
1127use super::*;
1128use super::gate_census::{census_pair, fit_census_enabled};
1129
1130pub fn build_imprints(
1131    solid_a: &BrepSolid,
1132    solid_b: &BrepSolid,
1133    options: &ImprintOptions,
1134) -> Result<ImprintResultRecord, KernelRefusal> {
1135    build_imprints_impl(solid_a, solid_b, options, false)
1136}
1137
1138/// Imprint temporary open offset sheets. A section riding one sheet's trim
1139/// still cuts the other sheet when no boundary copy supplied that section.
1140pub(crate) fn build_carrier_imprints(
1141    solid_a: &BrepSolid,
1142    solid_b: &BrepSolid,
1143    options: &ImprintOptions,
1144) -> Result<ImprintResultRecord, KernelRefusal> {
1145    build_imprints_impl(solid_a, solid_b, options, true)
1146}
1147
1148fn build_imprints_impl(
1149    solid_a: &BrepSolid,
1150    solid_b: &BrepSolid,
1151    options: &ImprintOptions,
1152    open_carriers: bool,
1153) -> Result<ImprintResultRecord, KernelRefusal> {
1154    let mut section_evidence = false;
1155    let edges = edge_map(solid_a, solid_b);
1156    let mut builder = ImprintBuilder {
1157        open_carriers,
1158        edges,
1159        tolerance: options.tolerance,
1160        barrier_edges: HashSet::default(),
1161        overlap_ridden_edges: HashSet::default(),
1162        scale: solid_scale(solid_a).max(solid_scale(solid_b)),
1163        vertices: Vec::new(),
1164        vertex_radii: HashMap::default(),
1165        pieces: Vec::new(),
1166        by_face: HashMap::default(),
1167        edge_splits: HashMap::default(),
1168        marched_pieces: HashSet::default(),
1169        next_id: 1,
1170    };
1171    let mut first_faces = faces(solid_a, 0);
1172    let mut second_faces = faces(solid_b, 1);
1173    // Per-face trim-window carriers, computed once: tighter BVH bounds, and
1174    // the seed/march stages walk the window instead of the full carrier
1175    // (identical geometry and parameterization inside the window).
1176    //
1177    // A window LIFTED across a closed direction's seam (`restricted_carrier`)
1178    // is the exception to "same parameterization": it is the trim's frame, a
1179    // period off the carrier's on one side. Its face carries it as its
1180    // `chart`, and every uv the imprint reads for that face — the clip's
1181    // containment, the sections' pcurves, a seed's normal — is read there.
1182    let first_restricted: Vec<Option<MarchWindow>> = first_faces
1183        .iter()
1184        .map(|tagged| restricted_carrier(tagged.face))
1185        .collect();
1186    let second_restricted: Vec<Option<MarchWindow>> = second_faces
1187        .iter()
1188        .map(|tagged| restricted_carrier(tagged.face))
1189        .collect();
1190    for (tagged, window) in first_faces.iter_mut().zip(&first_restricted) {
1191        tagged.lifted = window.as_ref().filter(|window| window.lifted).map(|window| &window.surface);
1192    }
1193    for (tagged, window) in second_faces.iter_mut().zip(&second_restricted) {
1194        tagged.lifted = window.as_ref().filter(|window| window.lifted).map(|window| &window.surface);
1195    }
1196    let first_faces = first_faces;
1197    let second_faces = second_faces;
1198    // The lifted charts, by key, for the post-passes that rebuild a pcurve
1199    // after the mint (junction canonicalization, the origin dissolve, the
1200    // truncation bridge). A face absent here is read on its own carrier.
1201    let charts: FaceCharts<'_> = first_faces
1202        .iter()
1203        .chain(second_faces.iter())
1204        .filter_map(|tagged| tagged.lifted.map(|surface| (tagged.key(), surface)))
1205        .collect();
1206    let first_bounds = first_faces
1207        .iter()
1208        .zip(&first_restricted)
1209        .map(|(tagged, restricted)| {
1210            face_bounds(
1211                tagged.face,
1212                restricted.as_ref().map(|window| &window.surface),
1213                &builder.edges,
1214                tagged.operand,
1215                options.tolerance,
1216            )
1217        })
1218        .collect::<Result<Vec<_>, _>>()?;
1219    let second_bounds = second_faces
1220        .iter()
1221        .zip(&second_restricted)
1222        .map(|(tagged, restricted)| {
1223            face_bounds(
1224                tagged.face,
1225                restricted.as_ref().map(|window| &window.surface),
1226                &builder.edges,
1227                tagged.operand,
1228                options.tolerance,
1229            )
1230        })
1231        .collect::<Result<Vec<_>, _>>()?;
1232    let second_bvh = Bvh::build(&second_bounds);
1233    // Per-face classifier data and edge lists/subcurves, computed once and
1234    // reused across every pair the face participates in.
1235    let first_classify = first_faces
1236        .iter()
1237        .map(|tagged| SurfaceClassifyData::build(&tagged.face.surface, options.tolerance))
1238        .collect::<Result<Vec<_>, _>>().or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
1239    let second_classify = second_faces
1240        .iter()
1241        .map(|tagged| SurfaceClassifyData::build(&tagged.face.surface, options.tolerance))
1242        .collect::<Result<Vec<_>, _>>().or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
1243    let mut face_edge_lists: HashMap<FaceKey, Vec<&EdgeRecord>> = HashMap::default();
1244    for tagged in first_faces.iter().chain(second_faces.iter()) {
1245        face_edge_lists.insert(tagged.key(), face_edges(*tagged, &builder.edges)?);
1246    }
1247    let mut subcurves: HashMap<(u8, u64), NurbsCurve> = HashMap::default();
1248    for (&key, edge) in &builder.edges {
1249        if !edge.degenerate {
1250            // Failures fall through: the use sites recompute and surface the
1251            // original error exactly where the uncached code did.
1252            if let Ok(curve) = edge_subcurve(edge) {
1253                subcurves.insert(key, curve);
1254            }
1255        }
1256    }
1257    let cached_subcurve = |operand: u8, edge: &EdgeRecord| -> Result<NurbsCurve, KernelRefusal> {
1258        match subcurves.get(&(operand, edge.id)) {
1259            Some(curve) => Ok(curve.clone()),
1260            None => edge_subcurve(edge),
1261        }
1262    };
1263    let mut profile = ImprintProfile::new();
1264    let mut tangent_nodes: Vec<Vec3> = Vec::new();
1265    let mut cosurface_pairs: Vec<(FaceKey, FaceKey)> = Vec::new();
1266    let mut paired = Vec::new();
1267    for (first_index, first) in first_faces.iter().enumerate() {
1268        let first = *first;
1269        paired.clear();
1270        second_bvh.overlapping(
1271            first_bounds[first_index],
1272            options.tolerance * 100.0,
1273            &mut paired,
1274        );
1275        paired.sort_unstable();
1276        let debug_pairs = std::env::var("BREP_DEBUG_PAIRS").is_ok();
1277        if debug_pairs {
1278            eprintln!(
1279                "bvh first_face={} -> {} candidate pairs {:?}",
1280                first.face.id,
1281                paired.len(),
1282                paired
1283                    .iter()
1284                    .map(|&index| second_faces[index].face.id)
1285                    .collect::<Vec<_>>()
1286            );
1287        }
1288        for &second_index in &paired {
1289            let second = second_faces[second_index];
1290            profile.pairs += 1;
1291            // Set when this pair's section passes through an isolated TANGENT
1292            // NODE inside both trims (see the classification below). The march
1293            // is attempted anyway — that is the whole point — but a marcher
1294            // that cannot get through a node must refuse in the tangent-node
1295            // class naming the node, not leak its own step-budget message.
1296            let mut tangent_node: Option<Vec3> = None;
1297            let mut lap_start = None;
1298            profile.lap(&mut lap_start);
1299            let pair_classification = classify_surface_pair_cached(
1300                &first.face.surface,
1301                &first_classify[first_index],
1302                &second.face.surface,
1303                &second_classify[second_index],
1304                options.tolerance,
1305                PAIR_ANGULAR_TOLERANCE,
1306            ).or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
1307            profile.classify += profile.lap(&mut lap_start);
1308            if pair_classification.relation == SurfacePairRelation::Disjoint {
1309                if debug_pairs {
1310                    eprintln!(
1311                        "pair {}x{}: DISJOINT-cull sep={:.4e}",
1312                        first.face.id, second.face.id, pair_classification.minimum_separation
1313                    );
1314                }
1315                continue;
1316            }
1317            // Coincident carriers are never marched: their true intersection
1318            // is a 2D region, not a curve, so anything the marcher traces on
1319            // them is noise ("similar faces we do not intersect" — Golovanov
1320            // §6.2).  The sampled classification catches coincident pairs
1321            // the strict reconstruction test misses (partial overlaps,
1322            // differing parameterizations); both route to the boundary-curve
1323            // exchange below.
1324            let is_cosurface = pair_classification.relation == SurfacePairRelation::Cosurface
1325                || cosurface_pair(&first.face.surface, &second.face.surface, options.tolerance)?;
1326            profile.cosurface += profile.lap(&mut lap_start);
1327            if is_cosurface {
1328                if debug_pairs {
1329                    eprintln!("pair {}x{}: cosurface", first.face.id, second.face.id);
1330                }
1331                cosurface_pairs.push((first.key(), second.key()));
1332                for edge in &face_edge_lists[&second.key()] {
1333                    if !edge.degenerate {
1334                        builder.process_curve(
1335                            cached_subcurve(second.operand, edge)?,
1336                            first,
1337                            second,
1338                            &[first],
1339                            &[first],
1340                            false,
1341                        )?;
1342                    }
1343                }
1344                for edge in &face_edge_lists[&first.key()] {
1345                    if !edge.degenerate {
1346                        builder.process_curve(
1347                            cached_subcurve(first.operand, edge)?,
1348                            first,
1349                            second,
1350                            &[second],
1351                            &[second],
1352                            false,
1353                        )?;
1354                    }
1355                }
1356                profile.process_curve += profile.lap(&mut lap_start);
1357                continue;
1358            }
1359
1360            // The exact section lanes below (planar-iso, analytic) mint this
1361            // pair's whole intersection, the part over either trim included, so
1362            // a SPAN of an edge lying on the other face would be a second copy
1363            // of a curve they already mint — which reorders the face's pieces
1364            // and with them its fragments' names (the 2026-09-10 chamfer groove
1365            // rim's `Box_PX` / `Box_PX_1`). Where they answer, only a whole
1366            // edge is exchanged, as before.
1367            let planar_iso = planar_iso_intersection(
1368                &first.face.surface,
1369                &second.face.surface,
1370                options.tolerance,
1371            )?;
1372            profile.planar_iso += profile.lap(&mut lap_start);
1373            let analytic = crate::intersect_analytic_pair(
1374                &first.face.surface,
1375                &second.face.surface,
1376                options.tolerance,
1377            );
1378            profile.analytic += profile.lap(&mut lap_start);
1379            let exact_section = planar_iso.is_some() || analytic.is_some();
1380
1381            for edge in &face_edge_lists[&first.key()] {
1382                if !edge.degenerate {
1383                    let curve = cached_subcurve(first.operand, edge)?;
1384                    let spans = curve_spans_on_face(curve, second.face, &face_edge_lists[&second.key()], options.tolerance, exact_section)?;
1385                    for span in spans {
1386                        builder.process_curve(span, first, second, &[second], &[second], false)?;
1387                    }
1388                }
1389            }
1390            for edge in &face_edge_lists[&second.key()] {
1391                if !edge.degenerate {
1392                    let curve = cached_subcurve(second.operand, edge)?;
1393                    let spans = curve_spans_on_face(curve, first.face, &face_edge_lists[&first.key()], options.tolerance, exact_section)?;
1394                    for span in spans {
1395                        builder.process_curve(span, first, second, &[first], &[first], false)?;
1396                    }
1397                }
1398            }
1399            profile.lies_on += profile.lap(&mut lap_start);
1400            // CENSUS ONLY (`BREP_FIT_CENSUS=1`): the lane this pair takes under
1401            // the count-keyed and the geometry-keyed gates, and — when it is
1402            // marched here and answered exactly there — the point-set comparison
1403            // of the two, printed when `pair_census` is dropped. `imprint/gate_census.rs`.
1404            let mut pair_census = if fit_census_enabled() {
1405                census_pair(first, second, options.tolerance)?
1406            } else {
1407                None
1408            };
1409            if let Some(curve) = planar_iso {
1410                if debug_pairs {
1411                    eprintln!("pair {}x{}: planar_iso", first.face.id, second.face.id);
1412                }
1413                builder.process_curve(
1414                    curve,
1415                    first,
1416                    second,
1417                    &[first, second],
1418                    &[first, second],
1419                    true,
1420                )?;
1421                profile.process_curve += profile.lap(&mut lap_start);
1422                continue;
1423            }
1424
1425            // Recognized analytic pairs produce their exact intersection
1426            // curves (lines, circles, ellipses) directly — no marching, no
1427            // polyline fitting, no chord-sag drift. An empty result is a
1428            // proof of non-intersection and also skips the marcher. (Read
1429            // above, with the planar-iso curve.)
1430            if let Some(curves) = analytic {
1431                if debug_pairs {
1432                    eprintln!(
1433                        "pair {}x{}: analytic x{}",
1434                        first.face.id,
1435                        second.face.id,
1436                        curves.len()
1437                    );
1438                }
1439                for curve in curves {
1440                    builder.process_curve(
1441                        curve,
1442                        first,
1443                        second,
1444                        &[first, second],
1445                        &[first, second],
1446                        true,
1447                    )?;
1448                }
1449                profile.process_curve += profile.lap(&mut lap_start);
1450                continue;
1451            }
1452
1453            // A pair whose every touching sample is TANGENTIAL cannot
1454            // contain a transverse intersection curve: the marcher would
1455            // walk the tangency band's noise, which neither closes nor
1456            // reaches a boundary (the distance-0 glue-extrude runaway).
1457            // Shared topology at a tangential contact comes from the
1458            // boundary-curve exchange above ("similar faces we do not
1459            // intersect" extended to tangential contacts — Golovanov §6.2;
1460            // where surfaces CROSS through a tangency, off-line samples
1461            // have non-parallel normals and the pair still marches).
1462            if pair_classification.relation == SurfacePairRelation::NearTangent
1463                && pair_classification.tangential_only
1464            {
1465                // The coarse 5×5 classifier can flag `tangential_only` off an
1466                // incidental tangential KISS between two curved carriers and
1467                // miss the transverse loop where they actually cross (two
1468                // overlapping tori: their tubes cross while their inner walls
1469                // just touch). Before honouring the skip, a GATED supplemental
1470                // detector (denser two-sided seeding, tangency-band seeds
1471                // rejected, trace-exhaustion swallowed, transverse-only
1472                // branches) checks whether a genuine transverse curve of
1473                // meaningful length lies inside BOTH trims.
1474                //
1475                // Such a pair's intersection is SINGULAR where a tangency
1476                // sits on it, and what the imprint can do about that depends
1477                // entirely on the SHAPE of the tangency — see
1478                // `imprint/tangent_contact.rs`. An isolated NODE (two branches
1479                // crossing, as every equal-radius pair produces) is already
1480                // assembled correctly by the ordinary march plus the 2D
1481                // arrangement's pinch carving; an EXTENDED contact along a
1482                // whole curve has no section to imprint at all and tears the
1483                // shell. Both are classified below and only the second is
1484                // refused. Skipping either silently would DROP the
1485                // intersection (torus∪torus double-counts; torus−torus removes
1486                // nothing — the two structural bugs the semantic oracle
1487                // found), so nothing here ever falls through quietly.
1488                let first_march = first_restricted[first_index]
1489                    .as_ref()
1490                    .map(|window| &window.surface)
1491                    .unwrap_or(&first.face.surface);
1492                let second_march = second_restricted[second_index]
1493                    .as_ref()
1494                    .map(|window| &window.surface)
1495                    .unwrap_or(&second.face.surface);
1496                let supplemental = intersect_surfaces_supplemental(
1497                    first_march,
1498                    second_march,
1499                    &SurfaceIntersectionOptions {
1500                        tolerance: options.tolerance,
1501                        maximum_step: options.maximum_ssi_step,
1502                        ..Default::default()
1503                    },
1504                ).or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
1505                let mut dropped_length = 0.0f64;
1506                let mut clipped: Vec<Vec<Vec3>> = Vec::new();
1507                for branch in &supplemental {
1508                    for run in clip_branch_to_trims(&branch.points, first, second)? {
1509                        if run.len() < 2 {
1510                            continue;
1511                        }
1512                        let length: f64 = run
1513                            .windows(2)
1514                            .map(|pair| pair[1].sub(pair[0]).length())
1515                            .sum();
1516                        dropped_length = dropped_length.max(length);
1517                        clipped.push(run);
1518                    }
1519                }
1520                if dropped_length <= options.tolerance * 100.0 {
1521                    if debug_pairs {
1522                        eprintln!(
1523                            "pair {}x{}: tangential-only contact, march skipped",
1524                            first.face.id, second.face.id
1525                        );
1526                    }
1527                    continue;
1528                }
1529                // A transverse curve EXISTS. What would make it unimprintable is
1530                // a tangent NODE on it — and the node need not lie inside the
1531                // trims. Two equal-radius pipe arms are tangent to each other
1532                // where their axes' common perpendicular leaves the junction,
1533                // and a joint ball wider than the arms trims that crotch off
1534                // both faces; what is left inside the trims is an ordinary
1535                // crossing. So the question is not "does a transverse curve
1536                // exist" but "does the curve INSIDE BOTH TRIMS reach a
1537                // tangency". Only the latter is the checkerboard case.
1538                let mut tangency_inside_trims = false;
1539                let mut tangency_witness = Vec3::default();
1540                'clipped: for run in &clipped {
1541                    for (index, &point) in run.iter().enumerate() {
1542                        if pair_normals_parallel_at(first, second, point)? {
1543                            tangency_inside_trims = true;
1544                            tangency_witness = point;
1545                            if debug_pairs {
1546                                eprintln!(
1547                                    "pair {}x{}: TANGENCY at run index {}/{} ({:.6},{:.6},{:.6}) dropped_len={:.4e}",
1548                                    first.face.id, second.face.id, index, run.len(),
1549                                    point.x, point.y, point.z, dropped_length
1550                                );
1551                            }
1552                            break 'clipped;
1553                        }
1554                    }
1555                }
1556                if tangency_inside_trims {
1557                    // WHAT SHAPE is the tangency? The witness is only a marched
1558                    // sample within the transverse-seed angular gate, so it is
1559                    // REFINED onto the contact before being classified — at the
1560                    // raw witness a G1 cylinder/torus join and a genuine
1561                    // equal-radius node are eighteen-fold apart, which would be
1562                    // a band; at the refined contact they are thirty orders
1563                    // apart, which is a rank question. See
1564                    // `imprint/tangent_contact.rs`.
1565                    let contact = classify_tangent_contact(
1566                        &first.face.surface,
1567                        &second.face.surface,
1568                        tangency_witness,
1569                        options.tolerance,
1570                    )?;
1571                    if debug_pairs {
1572                        eprintln!(
1573                            "pair {}x{}: contact classification {:?}",
1574                            first.face.id, second.face.id, contact
1575                        );
1576                    }
1577                    match contact {
1578                        // An isolated node: the section is a curve everywhere
1579                        // but that one point, so the ordinary march below runs
1580                        // and the 2D arrangement carves the pinch on both
1581                        // faces. `tangent_node` records it so a march that
1582                        // cannot get through still refuses in THIS class rather
1583                        // than leaking the marcher's own message.
1584                        Some(contact) if contact.is_isolated_node() => {
1585                            tangent_node = Some(contact.point);
1586                            tangent_nodes.push(contact.point);
1587                        }
1588                        // An extended contact (rank-deficient) or a witness we
1589                        // could not refine onto any contact at all (None — so
1590                        // nothing is proven and the pre-classification refusal
1591                        // stands). Neither has a section curve the imprint can
1592                        // represent.
1593                        other => {
1594                            let shape = match other {
1595                                Some(contact) => contact.describe(),
1596                                None => format!(
1597                                    "the tangency near ({:.6},{:.6},{:.6}) could not be refined \
1598                                     onto a contact, so its shape is unproven",
1599                                    tangency_witness.x, tangency_witness.y, tangency_witness.z
1600                                ),
1601                            };
1602                            return Err(KernelRefusal::new(
1603                                RefusalClass::TangentNodeSingularity,
1604                                KernelStage::Intersect,
1605                                format!(
1606                                    "boolean: unsupported singular/tangent-node surface intersection \
1607                                     between faces {} and {}: {shape}",
1608                                    first.face.id, second.face.id
1609                                ),
1610                            ));
1611                        }
1612                    }
1613                }
1614                if debug_pairs && tangent_node.is_none() {
1615                    // Transverse the whole way inside both trims: the coarse
1616                    // 5x5 classifier only saw the tangency the trims cut away.
1617                    eprintln!(
1618                        "pair {}x{}: classified tangential-only, but the curve inside both \
1619                         trims is transverse ({dropped_length:.4}) — marching",
1620                        first.face.id, second.face.id
1621                    );
1622                }
1623            }
1624
1625            // The trim-window carriers: same surface and parameterization
1626            // over the window, so hit (u, v) values remain valid on the
1627            // originals; out-of-window intersections could never survive
1628            // clip_branch_to_trims and are not walked at all.
1629            let first_march = first_restricted[first_index]
1630                .as_ref()
1631                .map(|window| &window.surface)
1632                .unwrap_or(&first.face.surface);
1633            let second_march = second_restricted[second_index]
1634                .as_ref()
1635                .map(|window| &window.surface)
1636                .unwrap_or(&second.face.surface);
1637            let mut seed_points = Vec::new();
1638            // Smallest |edge_tangent · surface_normal| over accepted seeds — the
1639            // local grazing measure at the trim crossings (near 0 = the edge
1640            // pierces the other surface tangentially). Gates the near-tangent
1641            // clip-order rescue below to genuinely grazing pairs.
1642            let mut min_seed_tangency = f64::INFINITY;
1643            // Two grazing crossings of one trim edge bound a real, narrow
1644            // interval. It cannot tolerate a section fit that misses either
1645            // crossing, even when a coarse whole-model distance looks small.
1646            let mut paired_grazing_crossings = false;
1647            for (face, other_march, other) in
1648                [(first, second_march, second), (second, first_march, first)]
1649            {
1650                for edge in &face_edge_lists[&face.key()] {
1651                    if edge.degenerate {
1652                        continue;
1653                    }
1654                    let mut grazing_crossings: Vec<Vec3> = Vec::new();
1655                    for hit in intersect_curve_surface(&edge.curve, other_march, options.tolerance).or_refuse(KernelStage::Intersect, "intersect_curve_surface")?
1656                    {
1657                        if hit.t < edge.t0 - 1e-9 || hit.t > edge.t1 + 1e-9 {
1658                            continue;
1659                        }
1660                        let derivative = edge.curve.derivatives(hit.t, 1).or_refuse(KernelStage::Intersect, "derivatives")?[1];
1661                        // A STATIONARY POINT on the edge curve is not a reason
1662                        // to refuse the boolean. An involute flank starts at
1663                        // the base circle with an exactly zero tangent, so a
1664                        // gear tooth's profile carries one cusp per flank, and
1665                        // a hit landing on that parameter used to propagate
1666                        // "Vec3.normalized: zero-length vector" out of the
1667                        // whole union (the 2026-09-14 herringbone report: the
1668                        // second tooth band's z=0 cap edge meets the first
1669                        // band's flank exactly at the cusp). The direction is
1670                        // still defined as the one-sided limit, so read it from
1671                        // just inside the edge's own range; an edge with no
1672                        // direction at all simply contributes no seed, exactly
1673                        // as a hit whose face normal is unavailable already
1674                        // does two lines below. Seeds are marcher hints, so a
1675                        // dropped one costs a hint, never an answer.
1676                        // Escape hatch for tamper-verification:
1677                        // BREP_CUSP_TANGENT_RESCUE=0 restores the refusal.
1678                        let tangent = match derivative.normalized() {
1679                            Ok(tangent) => tangent,
1680                            Err(error) => {
1681                                if std::env::var("BREP_CUSP_TANGENT_RESCUE").as_deref() == Ok("0") {
1682                                    return Err(error)
1683                                        .or_refuse(KernelStage::Intersect, "normalized");
1684                                }
1685                                match edge.curve.stationary_tangent_rescue(hit.t, edge.t0, edge.t1)
1686                                {
1687                                    Some(tangent) => tangent,
1688                                    None => continue,
1689                                }
1690                            }
1691                        };
1692                        // `hit` was solved on the other face's MARCH WINDOW, so
1693                        // its uv is in that face's chart — a period off the
1694                        // carrier's where the window is lifted, and `normal`
1695                        // clamps a parameter past its domain.
1696                        let normal = match other.chart().normal(hit.u, hit.v) {
1697                            Ok(normal) => normal,
1698                            Err(_) => continue,
1699                        };
1700                        if debug_pairs {
1701                            eprintln!(
1702                                "pair {}x{}: seed edge {} t={:.6} p=({:.5},{:.5},{:.5}) |tan.n|={:.4} {}",
1703                                first.face.id,
1704                                second.face.id,
1705                                edge.id,
1706                                hit.t,
1707                                hit.point.x,
1708                                hit.point.y,
1709                                hit.point.z,
1710                                tangent.dot(normal).abs(),
1711                                if tangent.dot(normal).abs() >= 0.1 { "ACCEPT" } else { "reject" }
1712                            );
1713                        }
1714                        if tangent.dot(normal).abs() >= 0.1 {
1715                            seed_points.push(hit.point);
1716                            min_seed_tangency = min_seed_tangency.min(tangent.dot(normal).abs());
1717                            section_evidence = true;
1718                        } else {
1719                            if grazing_crossings.iter().any(|point| {
1720                                point.sub(hit.point).length() > assembler_weld(options.tolerance)
1721                            }) {
1722                                paired_grazing_crossings = true;
1723                            }
1724                            grazing_crossings.push(hit.point);
1725                        }
1726                    }
1727                }
1728            }
1729            profile.seeds += profile.lap(&mut lap_start);
1730            profile.marched_pairs += 1;
1731            if debug_pairs {
1732                eprintln!(
1733                    "pair {}x{}: marching (relation {:?})...",
1734                    first.face.id, second.face.id, pair_classification.relation
1735                );
1736            }
1737            let marched = intersect_surfaces(
1738                first_march,
1739                second_march,
1740                &SurfaceIntersectionOptions {
1741                    tolerance: options.tolerance,
1742                    maximum_step: march_maximum_step(
1743                        &pair_classification,
1744                        options.maximum_ssi_step,
1745                        builder.scale,
1746                    ),
1747                    seed_points: seed_points.clone(),
1748                    ..Default::default()
1749                },
1750            );
1751            // A pair carrying a tangent node keeps its OWN refusal class when
1752            // the march fails: the node is why the trace cannot close, and the
1753            // equal-radius torus pair depends on being told so rather than on
1754            // reading a step-budget message it cannot act on.
1755            let marched = match (marched, tangent_node) {
1756                (Err(error), Some(node)) => {
1757                    return Err(KernelRefusal::new(
1758                        RefusalClass::TangentNodeSingularity,
1759                        KernelStage::Intersect,
1760                        format!(
1761                            "boolean: unsupported singular/tangent-node surface intersection \
1762                             between faces {} and {}: the section through the tangent node at \
1763                             ({:.6},{:.6},{:.6}) could not be marched ({error})",
1764                            first.face.id, second.face.id, node.x, node.y, node.z
1765                        ),
1766                    ));
1767                }
1768                (marched, _) => marched,
1769            };
1770            let mut branches = marched
1771            .map_err(|error| {
1772                format!(
1773                    "{error} (marching faces {} and {})",
1774                    first.face.id, second.face.id
1775                )
1776            }).or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
1777            profile.march += profile.lap(&mut lap_start);
1778            let rescue_started = profile.enabled.then(Instant::now);
1779            // MARCH-ORDER SWAP RESCUE (hatch BREP_MARCH_SWAP_RESCUE=0).
1780            // `intersect_surfaces` is not order-symmetric: the coupled Newton
1781            // trace can fail to start/continue from a valid seed when the two
1782            // surfaces are presented in one operand order yet succeed in the
1783            // other. This is the sole reason t217's `subtract(sphere, step)`
1784            // fails while every other op passes — pair 118x842 (sphere-first)
1785            // marches ZERO branches, while 842x118 (step-first, the working
1786            // a\b order) marches the section from the IDENTICAL accepted pierce
1787            // seeds. When the forward order returns no branch AND an accepted
1788            // transverse pierce seed exists (so a real section provably crosses
1789            // both trims), retry the march with the surfaces swapped — i.e.
1790            // reproduce the exact call the working operand order makes for this
1791            // pair (measured: seed_only alone does NOT recover it — the section
1792            // is found from an auto-grid start, not from the near-tangent pierce
1793            // seeds, which the seed normal-cross gate rejects). The returned
1794            // branch points are 3D and therefore order-independent, so no
1795            // parameter remap is needed; they flow through the identical clip /
1796            // process_curve gates below, which drop any out-of-trim or
1797            // sub-length run exactly as today. The rescue only ever runs when
1798            // the forward order found NOTHING, so it cannot alter a pair that
1799            // already marched.
1800            if branches.is_empty()
1801                && !seed_points.is_empty()
1802                && pair_classification.relation == SurfacePairRelation::Candidate
1803                && std::env::var("BREP_MARCH_SWAP_RESCUE").as_deref() != Ok("0")
1804            {
1805                // FAIL-SOFT: the forward order already returned Ok(empty); a
1806                // swapped-march error (trace-exhaustion is a real error class —
1807                // `intersect_surfaces_supplemental` swallows it for exactly this
1808                // reason) must NOT convert that graceful empty into a hard error.
1809                // On Err, keep the (empty) forward result and carry on.
1810                let swapped = intersect_surfaces(
1811                    second_march,
1812                    first_march,
1813                    &SurfaceIntersectionOptions {
1814                        tolerance: options.tolerance,
1815                        maximum_step: march_maximum_step(
1816                            &pair_classification,
1817                            options.maximum_ssi_step,
1818                            builder.scale,
1819                        ),
1820                        seed_points: seed_points.clone(),
1821                        ..Default::default()
1822                    },
1823                )
1824                .unwrap_or_default();
1825                if debug_pairs {
1826                    eprintln!(
1827                        "pair {}x{}: MARCH-SWAP RESCUE attempt -> {} branches, pts {:?}",
1828                        first.face.id,
1829                        second.face.id,
1830                        swapped.len(),
1831                        swapped.iter().map(|b| b.points.len()).collect::<Vec<_>>()
1832                    );
1833                }
1834                if !swapped.is_empty() {
1835                    branches = swapped;
1836                }
1837            }
1838            // NEAR-TANGENT CLIP-ORDER RESCUE (hatch BREP_MARCH_SWAP_CLIP_RESCUE=0).
1839            // Companion to the empty-branch MARCH-SWAP RESCUE above:
1840            // `intersect_surfaces` is order-asymmetric not only in WHETHER it
1841            // marches, but in the exact sample positions of the section
1842            // polyline. On a NEAR-TANGENT graze, `clip_branch_to_trims`
1843            // classifies those samples against the mutual trims, and a sample
1844            // landing just past the near-tangent boundary is dropped as a
1845            // false-Outside that TRUNCATES the clipped section. The two operand
1846            // orders drop DIFFERENT near-tangent tail samples, so one order
1847            // yields a section ~0.1-0.2mm shorter at ONE endpoint (t660: b\a's
1848            // step-first order clips pairs 223x105/399x105 ~0.22/0.14mm short of
1849            // the section a\b's cyl-first order keeps, stranding edge 173
1850            // one-use). Near-tangent clip errors are almost exclusively
1851            // false-Outside (a point truly outside a trim rarely projects to an
1852            // in-trim uv), so the order with the LONGER clipped section suffered
1853            // fewer drops and is the more complete one. When the swapped order
1854            // marches the SAME branch structure with a meaningfully longer
1855            // clipped total, adopt its branches (3D points, so they flow through
1856            // the identical clip/process_curve gates below). Gated to Candidate
1857            // pairs (the near-tangent/ambiguous class; clean Transverse
1858            // crossings clip identically in both orders and never differ) with
1859            // an accepted GRAZING pierce seed (min |tan·n| < 0.5 — a real
1860            // section provably crosses, and does so near-tangentially, the only
1861            // regime where the clip wobbles; this also bounds the extra march to
1862            // grazing pairs), and requires a >0.1%-of-length margin so
1863            // raw-sampling jitter (t660: 0.02%) cannot flip a clean pair.
1864            // Fail-soft: any swapped-march or comparison-clip error keeps the
1865            // forward result.
1866            if !branches.is_empty()
1867                && !seed_points.is_empty()
1868                && min_seed_tangency < 0.5
1869                && pair_classification.relation == SurfacePairRelation::Candidate
1870                && std::env::var("BREP_MARCH_SWAP_CLIP_RESCUE").as_deref() != Ok("0")
1871            {
1872                let swapped = intersect_surfaces(
1873                    second_march,
1874                    first_march,
1875                    &SurfaceIntersectionOptions {
1876                        tolerance: options.tolerance,
1877                        maximum_step: march_maximum_step(
1878                            &pair_classification,
1879                            options.maximum_ssi_step,
1880                            builder.scale,
1881                        ),
1882                        seed_points: seed_points.clone(),
1883                        ..Default::default()
1884                    },
1885                )
1886                .unwrap_or_default();
1887                // Only a swap that reproduces the SAME branch count — a
1888                // refined-endpoint variant of the same section, not a different
1889                // branch decomposition (guards against adopting a spurious
1890                // extra branch as "longer").
1891                if !swapped.is_empty() && swapped.len() == branches.len() {
1892                    let totals = (|| -> Result<(f64, f64), KernelRefusal> {
1893                        let mut fwd = 0.0;
1894                        for b in &branches {
1895                            let refined = insert_seed_points_into_branch(
1896                                &b.points,
1897                                &seed_points,
1898                                options.tolerance,
1899                            );
1900                            for run in clip_branch_to_trims(&refined, first, second)? {
1901                                fwd += run
1902                                    .windows(2)
1903                                    .map(|p| p[1].sub(p[0]).length())
1904                                    .sum::<f64>();
1905                            }
1906                        }
1907                        let mut swp = 0.0;
1908                        for b in &swapped {
1909                            let refined = insert_seed_points_into_branch(
1910                                &b.points,
1911                                &seed_points,
1912                                options.tolerance,
1913                            );
1914                            for run in clip_branch_to_trims(&refined, first, second)? {
1915                                swp += run
1916                                    .windows(2)
1917                                    .map(|p| p[1].sub(p[0]).length())
1918                                    .sum::<f64>();
1919                            }
1920                        }
1921                        Ok((fwd, swp))
1922                    })();
1923                    if let Ok((fwd_clip, swp_clip)) = totals {
1924                        let margin = fwd_clip.max(swp_clip) * 1.0e-3;
1925                        let adopt = swp_clip > fwd_clip + margin;
1926                        if debug_pairs {
1927                            eprintln!(
1928                                "pair {}x{}: CLIP-SWAP RESCUE fwd_clip={:.6} swp_clip={:.6} margin={:.6}{}",
1929                                first.face.id,
1930                                second.face.id,
1931                                fwd_clip,
1932                                swp_clip,
1933                                margin,
1934                                if adopt { " ADOPT" } else { "" }
1935                            );
1936                        }
1937                        if adopt {
1938                            branches = swapped;
1939                        }
1940                    }
1941                }
1942            }
1943            if let Some(started) = rescue_started {
1944                profile.rescue_march += started.elapsed();
1945            }
1946            if branches.iter().any(|branch| branch.points.len() >= 2) {
1947                section_evidence = true;
1948            }
1949            if debug_pairs {
1950                eprintln!(
1951                    "pair {}x{}: MARCH {} branches, pts {:?}",
1952                    first.face.id,
1953                    second.face.id,
1954                    branches.len(),
1955                    branches.iter().map(|b| b.points.len()).collect::<Vec<_>>()
1956                );
1957            }
1958            for branch in branches {
1959                // The pierce seeds are the section's exact trim-crossing
1960                // points — insert them so no trim interval shorter than the
1961                // march step is invisible to the point-classification clip.
1962                let refined_points =
1963                    insert_seed_points_into_branch(&branch.points, &seed_points, options.tolerance);
1964                let clip_started = profile.enabled.then(Instant::now);
1965                let runs = clip_branch_to_trims(&refined_points, first, second)?;
1966                if debug_pairs {
1967                    let dups = |points: &[Vec3]| points.windows(2).enumerate().filter(|(_, pair)| pair[1].sub(pair[0]).length() <= options.tolerance).map(|(i, pair)| format!("{i}:{:.2e}", pair[1].sub(pair[0]).length())).collect::<Vec<_>>();
1968                    eprintln!(
1969                        "pair {}x{}: DUPS branch {:?} seeded {:?} runs {:?} seeds {} branch_pts {} seeded_pts {}",
1970                        first.face.id, second.face.id, dups(&branch.points), dups(&refined_points), runs.iter().map(|run| dups(run)).collect::<Vec<_>>(), seed_points.len(), branch.points.len(), refined_points.len()
1971                    );
1972                }
1973                if let Some(started) = clip_started {
1974                    profile.clip += started.elapsed();
1975                }
1976                for run in runs {
1977                    if debug_pairs {
1978                        eprintln!(
1979                            "pair {}x{}: clip run len_pts={} length={:.4e}",
1980                            first.face.id,
1981                            second.face.id,
1982                            run.len(),
1983                            run.windows(2)
1984                                .map(|pair| pair[1].sub(pair[0]).length())
1985                                .sum::<f64>()
1986                        );
1987                    }
1988                    if run.len() < 2 {
1989                        continue;
1990                    }
1991                    let length: f64 = run
1992                        .windows(2)
1993                        .map(|pair| pair[1].sub(pair[0]).length())
1994                        .sum();
1995                    if length <= options.tolerance * 100.0 {
1996                        continue;
1997                    }
1998                    let shared_started = profile.enabled.then(Instant::now);
1999                    let follows_shared = branch_follows_shared_boundary(
2000                        &run,
2001                        first,
2002                        second,
2003                        &builder.edges,
2004                        options.tolerance,
2005                        builder.scale,
2006                    )?;
2007                    if let Some(started) = shared_started {
2008                        profile.shared_boundary += started.elapsed();
2009                    }
2010                    if follows_shared {
2011                        if debug_pairs {
2012                            eprintln!(
2013                                "pair {}x{}: run dropped (follows shared boundary)",
2014                                first.face.id, second.face.id
2015                            );
2016                        }
2017                        continue;
2018                    }
2019                    if let Some(census) = pair_census.as_mut() {
2020                        census.record_stations(&run);
2021                    }
2022                    let pieces_before = builder.pieces.len();
2023                    let chunk_points = options.fit_chunk_points.unwrap_or(run.len()).max(2);
2024                    let mut start = 0;
2025                    while start + 1 < run.len() {
2026                        let end = (start + chunk_points - 1).min(run.len() - 1);
2027                        let fit_started = profile.enabled.then(Instant::now);
2028                        let fit_tolerance = options.tolerance.max(1e-7);
2029                        let (mut section, mut fit, recovered, evening) = even_sliver_ends_measured(
2030                            run[start..=end].to_vec(),
2031                            &first.face.surface,
2032                            &second.face.surface,
2033                            options.tolerance,
2034                            fit_tolerance,
2035                            options.maximum_fit_points,
2036                            options.local_fit,
2037                        )
2038                        .or_refuse(KernelStage::Intersect, "csg.imprint.driver.sliver_end")?;
2039                        if evening != SliverEvening::NoSliver {
2040                            if let Ok(path) = std::env::var("BREP_SECTION_SLIVER_CENSUS") {
2041                                use std::io::Write;
2042                                if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
2043                                    let _ = writeln!(file, "sliver-end census: pair {}x{} {evening:?} stations {}", first.face.id, second.face.id, section.len());
2044                                }
2045                            }
2046                        }
2047                        // A recovered fit may reproduce every march point yet
2048                        // still wander off the carriers between sparse stations.
2049                        // Measure it against both supports before accepting that
2050                        // recovery, and use the existing bounded SSI refiner.
2051                        let met_requested = fit.report.met_requested_tolerance();
2052                        let refine_route = section_refine_route(
2053                            section_refine_gate(),
2054                            recovered,
2055                            || {
2056                                paired_grazing_crossings
2057                                    && std::env::var("BREP_GRAZE_SECTION_REFINE").as_deref() != Ok("0")
2058                            },
2059                            met_requested,
2060                        )
2061                        .or_else(|| {
2062                            // `BREP_SECTION_CARRIER_MISS=0` restores the fast path
2063                            // for an attribution A/B only (default on).
2064                            if std::env::var("BREP_SECTION_CARRIER_MISS").as_deref() == Ok("0") {
2065                                return None;
2066                            }
2067                            let (station_upper, midspan_upper) =
2068                                fast_path_carrier_readings(&fit, &section, &first.face.surface, &second.face.surface);
2069                            carrier_miss_route(station_upper, midspan_upper, fit_tolerance)
2070                        });
2071                        if let Some((gate, route)) = refine_route {
2072                            let stations_before = fit.kept.len();
2073                            let refine_census = std::env::var("BREP_SECTION_REFINE_CENSUS").ok();
2074                            let (census_head, census_tail) = (section[0], section[section.len() - 1]);
2075                            let refine_started = refine_census.is_some().then(Instant::now);
2076                            // The wrapper's own two lines, inlined so the receipt
2077                            // can be read (behaviour identical to
2078                            // `refine_section_against_carriers`).
2079                            let representatives = std::env::var("BREP_SECTION_REPRESENTATIVES").as_deref() != Ok("0");
2080                            let mut receipt = RefineReceipt::default();
2081                            let (refined, worst, unrefined, rounds, exit) = refine_section_against_carriers_with(
2082                                &mut section,
2083                                fit,
2084                                &first.face.surface,
2085                                &second.face.surface,
2086                                fit_tolerance,
2087                                options.tolerance,
2088                                options.local_fit,
2089                                gate * fit_tolerance,
2090                                representatives,
2091                                &mut receipt,
2092                            ).or_refuse(KernelStage::Intersect, "csg.imprint.driver.midspan")?;
2093                            let _ = &receipt;
2094                            // `BREP_SECTION_REFINE_CENSUS=<file>`: one line per section,
2095                            // appended, so a corpus replay (whose children's stderr is
2096                            // captured) can be read for trip counts and growth.
2097                            // The route, whether the standing fit met its request, and
2098                            // the refiner's own wall time close the line, so a run can
2099                            // total the UNMET-REQUEST route's cost apart from the
2100                            // global gate's (which reads every section).
2101                            if let Some(path) = refine_census {
2102                                use std::io::Write;
2103                                if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
2104                                    let _ = writeln!(
2105                                        file,
2106                                        "section-refine census: pair {}x{} exit {:?} rounds {} stations {} -> {} unrefined {:.3e} worst {:.3e} route {:?} met_requested {} ms {:.3} head ({:.9}, {:.9}, {:.9}) tail ({:.9}, {:.9}, {:.9})",
2107                                        first.face.id, second.face.id, exit, rounds, stations_before, refined.kept.len(), unrefined, worst,
2108                                        route, met_requested, refine_started.map_or(0.0, |started| started.elapsed().as_secs_f64() * 1e3),
2109                                        census_head.x, census_head.y, census_head.z, census_tail.x, census_tail.y, census_tail.z
2110                                    );
2111                                }
2112                            }
2113                            if debug_pairs && (rounds > 0 || !matches!(exit, MidspanExit::Converged | MidspanExit::BelowGate)) {
2114                                eprintln!(
2115                                    "pair {}x{}: mid-span refinement {:?} after {} round(s): {} stations, worst {:.3e}",
2116                                    first.face.id, second.face.id, exit, rounds, section.len(), worst
2117                                );
2118                            }
2119                            fit = refined;
2120                        }
2121                        // `BREP_SECTION_OVERSHOOT_CENSUS=<file>` (debug only; nothing
2122                        // reads it): EVERY section's final fit, on any route, is read
2123                        // against both carriers at its kept stations and at its
2124                        // mid-spans, so a fit that stands further off the carriers
2125                        // between its stations than at them (fixture 28 t217: 5.07e-4
2126                        // shipped against a 1.32e-4 worst station) is counted, not
2127                        // guessed. Projector distances are upper readings; the line
2128                        // says so by its field names.
2129                        {
2130                            if let Ok(path) = std::env::var("BREP_SECTION_OVERSHOOT_CENSUS") {
2131                                // An unreadable station makes the whole reading NaN
2132                                // (f64::max would silently drop it).
2133                                let read = |surface: &NurbsSurface, point: Vec3| {
2134                                    project_point_to_surface(surface, point).map_or(f64::NAN, |projection| projection.distance)
2135                                };
2136                                let mut station_worst = 0.0f64;
2137                                for point in &fit.kept {
2138                                    let (a, b) = (read(&first.face.surface, *point), read(&second.face.surface, *point));
2139                                    station_worst = if a.is_nan() || b.is_nan() || station_worst.is_nan() { f64::NAN } else { station_worst.max(a).max(b) };
2140                                }
2141                                let (run_worst, _) = fast_path_carrier_readings(&fit, &section, &first.face.surface, &second.face.surface);
2142                                let interior_worst = knot_interior_carrier_upper(&fit.curve, &first.face.surface, &second.face.surface);
2143                                let misses = midspan_misses_only(&fit, &first.face.surface, &second.face.surface).unwrap_or_default();
2144                                let (worst_span, midspan_worst) = misses
2145                                    .iter()
2146                                    .copied()
2147                                    .enumerate()
2148                                    .fold((usize::MAX, 0.0f64), |best, (span, miss)| if miss > best.1 { (span, miss) } else { best });
2149                                let line = format!(
2150                                    "section-overshoot census: pair {}x{} route {:?} kept {} spans_read {} station_upper {:.3e} midspan_upper {:.3e} worst_span {} fit_tolerance {:.1e} ratio {:.3} run {} run_upper {run_worst:.3e} knot_interior_upper {interior_worst:.3e} head ({:.9}, {:.9}, {:.9}) tail ({:.9}, {:.9}, {:.9})\n",
2151                                    first.face.id, second.face.id, refine_route.map(|(_, route)| route), fit.kept.len(), misses.len(), station_worst, midspan_worst,
2152                                    if worst_span == usize::MAX { -1 } else { worst_span as i64 }, fit_tolerance,
2153                                    midspan_worst / station_worst.max(fit_tolerance),
2154                                    section.len(),
2155                                    section[0].x, section[0].y, section[0].z,
2156                                    section[section.len() - 1].x, section[section.len() - 1].y, section[section.len() - 1].z
2157                                );
2158                                use std::io::Write;
2159                                if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
2160                                    let _ = file.write_all(line.as_bytes());
2161                                }
2162                            }
2163                        }
2164                        if let Some(started) = fit_started {
2165                            profile.fit += started.elapsed();
2166                        }
2167                        if let Some(census) = pair_census.as_mut() {
2168                            census.record_fit(&fit);
2169                        }
2170                        let process_started = profile.enabled.then(Instant::now);
2171                        builder.process_curve(
2172                            fit.curve,
2173                            first,
2174                            second,
2175                            &[first, second],
2176                            &[first, second],
2177                            true,
2178                        )?;
2179                        if let Some(started) = process_started {
2180                            profile.process += started.elapsed();
2181                        }
2182                        start = end;
2183                    }
2184                    for piece in &builder.pieces[pieces_before..] {
2185                        builder.marched_pieces.insert(piece.id);
2186                    }
2187                    if debug_pairs {
2188                        eprintln!(
2189                            "pair {}x{}: run -> {} pieces",
2190                            first.face.id,
2191                            second.face.id,
2192                            builder.pieces.len() - pieces_before
2193                        );
2194                    }
2195                }
2196            }
2197            profile.clip_and_fit += profile.lap(&mut lap_start);
2198        }
2199    }
2200    profile.report();
2201    // SELF-TOUCH SPLIT: a face whose loops touch at an edge interior (a hole
2202    // tangent to a fillet setback) gets a vertex minted at the touch on BOTH
2203    // edges, so the fragment arrangement's pinch resolution assembles — see
2204    // `imprint/self_touch.rs`. Escape hatch: BREP_SELF_TOUCH_SPLIT=0.
2205    let mut tail_lap: Option<Instant> = None;
2206    let mut tail_ms = Vec::<(&str, f64)>::new();
2207    let mut lap = |name: &'static str, started: &mut Option<Instant>, tail_ms: &mut Vec<(&str, f64)>| {
2208        if profile.enabled {
2209            let now = Instant::now();
2210            if let Some(previous) = *started {
2211                tail_ms.push((name, (now - previous).as_secs_f64() * 1_000.0));
2212            }
2213            *started = Some(now);
2214        }
2215    };
2216    lap("pairs", &mut tail_lap, &mut tail_ms);
2217    if std::env::var("BREP_SELF_TOUCH_SPLIT").as_deref() != Ok("0") {
2218        builder.split_self_touching_loops(&first_faces, &face_edge_lists, &cached_subcurve)?;
2219        builder.split_self_touching_loops(&second_faces, &face_edge_lists, &cached_subcurve)?;
2220    }
2221    // PIECE-ENDPOINT EXCHANGE post-pass: with every pair's process_curve
2222    // done, the piece set is final for the ridden-edge class — imprint each
2223    // open riding piece's junction endpoints onto the ridden edges (see the
2224    // method doc; hatch BREP_OVERLAP_PIECE_ENDPOINT_SPLIT=0).
2225    builder.exchange_piece_endpoint_junctions()?;
2226    lap("self_touch", &mut tail_lap, &mut tail_ms);
2227    let mut edge_splits = builder
2228        .edge_splits
2229        .into_iter()
2230        .map(|((operand, edge_id), mut parameters)| {
2231            parameters.sort_by(f64::total_cmp);
2232            EdgeSplitRecord {
2233                operand,
2234                edge_id,
2235                parameters,
2236            }
2237        })
2238        .collect::<Vec<_>>();
2239    edge_splits.sort_by_key(|record| (record.operand, record.edge_id));
2240    let mut by_face = builder
2241        .by_face
2242        .into_iter()
2243        .map(|(face, piece_ids)| FaceImprints {
2244            operand: face.operand,
2245            face_id: face.face_id,
2246            piece_ids,
2247        })
2248        .collect::<Vec<_>>();
2249    by_face.sort_by_key(|record| (record.operand, record.face_id));
2250    let section_evidence = section_evidence || !builder.pieces.is_empty();
2251    let marched_pieces = std::mem::take(&mut builder.marched_pieces);
2252    let mut result = ImprintResultRecord {
2253        tangent_nodes,
2254        vertices: builder.vertices,
2255        pieces: builder.pieces,
2256        by_face,
2257        edge_splits,
2258        barrier_edges: builder.barrier_edges.into_iter().collect(),
2259        section_evidence,
2260        cosurface_pairs,
2261    };
2262    // COINCIDENT-PIECE MERGE (problemInbox equator-tangent, and the generic
2263    // one-circle-from-many-pairs class): the SAME section curve can be minted
2264    // by several pairs — a cosurface boundary-edge copy (the cylinder cap
2265    // ring lying ON the inscribed sphere) AND the cap-plane's analytic
2266    // section ring are one circle minted twice, each carrying only its own
2267    // pair's supports/pcurves. Assembly then builds duplicate edges that
2268    // cannot both be two-use → one-use strands. Merge pieces whose curves
2269    // coincide along their whole span (bidirectional max deviation within
2270    // the weld band): keep the first, union the supports/pcurves/by_face
2271    // registrations of the rest into it. Escape hatch:
2272    // BREP_COINCIDENT_PIECE_MERGE=0.
2273    lap("collect", &mut tail_lap, &mut tail_ms);
2274    if std::env::var("BREP_COINCIDENT_PIECE_MERGE").as_deref() != Ok("0") {
2275        let weld = assembler_weld(options.tolerance).max(options.tolerance * 10.0);
2276        // Two pieces that coincide along their WHOLE spans (deviation within
2277        // the weld both ways) also have matching end points, up to direction
2278        // and a weld or two of overhang, so a piece whose ends are nowhere
2279        // near the other's needs no 33-station projection sweep. The sweep is
2280        // what made this pass quadratic in wall time: 630 pieces cost 3.2 s
2281        // of a 3.8 s imprint on the 2026-09-12 mesh-import report, all of it
2282        // rejecting pairs an end-point look already rules out.
2283        let mut ends: Vec<[Vec3; 2]> = Vec::with_capacity(result.pieces.len());
2284        for piece in &result.pieces {
2285            let [d0, d1] = piece.curve.domain().or_refuse(KernelStage::Intersect, "domain")?;
2286            ends.push([
2287                piece.curve.evaluate(d0).or_refuse(KernelStage::Intersect, "evaluate")?,
2288                piece.curve.evaluate(d1).or_refuse(KernelStage::Intersect, "evaluate")?,
2289            ]);
2290        }
2291        let end_band = 4.0 * weld;
2292        // A CLOSED piece (a full ring) has no end points to speak of: two
2293        // rings of the same circle seamed at different azimuths coincide
2294        // along their whole spans while their domain ends sit anywhere on
2295        // the ring, so a closed piece always takes the full sweep.
2296        let ends_match = |a: &[Vec3; 2], b: &[Vec3; 2]| -> bool {
2297            let near = |p: Vec3, q: Vec3| p.sub(q).length() <= end_band;
2298            near(a[0], a[1])
2299                || near(b[0], b[1])
2300                || (near(a[0], b[0]) && near(a[1], b[1]))
2301                || (near(a[0], b[1]) && near(a[1], b[0]))
2302        };
2303        let mut removed: Vec<u64> = Vec::new();
2304        let mut index = 0;
2305        while index < result.pieces.len() {
2306            let mut other = index + 1;
2307            while other < result.pieces.len() {
2308                let coincide = ends_match(&ends[index], &ends[other]) && {
2309                    let a = &result.pieces[index];
2310                    let b = &result.pieces[other];
2311                    max_curve_deviation(&a.curve, &b.curve)? <= weld
2312                        && max_curve_deviation(&b.curve, &a.curve)? <= weld
2313                };
2314                if coincide {
2315                    let absorbed = result.pieces.remove(other);
2316                    ends.remove(other);
2317                    removed.push(absorbed.id);
2318                    let keeper = &mut result.pieces[index];
2319                    for pcurve in absorbed.pcurves {
2320                        if !keeper
2321                            .pcurves
2322                            .iter()
2323                            .any(|existing| {
2324                                existing.operand == pcurve.operand
2325                                    && existing.face_id == pcurve.face_id
2326                            })
2327                        {
2328                            keeper.pcurves.push(pcurve);
2329                        }
2330                    }
2331                    let keeper_id = keeper.id;
2332                    for record in &mut result.by_face {
2333                        if let Some(position) =
2334                            record.piece_ids.iter().position(|&id| id == absorbed.id)
2335                        {
2336                            if record.piece_ids.contains(&keeper_id) {
2337                                record.piece_ids.remove(position);
2338                            } else {
2339                                record.piece_ids[position] = keeper_id;
2340                            }
2341                        }
2342                    }
2343                } else {
2344                    other += 1;
2345                }
2346            }
2347            index += 1;
2348        }
2349        if !removed.is_empty() && std::env::var("BREP_DEBUG_BOOL").is_ok() {
2350            eprintln!("coincident-piece merge: absorbed {:?}", removed);
2351        }
2352    }
2353    // Rescue near-tangent SSI truncations BEFORE canonicalization so the added
2354    // bridge pieces' endpoints (existing crossing/stub vertices) fold into the
2355    // same junction merges as every other section.
2356    lap("coincident_merge", &mut tail_lap, &mut tail_ms);
2357    extend_truncated_sections(
2358        &mut result,
2359        &face_edge_lists,
2360        solid_a,
2361        solid_b,
2362        &charts,
2363        options.tolerance,
2364    )?;
2365    lap("extend_truncated", &mut tail_lap, &mut tail_ms);
2366    canonicalize_imprint_junctions(&mut result, solid_a, solid_b, &charts, options.tolerance)?;
2367    lap("canonicalize", &mut tail_lap, &mut tail_ms);
2368    // ONE RIM, ONE EDGE: with vertex identity final, dissolve the section
2369    // vertices that no operand edge passes through — the marcher's own
2370    // parameterization origin, which `process_curve` cannot tell from a
2371    // junction because it sees one pair at a time. See the method doc.
2372    let dissolved = dissolve_section_origin_vertices(
2373        &mut result,
2374        &marched_pieces,
2375        solid_a,
2376        solid_b,
2377        &charts,
2378        options.tolerance,
2379    )?;
2380    if dissolved > 0 && std::env::var("BREP_DEBUG_BOOL").is_ok() {
2381        eprintln!("origin-dissolve: {dissolved} parameterization vertex/vertices removed");
2382    }
2383    lap("origin_dissolve", &mut tail_lap, &mut tail_ms);
2384    // B2: reuse an existing boundary edge as the shared section edge wherever a
2385    // section coincides with one along its whole span (vertices are final after
2386    // canonicalization; `face_edge_lists` holds each face's boundary edges on
2387    // the healed operands). Runs here so both operands reference ONE edge.
2388    reuse_boundary_section_edges(
2389        &mut result,
2390        &face_edge_lists,
2391        solid_a,
2392        solid_b,
2393        options.tolerance,
2394    )?;
2395    // Capstone step 1 — instrumentation only, zero behavior change: report
2396    // every (section piece × boundary edge) contact where the piece runs
2397    // within the scale-derived band of the edge over a real span. Measuring
2398    // the bands here first validates the graze-contact model on the
2399    // acceptance suite before step 2's common-block machinery replaces a
2400    // grazed overlap with a shared edge.
2401    lap("reuse_boundary", &mut tail_lap, &mut tail_ms);
2402    report_graze_contacts(&result, &face_edge_lists, solid_a, solid_b, options.tolerance)?;
2403    if profile.enabled {
2404        let line = tail_ms
2405            .iter()
2406            .map(|(name, ms)| format!("{name}={ms:.2}"))
2407            .collect::<Vec<_>>()
2408            .join(" ");
2409        eprintln!("imprint.stages {line}");
2410    }
2411    Ok(result)
2412}
2413
2414/// Debug-only graze-contact survey (`BREP_DEBUG_GRAZE=1`): for each section
2415/// piece and each boundary edge of its support faces, sample the piece and
2416/// measure distance to the edge; report contacts whose in-band span exceeds
2417/// both the weld scale and 4× the minimum deviation (span-wise proximity, not
2418/// a point touch). `band_cap` reuses the residual-merge `sep_cap` ceiling —
2419/// measured, never grown. The output is the raw material for capstone step 2
2420/// (partial-span common-block): which contacts exist, their spans, and their
2421/// measured bands.
2422fn report_graze_contacts(
2423    result: &ImprintResultRecord,
2424    face_edge_lists: &HashMap<FaceKey, Vec<&EdgeRecord>>,
2425    solid_a: &BrepSolid,
2426    solid_b: &BrepSolid,
2427    tolerance: f64,
2428) -> Result<(), KernelRefusal> {
2429    if std::env::var("BREP_DEBUG_GRAZE").as_deref() != Ok("1") {
2430        return Ok(());
2431    }
2432    let raw_extent = raw_solid_extent(solid_a).max(raw_solid_extent(solid_b));
2433    let (_, band_cap) = residual_merge_bands(raw_extent, tolerance);
2434    const SAMPLES: usize = 17;
2435    for piece in &result.pieces {
2436        let [t0, t1] = [piece.t0, piece.t1];
2437        if !(t1 > t0) {
2438            continue;
2439        }
2440        for key in piece.support_faces {
2441            let Some(edges) = face_edge_lists.get(&key) else {
2442                continue;
2443            };
2444            for edge in edges {
2445                if edge.degenerate {
2446                    continue;
2447                }
2448                let mut in_band = 0usize;
2449                let mut min_dev = f64::INFINITY;
2450                let mut max_dev_in_band = 0.0f64;
2451                let mut span = 0.0f64;
2452                let mut prev: Option<(bool, Vec3)> = None;
2453                for k in 0..SAMPLES {
2454                    let t = t0 + (t1 - t0) * k as f64 / (SAMPLES - 1) as f64;
2455                    let point = piece.curve.evaluate(t).or_refuse(KernelStage::Intersect, "evaluate")?;
2456                    let deviation = project_point_to_curve(&edge.curve, point).or_refuse(KernelStage::Intersect, "project_point_to_curve")?.distance;
2457                    min_dev = min_dev.min(deviation);
2458                    let inside = deviation <= band_cap;
2459                    if inside {
2460                        in_band += 1;
2461                        max_dev_in_band = max_dev_in_band.max(deviation);
2462                        if let Some((true, prev_point)) = prev {
2463                            span += point.sub(prev_point).length();
2464                        }
2465                    }
2466                    prev = Some((inside, point));
2467                }
2468                // Span-wise contact: several consecutive samples in band and a
2469                // span that dwarfs the closest-approach (not a transversal
2470                // crossing, which dips in and out at one sample).
2471                if in_band >= 3 && span > (4.0 * min_dev).max(assembler_weld(tolerance)) {
2472                    eprintln!(
2473                        "graze: piece {} sup=[{}:{},{}:{}] ~ edge {}:{} span={:.3e} band=[{:.3e},{:.3e}] samples_in_band={}/{}",
2474                        piece.id,
2475                        piece.support_faces[0].operand,
2476                        piece.support_faces[0].face_id,
2477                        piece.support_faces[1].operand,
2478                        piece.support_faces[1].face_id,
2479                        key.operand,
2480                        edge.id,
2481                        span,
2482                        min_dev,
2483                        max_dev_in_band,
2484                        in_band,
2485                        SAMPLES
2486                    );
2487                }
2488            }
2489        }
2490    }
2491    Ok(())
2492}
2493
2494/// Are the two carriers TANGENT (normals parallel) where they both pass through
2495/// `point`?
2496///
2497/// The test the tangential-only refusal above needs: a point on the marched
2498/// intersection is a TANGENT NODE when the two surface normals there are
2499/// parallel. The threshold is the transversality bound the supplemental
2500/// detector already accepts seeds by (`TRANSVERSE_SEED_CROSS`), not the
2501/// far tighter pair-classifier bound — a node the march merely passes CLOSE to
2502/// still poisons the assembly, so this errs toward calling a pair singular.
2503fn pair_normals_parallel_at(
2504    first: TaggedFace<'_>,
2505    second: TaggedFace<'_>,
2506    point: Vec3,
2507) -> Result<bool, KernelRefusal> {
2508    let mut normals = [Vec3::default(); 2];
2509    for (slot, face) in normals.iter_mut().zip([first, second]) {
2510        let projection = project_point_to_surface(&face.face.surface, point)
2511            .or_refuse(KernelStage::Intersect, "project_point_to_surface")?;
2512        let Ok(normal) = face.face.surface.normal(projection.u, projection.v) else {
2513            // A pole/singular parameter point cannot witness transversality;
2514            // treat it as tangential so the pair stays refused.
2515            return Ok(true);
2516        };
2517        *slot = normal;
2518    }
2519    Ok(normals[0].cross(normals[1]).length() <= crate::TRANSVERSE_SEED_CROSS)
2520}