brep_kernel/csg/imprint/driver.rs
1use crate::{KernelRefusal, KernelStage, OrRefuse, RefusalClass, MAX_FIT_STATIONS};
2
3// Fit-split instrumentation (2026-11-02): where the section
4// fit's time goes, read only under BREP_PROFILE and printed once per
5// `build_imprints` as `imprint.fit_split`. Zero behaviour: timers only.
6#[derive(Clone, Copy)]
7pub(super) enum FitSplitLane { InitialFit, EvenSliverTotal, RefineTotal, Consistency, Midspan, MidspanRefine, CarrierUpper, Representatives, Seeded, FullRefit, CarrierReadings }
8const FIT_SPLIT_LANES: usize = 11;
9const FIT_SPLIT_NAMES: [&str; FIT_SPLIT_LANES] = ["initial_fit", "even_sliver_total", "refine_total", "consistency", "midspan", "midspan_refine", "carrier_upper", "representatives", "seeded", "full_refit", "carrier_readings"];
10thread_local! {
11 static FIT_SPLIT: std::cell::RefCell<([f64; FIT_SPLIT_LANES], [u64; FIT_SPLIT_LANES])> = const { std::cell::RefCell::new(([0.0; FIT_SPLIT_LANES], [0; FIT_SPLIT_LANES])) };
12}
13pub(super) fn fit_split_started() -> Option<Instant> { crate::profile::profile_enabled().then(Instant::now) }
14pub(super) fn fit_split_add(lane: FitSplitLane, started: Option<Instant>, calls: u64) {
15 if let Some(started) = started {
16 FIT_SPLIT.with(|cell| { let mut cell = cell.borrow_mut(); cell.0[lane as usize] += started.elapsed().as_secs_f64() * 1e3; cell.1[lane as usize] += calls; });
17 }
18}
19pub(super) fn fit_split_timed<T>(lane: FitSplitLane, run: impl FnOnce() -> T) -> T {
20 let started = fit_split_started();
21 let out = run();
22 fit_split_add(lane, started, 1);
23 out
24}
25pub(super) fn fit_split_report() {
26 let (ms, calls) = FIT_SPLIT.with(|cell| std::mem::take(&mut *cell.borrow_mut()));
27 let line = FIT_SPLIT_NAMES.iter().enumerate().map(|(i, name)| format!("{name}={:.2}/{}", ms[i], calls[i])).collect::<Vec<_>>().join(" ");
28 profile_line!("imprint.fit_split {line}");
29 let (calls, stations, points, params_ms, interpolate_ms, measure_ms) = crate::fit::station_set_split_take();
30 profile_line!("imprint.station_set calls={calls} stations={stations} points={points} params_ms={params_ms:.2} interpolate_ms={interpolate_ms:.2} measure_ms={measure_ms:.2}");
31}
32
33/// The degree a marched section is interpolated at (2026-09-27). The march
34/// puts every station on both carriers; the fit decides how far the curve
35/// strays BETWEEN them, and a cubic's O(h^4) there left `BadBoolean`'s
36/// sphere×bore rim 2.5e-7 off the bore with a mean bias of −1.2e-8 that both
37/// trims inherited (+1.14e-6 on the solid). A quintic through the same
38/// stations reads 8.5e-9 and +6e-11 with the same knots and control-point
39/// count. More stations bought the same accuracy on 2026-09-26 and were
40/// reverted for their cost: the watertight tessellator samples every interior
41/// knot, so every scan downstream scales with station count, and a degree does
42/// not add knots. `BREP_SECTION_FIT_DEGREE=3` restores the cubic. The gate on
43/// the mid-span refinement, as a multiple of the fit tolerance (2026-09-27): a
44/// marched section whose unrefined fit misses a carrier between stations by
45/// more than this is UNDERSAMPLED — the 20° cylinder crossing of
46/// `fillet-skew-cylinder-seam-split-20deg` sits 4e-5..8e-5 off, 470 times the
47/// tolerance, at any degree — and only such a section is given true
48/// intersection points until its mid-spans hold. Refining every section cost
49/// more downstream than it returned (the tessellator samples every knot).
50/// `BREP_SECTION_REFINE_GATE` sets the multiple (`on` for
51/// [`SECTION_REFINE_GATE`]). OFF BY DEFAULT: at k = 100 the sequential
52/// blend/direct_edit/imprint/boolean lib set costs +64 % and
53/// `oblique_multi_rim_cone_cap_tearing_push_refuses` goes red; k = 1000 costs
54/// +32 % and leaves the skew union +5.3e-4 off. This global switch stays off.
55/// Independently, a pair of grazing crossings on one trim edge requests a
56/// fit-floor check for that face pair below: those two nearby junctions and
57/// the intervening cap must survive the fit.
58fn section_refine_gate() -> Option<f64> {
59 static GATE: std::sync::OnceLock<Option<f64>> = std::sync::OnceLock::new();
60 *GATE.get_or_init(|| match std::env::var("BREP_SECTION_REFINE_GATE") {
61 Ok(value) if value == "on" => Some(SECTION_REFINE_GATE),
62 Ok(value) => value.parse::<f64>().ok().filter(|gate| *gate >= 0.0),
63 Err(_) => None,
64 })
65}
66
67/// Why a section goes to [`refine_section_against_carriers`].
68#[derive(Clone, Copy, Debug, PartialEq)]
69pub(super) enum SectionRefineRoute {
70 /// `BREP_SECTION_REFINE_GATE`: every section, at the variable's multiple.
71 Global,
72 /// A recovered fit reproduces its march points; check it between them.
73 Recovered,
74 /// A pair of grazing crossings on one trim edge.
75 Grazing,
76 /// The standing fit missed the tolerance it was asked for. The polyline
77 /// deviation can overstate the carrier miss (a band-raised floor reads the
78 /// input's noise), so the refiner's own carrier measure decides: a section
79 /// whose mid-spans already hold the gate leaves at round 0 as `BelowGate`,
80 /// unchanged. Measured 2026-10-04: the StationCeiling fits it reaches ship
81 /// 4.7e-5 to 2.1e-4 off the true section; the refiner brings them to
82 /// 1e-8..3e-8.
83 UnmetRequest,
84 /// A standing fit that MET its request on no other route, whose kept
85 /// stations all lie on both carriers within the fit tolerance, but whose
86 /// curve stands further off the carriers BETWEEN them than the local gate
87 /// ([`carrier_miss_route`]). The polyline it met is the march's chords; an
88 /// interpolant through consistent stations can still ring between them.
89 /// Measured 2026-10-04 (08356963b, 94 cases): 23 of 285 fast-path sections,
90 /// 13 distinct pairs, read their mid-spans up to 6.03e-6 off while every
91 /// kept station read within 1e-7; fixture 28 t217 pair 118x474 read 1.59e-6
92 /// between stations 1.8e-9 off.
93 CarrierMiss,
94}
95
96/// The carrier-miss route for a fit that met its request on no other route:
97/// refine at the local gate when its mid-spans miss the carriers by more than
98/// the fit tolerance WHILE every kept station is within it. Stations off a
99/// carrier are the input's (moving them is the reserved relocation ruling),
100/// so such a section keeps the fast path and is not reported as refined. An
101/// unreadable reading (NaN) never routes.
102pub(super) fn carrier_miss_route(station_upper: f64, midspan_upper: f64, fit_tolerance: f64) -> Option<(f64, SectionRefineRoute)> {
103 (station_upper.is_finite() && midspan_upper.is_finite() && station_upper <= fit_tolerance && midspan_upper > fit_tolerance)
104 .then_some((1.0, SectionRefineRoute::CarrierMiss))
105}
106
107/// The carrier readings [`carrier_miss_route`] decides on: the worst of both
108/// carriers over EVERY point of the section's run (the march's original
109/// stations, kept or dropped by the fit, so a fit through consistent kept
110/// stations cannot hide an inconsistent dropped one), and at the fit's
111/// mid-spans. Projector distances, so upper readings; NaN as soon as any is
112/// unreadable. The mid-span read is the route's TRIGGER, not a bound on the
113/// curve's maximum; [`knot_interior_carrier_upper`] is the finer read.
114pub(super) fn fast_path_carrier_readings(fit: &PolylineFit, run: &[Vec3], first: &NurbsSurface, second: &NurbsSurface) -> (f64, f64) {
115 let read = |surface: &NurbsSurface, point: Vec3| project_point_to_surface(surface, point).map_or(f64::NAN, |projection| projection.distance);
116 let mut station = 0.0f64;
117 for point in run {
118 let (a, b) = (read(first, *point), read(second, *point));
119 station = if a.is_nan() || b.is_nan() || station.is_nan() { f64::NAN } else { station.max(a).max(b) };
120 }
121 (station, midspan_carrier_upper(fit, first, second))
122}
123
124/// SECTION-REFINE READ MEMO (`BREP_SECTION_REFINE_READ_MEMO`, default on, =0 restores;
125/// 2026-11-03). One section refine reads the carrier distance of
126/// the SAME points several times: a representative round reads every mid-span
127/// in [`midspan_misses`], again in [`midspan_carrier_upper`], and, once a
128/// representative has run, the mid-spans and knot interiors a second time on
129/// the unchanged fit. In wasm the planter's carrier upper reads were 103 ms of
130/// its 395 ms section fit. This keeps each `(carrier, point bits)` reading for
131/// one `refine_section_against_carriers_inner` call, error included, so every
132/// caller sees exactly the value (or error) the first read returned. Off, it
133/// is the projector itself.
134pub(super) struct CarrierReads {
135 memo: Option<HashMap<(usize, [u64; 3]), Result<f64, String>>>,
136}
137impl CarrierReads {
138 pub(super) fn off() -> Self {
139 Self { memo: None }
140 }
141 pub(super) fn open() -> Self {
142 Self { memo: switch!(live "BREP_SECTION_REFINE_READ_MEMO", true).then(HashMap::default) }
143 }
144 /// The carrier distance at `point` on carrier `side` (0 first, 1 second).
145 pub(super) fn distance(&mut self, side: usize, surface: &NurbsSurface, point: Vec3) -> Result<f64, String> {
146 let Some(memo) = self.memo.as_mut() else {
147 return project_point_to_surface(surface, point).map(|projection| projection.distance);
148 };
149 memo.entry((side, [point.x.to_bits(), point.y.to_bits(), point.z.to_bits()]))
150 .or_insert_with(|| project_point_to_surface(surface, point).map(|projection| projection.distance))
151 .clone()
152 }
153}
154
155/// The worst of BOTH carriers at the fit's mid-spans, each carrier read on its
156/// own and checked finite BEFORE any max (`f64::max` would drop a NaN from one
157/// carrier and pass the other's reading): NaN as soon as either is unreadable.
158pub(super) fn midspan_carrier_upper(fit: &PolylineFit, first: &NurbsSurface, second: &NurbsSurface) -> f64 {
159 midspan_carrier_upper_read(fit, first, second, &mut CarrierReads::off())
160}
161
162/// [`midspan_carrier_upper`] through a section-refine round's [`CarrierReads`].
163fn midspan_carrier_upper_read(fit: &PolylineFit, first: &NurbsSurface, second: &NurbsSurface, reads: &mut CarrierReads) -> f64 {
164 let mut read = |side: usize, surface: &NurbsSurface, point: Vec3| reads.distance(side, surface, point).unwrap_or(f64::NAN);
165 let mut worst = 0.0f64;
166 for span in 0..fit.parameters.len().saturating_sub(1) {
167 let mid = 0.5 * (fit.parameters[span] + fit.parameters[span + 1]);
168 let (a, b) = match fit.curve.evaluate(mid) {
169 Ok(point) => (read(0, first, point), read(1, second, point)),
170 Err(_) => (f64::NAN, f64::NAN),
171 };
172 if !(a.is_finite() && b.is_finite()) || worst.is_nan() {
173 return f64::NAN;
174 }
175 worst = worst.max(a).max(b);
176 }
177 worst
178}
179
180/// The curve's worst distance from both carriers at the quarter points of
181/// every one of its own knot spans (its native pieces, where an interpolant
182/// is extremal between knots), NaN as soon as any read is unreadable. Sampled
183/// and projector-based: an upper reading at its points, not a certified
184/// maximum of the curve.
185pub(super) fn knot_interior_carrier_upper(curve: &NurbsCurve, first: &NurbsSurface, second: &NurbsSurface) -> f64 {
186 knot_interior_carrier_upper_read(curve, first, second, &mut CarrierReads::off())
187}
188
189/// [`knot_interior_carrier_upper`] through a section-refine round's [`CarrierReads`].
190fn knot_interior_carrier_upper_read(curve: &NurbsCurve, first: &NurbsSurface, second: &NurbsSurface, reads: &mut CarrierReads) -> f64 {
191 let mut read = |side: usize, surface: &NurbsSurface, point: Vec3| reads.distance(side, surface, point).unwrap_or(f64::NAN);
192 let Ok([t0, t1]) = curve.domain() else { return f64::NAN };
193 let mut knots: Vec<f64> = curve.knots.iter().copied().filter(|knot| *knot >= t0 && *knot <= t1).collect();
194 knots.dedup();
195 let mut worst = 0.0f64;
196 for pair in knots.windows(2) {
197 if pair[1] <= pair[0] {
198 continue;
199 }
200 for fraction in [0.25, 0.5, 0.75] {
201 let value = match curve.evaluate(pair[0] + (pair[1] - pair[0]) * fraction) {
202 Ok(point) => {
203 let (a, b) = (read(0, first, point), read(1, second, point));
204 if a.is_finite() && b.is_finite() { a.max(b) } else { f64::NAN }
205 }
206 Err(_) => f64::NAN,
207 };
208 worst = if worst.is_nan() || !value.is_finite() { f64::NAN } else { worst.max(value) };
209 }
210 }
211 worst
212}
213
214/// The refine route and its gate (a multiple of the fit tolerance) for one
215/// section, or `None` for the fast path.
216///
217/// A standing fit that MET its request takes exactly the routes that existed
218/// before: the global gate alone when set, else a recovered fit, else a grazing
219/// pair (read lazily, only when the first two do not apply), else nothing — it
220/// goes straight to `process_curve`, untouched.
221///
222/// A fit that MISSED its request is always refined, at the local gate 1, or at
223/// a tighter global gate. A looser global gate does not mask the miss: seam 60
224/// was a recovered fit missing its 1e-7 request, and `BREP_SECTION_REFINE_GATE`
225/// above 1 used to let it through. Its route stays `UnmetRequest` under any
226/// global gate, so its cost reads apart from the global gate's.
227pub(super) fn section_refine_route(
228 global: Option<f64>,
229 recovered: bool,
230 grazing: impl FnOnce() -> bool,
231 met_requested: bool,
232) -> Option<(f64, SectionRefineRoute)> {
233 if !met_requested {
234 return Some((global.map_or(1.0, |gate| gate.min(1.0)), SectionRefineRoute::UnmetRequest));
235 }
236 global
237 .map(|gate| (gate, SectionRefineRoute::Global))
238 .or_else(|| recovered.then_some((1.0, SectionRefineRoute::Recovered)))
239 .or_else(|| grazing().then_some((1.0, SectionRefineRoute::Grazing)))
240}
241
242/// The multiple `BREP_SECTION_REFINE_GATE=on` uses: the smallest measured
243/// that trips the skew union's undersampled sections and leaves fixture 22 as
244/// it was (k = 10 flips it).
245const SECTION_REFINE_GATE: f64 = 100.0;
246
247/// The axis-aligned box of a curve's control points (rational ones read at
248/// their Cartesian position), grown by `margin` on every side: a box the
249/// curve cannot leave, since a NURBS curve with positive weights lies in its
250/// control hull.
251pub(super) fn control_hull_box(curve: &NurbsCurve, margin: f64) -> [Vec3; 2] {
252 let mut low = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
253 let mut high = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
254 for control in &curve.control_points {
255 let w = if control.w.abs() > 1e-300 { control.w } else { 1.0 };
256 let point = Vec3::new(control.x / w, control.y / w, control.z / w);
257 low = Vec3::new(low.x.min(point.x), low.y.min(point.y), low.z.min(point.z));
258 high = Vec3::new(high.x.max(point.x), high.y.max(point.y), high.z.max(point.z));
259 }
260 [
261 Vec3::new(low.x - margin, low.y - margin, low.z - margin),
262 Vec3::new(high.x + margin, high.y + margin, high.z + margin),
263 ]
264}
265
266/// Whether two closed boxes share a point.
267pub(super) fn boxes_overlap(a: &[Vec3; 2], b: &[Vec3; 2]) -> bool {
268 a[0].x <= b[1].x && b[0].x <= a[1].x && a[0].y <= b[1].y && b[0].y <= a[1].y && a[0].z <= b[1].z && b[0].z <= a[1].z
269}
270
271/// A clipped run's END span can be a sliver: the clip endpoint lands beside a
272/// march station, a spacing ratio to the run's median of 0.011 on
273/// `anotherBooleanFail`, and a global interpolant rings next to such a jump —
274/// the mid-spans near the ends miss their carriers by 1e-3, and bisecting them
275/// only makes the spacing more uneven (2026-09-27). The reduced witness
276/// (`imprint/tests.rs` `sliver_end_evening`) says what makes the jump ring: a
277/// sliver end ON both carriers fits to 3e-8, while the same end 2.7e-4 off
278/// the torus — the vendor's edge curves sit that far off the tori they
279/// bound, and the run's end is solved on such an edge — rings at 2e-3 with
280/// the interior at 3e-13 (2026-09-30). Below
281/// `SLIVER_END_FRACTION` of the median spacing the station beside the
282/// endpoint is RE-SOLVED at the middle of the merged gap as a fresh
283/// intersection point (the default, `BREP_SECTION_SLIVER_END=resolve`) or
284/// dropped (`drop`); `off` restores the unevened fit for a before/after
285/// reading. Returns how many ends were evened.
286///
287/// ON BY DEFAULT since 2026-09-30 (lane K6), behind the measurement in
288/// [`even_sliver_ends_measured`]: only a section whose fit shows the ringing signature
289/// ([`ringing_signature`]) is evened, and the evened fit stands only where it reads closer to
290/// the carriers by [`SLIVER_END_GAIN`]. The ringing is what a marched section carries into the
291/// result: on `anotherBooleanFail` the sphere sections leave the analytic vendor torus by
292/// 3.1e-3 within 1% of their span (measured against the STEP file's own `TOROIDAL_SURFACE`,
293/// not the projector), the cut torus faces depart from their edges by 2.6e-4 and the result
294/// shell's vector-area closure reads 1.88e-4 against a 3.83e-5 bar. Evened, the sections sit
295/// 5e-8 off, the shell closes at 1.7e-6 and the boolean takes 2.3 s instead of 9.1 s. The
296/// 2026-09-27 shelving reason was three fixture-25 exact-value pins moving under a blanket
297/// `resolve`; a blanket default also refused fixture 28's reversed subtract and broke fixture
298/// 26's idempotence, which is why the signature gates it. Fixture 25's sections do not show
299/// the signature at the shipped contrast and its pins stand.
300fn even_sliver_ends(
301 section: &mut Vec<Vec3>,
302 first: &NurbsSurface,
303 second: &NurbsSurface,
304 tolerance: f64,
305) -> Result<usize, String> {
306 static MODE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
307 let mode = MODE.get_or_init(|| {
308 std::env::var("BREP_SECTION_SLIVER_END").unwrap_or_else(|_| "resolve".to_string())
309 });
310 if !(mode == "drop" || mode == "resolve") {
311 return Ok(0);
312 }
313 let mut evened = 0;
314 // One sliver at a time: each removal changes the spans it sits between.
315 for _ in 0..SLIVER_END_ROUNDS {
316 let count = section.len();
317 if count < 6 {
318 break;
319 }
320 let mut chords: Vec<f64> = section.windows(2).map(|pair| pair[1].sub(pair[0]).length()).collect();
321 let lengths = chords.clone();
322 chords.sort_by(f64::total_cmp);
323 let median = chords[chords.len() / 2];
324 let spans = lengths.len();
325 let near_ends = [0, 1, 2, spans - 3, spans - 2, spans - 1];
326 let Some(&sliver) = near_ends
327 .iter()
328 .filter(|&&span| lengths[span] < sliver_end_fraction() * median)
329 .min_by(|a, b| lengths[**a].total_cmp(&lengths[**b]))
330 else {
331 break;
332 };
333 // The sliver's bounding stations are `sliver` and `sliver + 1`; a run
334 // endpoint is never moved. Of the interior ones, remove the one whose
335 // removal leaves the shorter merged span.
336 let mut candidates = Vec::new();
337 for station in [sliver, sliver + 1] {
338 if station == 0 || station == count - 1 {
339 continue;
340 }
341 let merged = section[station + 1].sub(section[station - 1]).length();
342 candidates.push((station, merged));
343 }
344 let Some(&(station, _)) = candidates.iter().min_by(|a, b| a.1.total_cmp(&b.1)) else {
345 break;
346 };
347 if mode == "resolve" {
348 let (before, after) = (section[station - 1], section[station + 1]);
349 let seed = before.add(after).scale(0.5);
350 let reach = 0.5 * after.sub(before).length();
351 match crate::surface_surface_intersection::refine_to_intersection(first, second, seed, tolerance, reach)? {
352 Some(point) => section[station] = point,
353 None => {
354 section.remove(station);
355 }
356 }
357 } else {
358 section.remove(station);
359 }
360 evened += 1;
361 }
362 Ok(evened)
363}
364
365/// At 0.25 six of `anotherBooleanFail`'s eight sections stay NoProgress
366/// (worst miss 2.7e-4); at 0.5 all eight are evened and fall below the
367/// refinement gate (worst miss 2.8e-3 -> 5.4e-8).
368const SLIVER_END_FRACTION: f64 = 0.5;
369
370/// How many sliver spans one section may lose, at most, over both ends. Every
371/// `anotherBooleanFail` section reaches this cap (the march lands several
372/// closely spaced stations beside each clip end), and the evened fits read
373/// 2.2e-8..5.4e-8 against the carriers under it; whether a higher cap would
374/// read differently was not measured (lane K6, 2026-09-30).
375const SLIVER_END_ROUNDS: usize = 4;
376
377/// `BREP_SECTION_SLIVER_FRACTION` overrides [`SLIVER_END_FRACTION`] for the
378/// measurement that picks it.
379fn sliver_end_fraction() -> f64 {
380 static FRACTION: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
381 *FRACTION.get_or_init(|| {
382 std::env::var("BREP_SECTION_SLIVER_FRACTION")
383 .ok()
384 .and_then(|value| value.parse::<f64>().ok())
385 .filter(|fraction| *fraction > 0.0 && *fraction < 1.0)
386 .unwrap_or(SLIVER_END_FRACTION)
387 })
388}
389
390fn section_fit_degree() -> usize {
391 static DEGREE: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
392 *DEGREE.get_or_init(|| {
393 std::env::var("BREP_SECTION_FIT_DEGREE")
394 .ok()
395 .and_then(|value| value.parse::<usize>().ok())
396 .filter(|degree| (1..=7).contains(degree))
397 .unwrap_or(5)
398 })
399}
400
401/// `BREP_SPAN_BUDGET`, read once: unset leaves [`ImprintOptions::span_budget`]
402/// in charge; `off` forces none; `fit` is the pair's `intersection_fit` at the
403/// operands' scale; a number is an absolute budget.
404#[derive(Clone, Copy, Debug, PartialEq)]
405enum SpanBudgetSwitch {
406 Unset,
407 Off,
408 Fit,
409 Absolute(f64),
410}
411
412fn span_budget_switch() -> SpanBudgetSwitch {
413 static SWITCH: std::sync::OnceLock<SpanBudgetSwitch> = std::sync::OnceLock::new();
414 *SWITCH.get_or_init(|| match std::env::var("BREP_SPAN_BUDGET") {
415 Ok(value) if value == "fit" => SpanBudgetSwitch::Fit,
416 Ok(value) if value == "off" || value == "0" => SpanBudgetSwitch::Off,
417 Ok(value) => value
418 .parse::<f64>()
419 .ok()
420 .filter(|budget| *budget > 0.0)
421 .map_or(SpanBudgetSwitch::Unset, SpanBudgetSwitch::Absolute),
422 Err(_) => SpanBudgetSwitch::Unset,
423 })
424}
425
426/// The span budget every march of this imprint carries: the option under
427/// its environment override. `fit` is what the catalogue names as the
428/// budget a lane may carry, the imprint's `intersection_fit`
429/// (`KernelTolerances::for_scale`), never the corrector's `tolerance`.
430pub(super) fn span_budget(options: &ImprintOptions, scale: f64) -> Option<f64> {
431 match span_budget_switch() {
432 SpanBudgetSwitch::Unset => options.span_budget,
433 SpanBudgetSwitch::Off => None,
434 SpanBudgetSwitch::Fit => Some(crate::KernelTolerances::for_scale(scale, options.tolerance).intersection_fit),
435 SpanBudgetSwitch::Absolute(budget) => Some(budget),
436 }
437}
438
439/// [`ImprintOptions::hermite_sections`] under its environment override.
440fn hermite_sections(options: &ImprintOptions) -> bool {
441 match std::env::var("BREP_HERMITE_SECTIONS").as_deref() {
442 Ok("0") => false,
443 Ok(_) => true,
444 Err(_) => options.hermite_sections,
445 }
446}
447
448/// [`ImprintOptions::exact_iso_pcurves`] under its environment override
449/// (`BREP_EXACT_ISO_PCURVES`: `0`/`off`, `check`, anything else on).
450#[derive(Clone, Copy, Debug, PartialEq, Eq)]
451pub(super) enum ExactIsoPcurves {
452 Off,
453 On,
454 /// The fit lane runs; the exact pcurve is built beside it and compared
455 /// (`ImprintBuilder::exact_pcurve_check`).
456 Check,
457}
458
459fn exact_iso_pcurves(options: &ImprintOptions) -> ExactIsoPcurves {
460 match mode_switch!(live "BREP_EXACT_ISO_PCURVES", options.exact_iso_pcurves) {
461 crate::env_read::Mode::Off => ExactIsoPcurves::Off,
462 crate::env_read::Mode::Check => ExactIsoPcurves::Check,
463 crate::env_read::Mode::On => ExactIsoPcurves::On,
464 }
465}
466
467thread_local! {
468 /// Set by `build_imprints_impl` as it returns, read by the boolean
469 /// right after ([`take_rescue_firings`]) into
470 /// `intersect.march_swap_rescue` and `intersect.clip_swap_rescue`; a
471 /// thread-local rather than a field so the shared record's eight
472 /// literals stay as they are.
473 static RESCUE_FIRINGS: std::cell::Cell<(u64, u64)> = const { std::cell::Cell::new((0, 0)) };
474}
475
476/// `(march-order swap rescues, clip-order swap rescues)` of the last imprint
477/// built on this thread, cleared by the read.
478pub(crate) fn take_rescue_firings() -> (u64, u64) {
479 RESCUE_FIRINGS.with(|cell| cell.replace((0, 0)))
480}
481
482/// [`ImprintOptions::march_starts`] under its environment override, as
483/// `(ordered_starts, consume_starts, tree_seeds)` for every march this
484/// imprint runs. `BREP_MARCH_STARTS`: `0`/`off`; `order` (A3's ordering
485/// alone); `snap` (the ordering with run-through starts consumed); `tree`
486/// (A4 alone); `order+tree`; anything else all three.
487fn march_starts(options: &ImprintOptions, carrier_tree_seeds: bool) -> (bool, bool, bool) {
488 match std::env::var("BREP_MARCH_STARTS").as_deref() {
489 Ok("0") | Ok("off") => (false, false, false),
490 Ok("order") => (true, false, false),
491 Ok("snap") => (true, true, false),
492 Ok("tree") => (false, false, true),
493 Ok("order+tree") => (true, false, true),
494 Ok(_) => (true, true, true),
495 Err(_) => (options.march_starts, options.march_starts, options.march_starts || carrier_tree_seeds),
496 }
497}
498
499/// `BREP_SPAN_CENSUS=<file>`: one line per clipped section, appended, so a
500/// corpus replay can be read for station growth and refine-route firings
501/// under a budget. `BREP_CENSUS_LABEL` names the run on every line.
502fn span_census(line: impl FnOnce() -> String) {
503 if let Ok(path) = std::env::var("BREP_SPAN_CENSUS") {
504 use std::io::Write;
505 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
506 let label = std::env::var("BREP_CENSUS_LABEL").unwrap_or_default();
507 let _ = writeln!(file, "span-budget census: label {label} {}", line());
508 }
509 }
510}
511
512/// A marched section is held to the fit tolerance against the CARRIERS, not
513/// only against the polyline it was fitted through (2026-09-26). The march
514/// refines every station onto both surfaces to `tolerance * 0.01`, but the
515/// cubic interpolant between stations is measured by `fit_polyline` against the
516/// polyline's own chords, which are further from the true curve than the
517/// interpolant is — so a systematic bias of the interpolant (2.8e-8 mean,
518/// 4.7e-7 worst, inside the bore on `BadBoolean`'s sphere/bore rim at 135
519/// stations) passed every check the fit could make, and both trims inherited it
520/// (+1.14e-6 on the solid's volume, against two independent readings agreeing
521/// to 1e-8). The check below evaluates the fitted curve at every mid-span,
522/// measures it against both carriers, and where it misses `fit tolerance *
523/// SECTION_MIDSPAN_FRACTION` inserts a TRUE intersection point there and
524/// refits. The interpolant's bias falls as h^4, so one round usually suffices.
525/// A round that neither halves the worst miss nor lowers the count of spans
526/// missing is not in that regime — a carrier's C0 crease puts a corner in the
527/// section (fixture 25's pierce solid), where every round only doubles the
528/// stations — so that round is discarded and the loop stops. It is also capped
529/// in rounds and stations, and names how it left.
530/// `BREP_SECTION_REFINE_GATE=off` switches it off, for the before/after reading
531/// and nothing else.
532pub(super) const SECTION_MIDSPAN_FRACTION: f64 = 0.1;
533const SECTION_MIDSPAN_ROUNDS: usize = 4;
534/// A round earns another if it cuts the worst mid-span miss by this factor
535/// (h^4 promises 16 for a halved spacing) OR lowers the count of spans
536/// missing: near the floor a few isolated spans hold the worst while the count
537/// falls, and on a corner the count GROWS round on round.
538const SECTION_MIDSPAN_PROGRESS: f64 = 0.5;
539
540/// Squared distance from `point` to the segment `from`-`to`.
541fn distance_to_segment_sq(point: Vec3, from: Vec3, to: Vec3) -> f64 {
542 point.distance_to_segment_squared(from, to)
543}
544
545/// How a mid-span refinement left (`SECTION_MIDSPAN_*`), for the pair trace.
546#[derive(Clone, Copy, Debug, PartialEq)]
547pub(super) enum MidspanExit {
548 /// Every mid-span within the floor of both carriers.
549 Converged,
550 /// The unrefined fit's worst mid-span is within the gate: the section is
551 /// sampled well enough that its degree carries it, and stations would
552 /// cost more downstream than they return.
553 BelowGate,
554 /// Some mid-span still misses and no inserted point could improve it
555 /// (the Newton failed, wandered off the span, or landed on a station
556 /// already present).
557 Stalled,
558 /// A round neither halved the worst miss nor lowered the count of spans
559 /// missing: not the h^4 regime, so the section has a corner the
560 /// interpolant cannot follow. That round's fit is discarded.
561 NoProgress,
562 RoundBudget,
563 StationCeiling,
564}
565
566/// Measure `fit` at every mid-span against both carriers; return the worst
567/// distance, the count of spans that miss `floor` and, for each of them the
568/// Newton could place, the refined intersection point to insert (with the
569/// span's index).
570fn midspan_misses(
571 fit: &PolylineFit,
572 first: &NurbsSurface,
573 second: &NurbsSurface,
574 floor: f64,
575 tolerance: f64,
576 reads: &mut CarrierReads,
577) -> Result<(f64, usize, Vec<(usize, Vec3)>), String> {
578 let mut worst = 0.0f64;
579 let mut missing = 0usize;
580 let mut inserts = Vec::new();
581 for span in 0..fit.parameters.len().saturating_sub(1) {
582 let mid = 0.5 * (fit.parameters[span] + fit.parameters[span + 1]);
583 let point = fit.curve.evaluate(mid)?;
584 let off = reads.distance(0, first, point)?.max(reads.distance(1, second, point)?);
585 worst = worst.max(off);
586 if off <= floor {
587 continue;
588 }
589 missing += 1;
590 // The refined point must stay on THIS span: half its chord is the reach.
591 let reach = 0.5 * fit.kept[span + 1].sub(fit.kept[span]).length();
592 if let Some(refined) = fit_split_timed(FitSplitLane::MidspanRefine, || crate::surface_surface_intersection::refine_to_intersection(
593 first, second, point, tolerance, reach,
594 ))? {
595 inserts.push((span, refined));
596 }
597 }
598 Ok((worst, missing, inserts))
599}
600
601/// Every mid-span miss of `fit` against both carriers, measured and nothing
602/// else: what [`even_sliver_ends_measured`] reads before and after evening.
603/// It refines nothing, so a section without a sliver end pays only the
604/// projections.
605fn midspan_misses_only(fit: &PolylineFit, first: &NurbsSurface, second: &NurbsSurface) -> Result<Vec<f64>, String> {
606 let mut misses = Vec::with_capacity(fit.parameters.len());
607 for span in 0..fit.parameters.len().saturating_sub(1) {
608 let mid = 0.5 * (fit.parameters[span] + fit.parameters[span + 1]);
609 let point = fit.curve.evaluate(mid)?;
610 let off = project_point_to_surface(first, point)?
611 .distance
612 .max(project_point_to_surface(second, point)?.distance);
613 misses.push(off);
614 }
615 Ok(misses)
616}
617
618/// The signature of a global interpolant ringing on a sliver end span: the
619/// worst mid-span miss sits in one of the [`SLIVER_END_SPANS`] spans at either
620/// end, is over the fit `floor`, AND stands [`SLIVER_RING_CONTRAST`] times
621/// above the median of the interior spans. A section sampled too coarsely for the carriers misses
622/// everywhere at once and does not show this; a section with fewer than two
623/// interior spans offers no evidence either way. Returns the end-span worst
624/// and the interior median when the signature is present.
625fn ringing_signature(misses: &[f64], floor: f64) -> Option<(f64, f64)> {
626 let spans = misses.len();
627 if spans < 2 * SLIVER_END_SPANS + 2 {
628 return None;
629 }
630 let ends = misses[..SLIVER_END_SPANS]
631 .iter()
632 .chain(&misses[spans - SLIVER_END_SPANS..])
633 .copied()
634 .fold(0.0f64, f64::max);
635 let mut interior: Vec<f64> = misses[SLIVER_END_SPANS..spans - SLIVER_END_SPANS].to_vec();
636 interior.sort_by(f64::total_cmp);
637 let median = interior[interior.len() / 2];
638 let worst = misses.iter().copied().fold(0.0f64, f64::max);
639 // Below the fit floor there is nothing to even: a 1e-8 miss on a planar
640 // section passed the contrast on fixture 26 and flipped its idempotence
641 // reading while changing nothing a bar could see.
642 (ends > floor.max(sliver_min_miss()) && ends >= worst && ends > sliver_ring_contrast() * median.max(f64::MIN_POSITIVE))
643 .then_some((ends, median))
644}
645
646/// How many spans at each end of a section [`even_sliver_ends`] may even —
647/// the same three it inspects — and where [`ringing_signature`] looks.
648const SLIVER_END_SPANS: usize = 3;
649
650/// The ringing signature's contrast: the end spans' worst mid-span miss over
651/// the interior spans' median. A converged march leaves its interior at
652/// 1e-9 and a sliver end rings at 1e-4..1e-3, so the ratio is 1e5 and up:
653/// `anotherBooleanFail`'s eight sections read 1.3e5..2.2e6 (interior
654/// 6e-10..2.6e-9, ends 1.6e-4..2.8e-3). A section whose interior itself
655/// misses is under-sampled rather than ringing, and evening its end moves a
656/// junction the assembler then cannot repair: fixture 28's pair 822×118
657/// (interior 1.5e-6, ends 1.4e-3, contrast 930) refused the reversed
658/// subtract whenever it was evened, while its pairs 474×118 (1.7e4) and
659/// 842×118 (1.4e5) are evened and build. Fixture 25's two sections read
660/// 2.2e3 and 5.9e3 (interior 2e-8) and keep their stations and their pins.
661/// 3e4 sits at least 4× from the nearest reading on either side
662/// (2026-09-30).
663const SLIVER_RING_CONTRAST: f64 = 3.0e4;
664
665/// `BREP_SECTION_SLIVER_CONTRAST` overrides [`SLIVER_RING_CONTRAST`] for the
666/// measurement that picks it.
667fn sliver_ring_contrast() -> f64 {
668 static CONTRAST: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
669 *CONTRAST.get_or_init(|| {
670 std::env::var("BREP_SECTION_SLIVER_CONTRAST")
671 .ok()
672 .and_then(|value| value.parse::<f64>().ok())
673 .filter(|contrast| *contrast >= 1.0)
674 .unwrap_or(SLIVER_RING_CONTRAST)
675 })
676}
677
678/// `BREP_SECTION_SLIVER_MIN_MISS` raises the absolute floor under the end
679/// spans' worst miss, for measurement only (the shipped floor is the fit
680/// tolerance).
681fn sliver_min_miss() -> f64 {
682 static MIN: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
683 *MIN.get_or_init(|| {
684 std::env::var("BREP_SECTION_SLIVER_MIN_MISS")
685 .ok()
686 .and_then(|value| value.parse::<f64>().ok())
687 .filter(|miss| *miss >= 0.0)
688 .unwrap_or(0.0)
689 })
690}
691
692/// How a sliver-end evening left, for the census line.
693#[derive(Clone, Copy, Debug, PartialEq)]
694pub(super) enum SliverEvening {
695 /// No end span was shorter than the fraction, or the hatch is `off`.
696 NoSliver,
697 /// A sliver end exists but the unevened fit does not show the ringing
698 /// signature ([`ringing_signature`]): its stations stand.
699 NotRinging { slivers: usize, worst: f64 },
700 /// The evened section's fit reads at least [`SLIVER_END_GAIN`] times
701 /// closer to the carriers at its worst mid-span: the evened fit stands.
702 Accepted { slivers: usize, before: f64, after: f64, interior: f64 },
703 /// Evening did not earn its keep: the unevened fit stands, the stations
704 /// untouched.
705 Rejected { slivers: usize, before: f64, after: f64, interior: f64 },
706}
707
708/// A sliver-end evening must earn its keep: the evened fit stands only when
709/// its worst mid-span miss against both carriers falls by at least this
710/// factor. Measured on 2026-09-30 against the blanket default: `resolve` on
711/// every section that HAS a sliver end refused the reversed subtract of
712/// fixture 28 (`march_swap_rescue_recovers_reversed_subtract_t217`, an open
713/// loop) and made fixture 26's union read differently before and after
714/// operand splitting, while on `anotherBooleanFail` (2.8e-3 → 5e-8) and
715/// fixture 25 (2.0e-5 / 3.4e-5 → 9.2e-7) it cut the miss by 20× to 5e4×. A
716/// section whose evened fit is not clearly better keeps its stations.
717const SLIVER_END_GAIN: f64 = 2.0;
718
719/// [`even_sliver_ends`] with the measurement that decides whether it stands:
720/// fit the section as marched; if its mid-spans show the ringing signature
721/// ([`ringing_signature`]), even its sliver ends, fit again, and keep the
722/// evened fit only when its worst mid-span wins by [`SLIVER_END_GAIN`].
723/// Returns the section, its fit, the recovery flag of the fit that stands and
724/// the outcome.
725pub(super) fn even_sliver_ends_measured(
726 section: Vec<Vec3>,
727 first: &NurbsSurface,
728 second: &NurbsSurface,
729 tolerance: f64,
730 fit_tolerance: f64,
731 maximum_fit_points: usize,
732 local_fit: bool,
733) -> Result<(Vec<Vec3>, PolylineFit, bool, SliverEvening), String> {
734 fit_split_timed(FitSplitLane::EvenSliverTotal, || even_sliver_ends_measured_inner(section, first, second, tolerance, fit_tolerance, maximum_fit_points, local_fit))
735}
736
737fn even_sliver_ends_measured_inner(
738 section: Vec<Vec3>,
739 first: &NurbsSurface,
740 second: &NurbsSurface,
741 tolerance: f64,
742 fit_tolerance: f64,
743 maximum_fit_points: usize,
744 local_fit: bool,
745) -> Result<(Vec<Vec3>, PolylineFit, bool, SliverEvening), String> {
746 let (fit, recovered) = fit_split_timed(FitSplitLane::InitialFit, || crate::fit::fit_polyline_of_degree_with_recovery(
747 §ion,
748 fit_tolerance,
749 maximum_fit_points,
750 local_fit,
751 section_fit_degree(),
752 ))?;
753 let mut evened = section.clone();
754 let slivers = even_sliver_ends(&mut evened, first, second, tolerance)?;
755 if slivers == 0 {
756 return Ok((section, fit, recovered, SliverEvening::NoSliver));
757 }
758 let misses = midspan_misses_only(&fit, first, second)?;
759 let Some((before, interior)) = ringing_signature(&misses, fit_tolerance) else {
760 let worst = misses.iter().copied().fold(0.0f64, f64::max);
761 return Ok((section, fit, recovered, SliverEvening::NotRinging { slivers, worst }));
762 };
763 let (evened_fit, evened_recovered) = crate::fit::fit_polyline_of_degree_with_recovery(
764 &evened,
765 fit_tolerance,
766 maximum_fit_points,
767 local_fit,
768 section_fit_degree(),
769 )?;
770 let after_misses = midspan_misses_only(&evened_fit, first, second)?;
771 let after = after_misses.iter().copied().fold(0.0f64, f64::max);
772 if let Ok(path) = std::env::var("BREP_SECTION_SLIVER_CENSUS") {
773 use std::io::Write;
774 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
775 let chords = |points: &[Vec3]| points.windows(2).map(|pair| format!("{:.4e}", pair[1].sub(pair[0]).length())).collect::<Vec<_>>().join(" ");
776 let fmt = |values: &[f64]| values.iter().map(|value| format!("{value:.2e}")).collect::<Vec<_>>().join(" ");
777 let _ = writeln!(file, "sliver-end detail: stations {} -> {} kept {} -> {}", section.len(), evened.len(), fit.kept.len(), evened_fit.kept.len());
778 let _ = writeln!(file, "sliver-end detail: chords before: {}", chords(§ion));
779 let _ = writeln!(file, "sliver-end detail: chords after: {}", chords(&evened));
780 let _ = writeln!(file, "sliver-end detail: misses before: {}", fmt(&misses));
781 let _ = writeln!(file, "sliver-end detail: misses after: {}", fmt(&after_misses));
782 let _ = writeln!(file, "sliver-end detail: ends before: {:?} {:?}", section[0], section[section.len() - 1]);
783 let _ = writeln!(file, "sliver-end detail: ends after: {:?} {:?}", evened[0], evened[evened.len() - 1]);
784 }
785 }
786 if after * SLIVER_END_GAIN <= before {
787 Ok((evened, evened_fit, evened_recovered, SliverEvening::Accepted { slivers, before, after, interior }))
788 } else {
789 Ok((section, fit, recovered, SliverEvening::Rejected { slivers, before, after, interior }))
790 }
791}
792
793/// Refit `section` until its mid-spans sit within `floor` of both carriers,
794/// inserting true intersection points where they do not. Returns the fit that
795/// stands, the worst mid-span miss it still carries, the unrefined fit's worst
796/// (what the gate read), the rounds spent and the exit.
797/// Keep the single best measured refiner state (root grant 2026-10-04,
798/// accepted-representative sections only): replaced when `worst` is finite and
799/// no larger than the kept one (ties go to the later state). An unreadable
800/// reading is never kept. At most one snapshot is held.
801pub(super) fn keep_best_state<S>(best: &mut Option<(S, f64)>, state: impl FnOnce() -> S, worst: f64) {
802 if worst.is_finite() && best.as_ref().is_none_or(|(_, kept)| worst <= *kept) {
803 *best = Some((state(), worst));
804 }
805}
806
807/// The rollback a budget-type exit (RoundBudget, StationCeiling, Stalled)
808/// takes: on a section where an ACCEPTED representative round ran, the kept
809/// best state when its COMMON reading is strictly lower than the current
810/// state's common reading on the same instrument (both carriers, finite, at the
811/// fit's mid-spans and its own knot-span quarter points). Otherwise `None`:
812/// the exit returns the current state exactly as before, with nothing cloned.
813/// An unreadable current reading never rolls back (it is not hidden).
814pub(super) fn take_rollback<S>(best: &mut Option<(S, f64)>, current_common: f64, representative_ran: bool) -> Option<(S, f64)> {
815 let lower = matches!(best, Some((_, kept)) if representative_ran && current_common.is_finite() && *kept < current_common);
816 if lower { best.take() } else { None }
817}
818
819/// Which fit a refinement RETURNED: a receipt for controls, deciding nothing.
820#[derive(Clone, Copy, Debug, PartialEq)]
821pub(super) enum RefinedFitSource {
822 /// The standing fit the caller passed in.
823 Standing,
824 /// The original full refit of the run, made directly (`full-unmatched`,
825 /// or the retry after an unreadable representative or a seeded stall).
826 FullRefit,
827 /// The seeded (forced-station) call and its own path: a `Fallback` path
828 /// IS the original full refit.
829 Seeded(crate::fit::SeededPath),
830 /// The representative candidate call and its own path: a `Fallback` path
831 /// IS the original full refit.
832 Representatives(crate::fit::SeededPath),
833 /// The best measured state the budget-exit rollback restored.
834 Rollback,
835}
836
837/// The refiner's receipt: whether the run took the representative route at
838/// all, and which fit it returned (None on an error return). Production
839/// callers discard it; only controls read it.
840#[allow(dead_code)]
841#[derive(Clone, Copy, Debug, Default, PartialEq)]
842pub(super) struct RefineReceipt {
843 pub original_consistent: bool,
844 pub returned: Option<RefinedFitSource>,
845}
846
847pub(super) fn refine_section_against_carriers(
848 section: &mut Vec<Vec3>,
849 fit: PolylineFit,
850 first: &NurbsSurface,
851 second: &NurbsSurface,
852 fit_tolerance: f64,
853 tolerance: f64,
854 local_fit: bool,
855 gate: f64,
856) -> Result<(PolylineFit, f64, f64, usize, MidspanExit), String> {
857 // `BREP_SECTION_REPRESENTATIVES=0` restores the seeded path everywhere for
858 // an attribution A/B only; read here, once per call, as before.
859 let representatives = std::env::var("BREP_SECTION_REPRESENTATIVES").as_deref() != Ok("0");
860 refine_section_against_carriers_with(section, fit, first, second, fit_tolerance, tolerance, local_fit, gate, representatives, &mut RefineReceipt::default())
861}
862
863/// [`refine_section_against_carriers`] with the representative switch passed
864/// in (the env hatch's value, so a control can take the seeded route without
865/// touching the process environment) and a receipt of the fit returned.
866#[allow(clippy::too_many_arguments)]
867pub(super) fn refine_section_against_carriers_with(
868 section: &mut Vec<Vec3>,
869 fit: PolylineFit,
870 first: &NurbsSurface,
871 second: &NurbsSurface,
872 fit_tolerance: f64,
873 tolerance: f64,
874 local_fit: bool,
875 gate: f64,
876 representatives: bool,
877 receipt: &mut RefineReceipt,
878) -> Result<(PolylineFit, f64, f64, usize, MidspanExit), String> {
879 let started = fit_split_started();
880 let out = refine_section_against_carriers_inner(section, fit, first, second, fit_tolerance, tolerance, local_fit, gate, representatives, receipt);
881 fit_split_add(FitSplitLane::RefineTotal, started, 1);
882 out
883}
884
885#[allow(clippy::too_many_arguments)]
886fn refine_section_against_carriers_inner(
887 section: &mut Vec<Vec3>,
888 fit: PolylineFit,
889 first: &NurbsSurface,
890 second: &NurbsSurface,
891 fit_tolerance: f64,
892 tolerance: f64,
893 local_fit: bool,
894 gate: f64,
895 representatives: bool,
896 receipt: &mut RefineReceipt,
897) -> Result<(PolylineFit, f64, f64, usize, MidspanExit), String> {
898 // A reused receipt starts clean, so an error return leaves `returned`
899 // None as documented, never a previous call's value.
900 *receipt = RefineReceipt::default();
901 // Which fit `fit` currently is, and which fit `previous` holds.
902 let mut fit_source = RefinedFitSource::Standing;
903 let mut previous_source = RefinedFitSource::Standing;
904 let floor = fit_tolerance * SECTION_MIDSPAN_FRACTION;
905 let mut unrefined = f64::NAN;
906 let mut fit = fit;
907 let mut rounds = 0usize;
908 let mut previous: Option<(PolylineFit, f64, usize)> = None;
909 // Every true intersection point inserted so far: the residual-driven bend
910 // witnesses, which the refit must keep as stations.
911 let mut inserted_points: Vec<Vec3> = Vec::new();
912 // `BREP_SECTION_REFINE_CENSUS`: one `section-refine round:` line per round,
913 // bound to its section by the run's head and tail, so a stall can be read
914 // round by round (debug only; nothing below reads it).
915 let round_census = std::env::var("BREP_SECTION_REFINE_CENSUS").ok();
916 let (head, tail) = (section[0], section[section.len() - 1]);
917 // Whether EVERY original point of the run lies on both carriers within the
918 // fit tolerance (projector upper readings, NaN = no): only such a run takes
919 // the refiner-specific representative candidate below. A run with any
920 // point off a carrier keeps the existing seeded path exactly (moving or
921 // discounting input points is the reserved relocation ruling).
922 // `representatives` false (the `BREP_SECTION_REPRESENTATIVES=0` hatch)
923 // restores the seeded path everywhere for an attribution A/B only.
924 let consistency_started = fit_split_started();
925 let original_consistent = representatives
926 && section.iter().all(|point| {
927 let a = project_point_to_surface(first, *point).map_or(f64::NAN, |projection| projection.distance);
928 let b = project_point_to_surface(second, *point).map_or(f64::NAN, |projection| projection.distance);
929 a.is_finite() && b.is_finite() && a <= fit_tolerance && b <= fit_tolerance
930 });
931 receipt.original_consistent = original_consistent;
932 fit_split_add(FitSplitLane::Consistency, consistency_started, 1);
933 let mut refit_path = String::from("standing");
934 let mut refit_seeds = 0usize;
935 let mut refit_seeds_kept = 0usize;
936 let mut refit_seeds_outside_pool = 0usize;
937 let mut refit_pool = 0usize;
938 let mut refit_pool_discarded = 0usize;
939 let mut refit_pool_floor = f64::NAN;
940 let mut refit_seed_min_chord = f64::NAN;
941 // Trace only: what the closest seed pair is made of (kept station or
942 // refiner insert at each end), to place the near pair upstream.
943 let mut refit_seed_min_pair = "n/a";
944 // Trace only: every seed the full refit's pool rejects, by ORIGIN (a march
945 // station of the run, or a refiner insert) and by REASON (dropped by
946 // `station_pool`'s simplification, or kept by it and then conditioned
947 // away as a near-duplicate): [march simplified, march conditioned,
948 // insert simplified, insert conditioned].
949 let mut refit_rejected = [0usize; 4];
950 // Fits made for the round being judged (2 when its seeded fit stalled and
951 // the full refit was tried as well), and that retry's own cost.
952 let mut refit_trials = 1usize;
953 let mut retry_ms = 0.0f64;
954 // Whether the fit judged this round is an ACCEPTED seeded fit (not merely
955 // a seeded call: a seeded call that fell back already IS the full refit).
956 let mut seeded_refit = false;
957 // Whether the fit judged this round is an ACCEPTED representative
958 // candidate, and what that candidate call spent (its own interpolations
959 // and whether it ended on the full refit), kept apart from the fit's
960 // report, which on a fallback must stay the full refit's to the bit.
961 let mut representative_fit = false;
962 let mut refit_cost = crate::fit::RepresentativeCost::default();
963 // Whether an ACCEPTED representative round ran on this section, and the one
964 // best measured (fit, section) state kept for its budget exits.
965 let mut representative_ran = false;
966 let mut best_state: Option<((PolylineFit, Vec<Vec3>), f64)> = None;
967 let mut reads = CarrierReads::open();
968 loop {
969 // On an accepted representative candidate a read or refinement ERROR is
970 // not a refusal: the section takes the original full refit of the same
971 // run instead, as an unreadable read does below.
972 let (mut worst, missing, inserts) = match fit_split_timed(FitSplitLane::Midspan, || midspan_misses(&fit, first, second, floor, tolerance, &mut reads)) {
973 Ok(reading) => reading,
974 Err(_) if representative_fit => {
975 representative_fit = false;
976 seeded_refit = false;
977 let maximum_points = section.len().min(MAX_FIT_STATIONS);
978 fit = fit_split_timed(FitSplitLane::FullRefit, || fit_polyline_of_degree(section, fit_tolerance, maximum_points, local_fit, section_fit_degree()))?;
979 fit_source = RefinedFitSource::FullRefit;
980 refit_path = String::from("full-after-unreadable-representative");
981 refit_trials += 1;
982 refit_cost.full_refit = true;
983 continue;
984 }
985 Err(error) => return Err(error),
986 };
987 // An ACCEPTED representative candidate is judged on more than its
988 // mid-spans: both carriers must read finite at every mid-span and at
989 // the quarter points of the curve's own knot spans, and its worst is
990 // the worse of the two reads (sampled, projector upper readings; the
991 // carrier target is unchanged). An unreadable read never passes: the
992 // section takes the original full refit of the same run instead.
993 if representative_fit {
994 let interior = fit_split_timed(FitSplitLane::CarrierUpper, || knot_interior_carrier_upper_read(&fit.curve, first, second, &mut reads));
995 let midspans = fit_split_timed(FitSplitLane::CarrierUpper, || midspan_carrier_upper_read(&fit, first, second, &mut reads));
996 if interior.is_finite() && midspans.is_finite() {
997 worst = worst.max(interior).max(midspans);
998 } else {
999 representative_fit = false;
1000 seeded_refit = false;
1001 let maximum_points = section.len().min(MAX_FIT_STATIONS);
1002 fit = fit_split_timed(FitSplitLane::FullRefit, || fit_polyline_of_degree(section, fit_tolerance, maximum_points, local_fit, section_fit_degree()))?;
1003 fit_source = RefinedFitSource::FullRefit;
1004 refit_path = String::from("full-after-unreadable-representative");
1005 refit_trials += 1;
1006 refit_cost.full_refit = true;
1007 continue;
1008 }
1009 }
1010 if let Some(path) = &round_census {
1011 use std::io::Write;
1012 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
1013 let _ = writeln!(
1014 file,
1015 "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 {}",
1016 head.x, head.y, head.z, tail.x, tail.y, tail.z,
1017 fit.kept.len(),
1018 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(),
1019 refit_cost.representatives, refit_cost.witnesses, refit_cost.candidate_fits, refit_cost.full_refit,
1020 refit_cost.start_offset, refit_cost.end_offset,
1021 // On the representative path the seeds counted above are
1022 // WITNESSES (not forced); on the seeded path they are forced.
1023 if original_consistent { "witness" } else { "forced" }
1024 );
1025 // Trace only: where the full refit's band-raised floor comes
1026 // from on THIS run, and whether the run's own points are on
1027 // the carriers (input-consistent) or not. The band is
1028 // `dropped_band`'s reading (largest distance of a point the
1029 // pool drops from the segment between its surviving
1030 // neighbours); its point is classified as simplified away or
1031 // conditioned away, with its carrier distance and its gap to
1032 // the nearest run neighbour. Projector distances are upper
1033 // readings.
1034 let pool = crate::fit::polyline_station_pool(section, fit_tolerance);
1035 let simplified = crate::fit::simplify_polyline_indices(section, fit_tolerance);
1036 let mut band = (0.0f64, usize::MAX);
1037 for pair in pool.windows(2) {
1038 for index in pair[0] + 1..pair[1] {
1039 let off = distance_to_segment_sq(section[index], section[pair[0]], section[pair[1]]).sqrt();
1040 if off > band.0 {
1041 band = (off, index);
1042 }
1043 }
1044 }
1045 let carrier = |point: Vec3| -> f64 {
1046 let a = project_point_to_surface(first, point).map_or(f64::NAN, |projection| projection.distance);
1047 let b = project_point_to_surface(second, point).map_or(f64::NAN, |projection| projection.distance);
1048 if a.is_nan() || b.is_nan() { f64::NAN } else { a.max(b) }
1049 };
1050 let mut run_carrier = 0.0f64;
1051 for point in section.iter() {
1052 let off = carrier(*point);
1053 run_carrier = if off.is_nan() || run_carrier.is_nan() { f64::NAN } else { run_carrier.max(off) };
1054 }
1055 let (kind, band_carrier, gap) = if band.1 == usize::MAX {
1056 ("none", f64::NAN, f64::NAN)
1057 } else {
1058 let index = band.1;
1059 let kind = if simplified.binary_search(&index).is_ok() { "conditioned" } else { "simplified" };
1060 let mut gap = f64::INFINITY;
1061 if index > 0 {
1062 gap = gap.min(section[index].sub(section[index - 1]).length());
1063 }
1064 if index + 1 < section.len() {
1065 gap = gap.min(section[index + 1].sub(section[index]).length());
1066 }
1067 (kind, carrier(section[index]), gap)
1068 };
1069 let _ = writeln!(
1070 file,
1071 "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}",
1072 head.x, head.y, head.z, tail.x, tail.y, tail.z,
1073 section.len(), pool.len(), fit_tolerance, band.0,
1074 if band.1 == usize::MAX { -1 } else { band.1 as i64 }, section.len()
1075 );
1076 }
1077 }
1078 if rounds == 0 {
1079 unrefined = worst;
1080 }
1081 // The rollback path's COMMON reading, only once an accepted
1082 // representative round has run and only on a run the refit can hold
1083 // (<= MAX_FIT_STATIONS): both carriers, finite, at the fit's mid-spans
1084 // and its own knot-span quarter points, for EVERY state compared
1085 // (representative, standing or full alike). One snapshot, the fit and
1086 // the run it was measured on as one coherent pair: its payload is one
1087 // PolylineFit and at most MAX_FIT_STATIONS points. Off this path
1088 // nothing is read or cloned.
1089 let mut current_common = f64::NAN;
1090 if representative_ran && section.len() <= MAX_FIT_STATIONS {
1091 let interior = fit_split_timed(FitSplitLane::CarrierUpper, || knot_interior_carrier_upper_read(&fit.curve, first, second, &mut reads));
1092 let midspans = fit_split_timed(FitSplitLane::CarrierUpper, || midspan_carrier_upper_read(&fit, first, second, &mut reads));
1093 current_common = if interior.is_finite() && midspans.is_finite() { interior.max(midspans) } else { f64::NAN };
1094 keep_best_state(&mut best_state, || (fit.clone(), section.clone()), current_common);
1095 }
1096 if worst <= floor {
1097 receipt.returned = Some(fit_source);
1098 return Ok((fit, worst, unrefined, rounds, MidspanExit::Converged));
1099 }
1100 if rounds == 0 && worst <= gate {
1101 receipt.returned = Some(fit_source);
1102 return Ok((fit, worst, unrefined, rounds, MidspanExit::BelowGate));
1103 }
1104 if let Some((earlier, earlier_worst, earlier_missing)) = previous.take() {
1105 // A round that did not earn its keep is discarded whole: the fit
1106 // of the last round that did stands (on a corner, the unrefined
1107 // fit), not whichever of the two happens to read a smaller max.
1108 if worst > earlier_worst * SECTION_MIDSPAN_PROGRESS && missing >= earlier_missing {
1109 // A SEEDED round that did not earn its keep is refitted once
1110 // with the original full refit of the same denser run, and
1111 // that is judged against the same earlier round before the
1112 // section stops. The seeded fit keeps fewer stations; on the
1113 // helmet's 2036x3009 section that gave back the whole of the
1114 // full refit's gain (6.335e-6 stood where 5.596e-7 was
1115 // reached). The full refit is the round the refiner made
1116 // before the seeded entry existed; only a round that fails
1117 // both ends the section, exactly as before.
1118 if seeded_refit {
1119 seeded_refit = false;
1120 let maximum_points = section.len().min(MAX_FIT_STATIONS);
1121 let retry_started = round_census.is_some().then(Instant::now);
1122 fit = fit_split_timed(FitSplitLane::FullRefit, || fit_polyline_of_degree(section, fit_tolerance, maximum_points, local_fit, section_fit_degree()))?;
1123 fit_source = RefinedFitSource::FullRefit;
1124 // Trace: one more fit for the SAME round, its cost, and no
1125 // seed counters carried over from the rejected seeded fit.
1126 refit_path = String::from("full-after-seeded-stall");
1127 // Every fit this round made: the candidate's (or the seeded
1128 // call's one) plus this retry.
1129 refit_trials += 1;
1130 retry_ms = retry_started.map_or(0.0, |started| started.elapsed().as_secs_f64() * 1e3);
1131 refit_seeds = 0;
1132 refit_seeds_kept = 0;
1133 refit_seeds_outside_pool = 0;
1134 refit_seed_min_chord = f64::NAN;
1135 refit_seed_min_pair = "n/a";
1136 refit_rejected = [0; 4];
1137 representative_fit = false;
1138 // Keep the candidate's own counts; the retry is the full refit.
1139 refit_cost.full_refit = true;
1140 previous = Some((earlier, earlier_worst, earlier_missing));
1141 continue;
1142 }
1143 receipt.returned = Some(previous_source);
1144 return Ok((earlier, earlier_worst, unrefined, rounds, MidspanExit::NoProgress));
1145 }
1146 }
1147 if inserts.is_empty() {
1148 if let Some(((kept_fit, kept_run), kept_common)) = take_rollback(&mut best_state, current_common, representative_ran) {
1149 *section = kept_run;
1150 receipt.returned = Some(RefinedFitSource::Rollback);
1151 return Ok((kept_fit, kept_common, unrefined, rounds, MidspanExit::Stalled));
1152 }
1153 receipt.returned = Some(fit_source);
1154 return Ok((fit, worst, unrefined, rounds, MidspanExit::Stalled));
1155 }
1156 if rounds >= SECTION_MIDSPAN_ROUNDS {
1157 if let Some(((kept_fit, kept_run), kept_common)) = take_rollback(&mut best_state, current_common, representative_ran) {
1158 *section = kept_run;
1159 receipt.returned = Some(RefinedFitSource::Rollback);
1160 return Ok((kept_fit, kept_common, unrefined, rounds, MidspanExit::RoundBudget));
1161 }
1162 receipt.returned = Some(fit_source);
1163 return Ok((fit, worst, unrefined, rounds, MidspanExit::RoundBudget));
1164 }
1165 if section.len() + inserts.len() > MAX_FIT_STATIONS {
1166 if let Some(((kept_fit, kept_run), kept_common)) = take_rollback(&mut best_state, current_common, representative_ran) {
1167 *section = kept_run;
1168 receipt.returned = Some(RefinedFitSource::Rollback);
1169 return Ok((kept_fit, kept_common, unrefined, rounds, MidspanExit::StationCeiling));
1170 }
1171 receipt.returned = Some(fit_source);
1172 return Ok((fit, worst, unrefined, rounds, MidspanExit::StationCeiling));
1173 }
1174 // Insert each refined point into the run between its span's stations,
1175 // on the nearest segment of the run there. Positions are located
1176 // against the run BEFORE any insertion of this round, then applied
1177 // from the back so earlier indices stay valid. The kept stations are
1178 // found by a FORWARD search from the previous one: a closed section
1179 // ends on its first station, and a search from the start would put
1180 // the last span's far end at index 0 and skip that span forever.
1181 let station_index = |point: Vec3, from: usize| -> Option<usize> {
1182 section[from..]
1183 .iter()
1184 .position(|p| p.sub(point).length() <= 1e-15 * (1.0 + point.length()))
1185 .map(|offset| from + offset)
1186 };
1187 let mut placed: Vec<(usize, Vec3)> = Vec::new();
1188 for (span, point) in inserts {
1189 let Some(lo) = station_index(fit.kept[span], 0) else {
1190 continue;
1191 };
1192 let Some(hi) = station_index(fit.kept[span + 1], lo + 1) else {
1193 continue;
1194 };
1195 if hi <= lo {
1196 continue;
1197 }
1198 if section[lo..=hi].iter().any(|p| p.sub(point).length() <= floor) {
1199 continue; // already a station: inserting it again refines nothing
1200 }
1201 let mut best = (lo, f64::INFINITY);
1202 for index in lo..hi {
1203 let d2 = distance_to_segment_sq(point, section[index], section[index + 1]);
1204 if d2 < best.1 {
1205 best = (index, d2);
1206 }
1207 }
1208 placed.push((best.0 + 1, point));
1209 }
1210 if placed.is_empty() {
1211 if let Some(((kept_fit, kept_run), kept_common)) = take_rollback(&mut best_state, current_common, representative_ran) {
1212 *section = kept_run;
1213 receipt.returned = Some(RefinedFitSource::Rollback);
1214 return Ok((kept_fit, kept_common, unrefined, rounds, MidspanExit::Stalled));
1215 }
1216 receipt.returned = Some(fit_source);
1217 return Ok((fit, worst, unrefined, rounds, MidspanExit::Stalled));
1218 }
1219 placed.sort_by(|a, b| b.0.cmp(&a.0));
1220 for (at, point) in placed {
1221 section.insert(at, point);
1222 inserted_points.push(point);
1223 }
1224 // The refit keeps the stations the current fit kept and every point
1225 // inserted so far as SEEDS, and grows only where it is measured to
1226 // miss (`fit_polyline_seeded_with_path`). It is no longer the whole denser run
1227 // from the run's own count: that interpolated every march station the
1228 // standing fit had dropped as well, 3x the controls on the skew-cylinder
1229 // loop for the same carrier floor. The ceiling is the one the full refit
1230 // derives from the same `maximum_points`, and a seeded fit that cannot
1231 // meet the floor, or cannot be read, IS the full refit. The carrier
1232 // measure above still decides every round.
1233 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();
1234 let mut seeds = Vec::with_capacity(fit.kept.len() + inserted_points.len());
1235 // The previous fit's kept stations alone: the representative candidate's
1236 // starting set (inserts are its witnesses, not forced stations).
1237 let mut kept_seeds = Vec::with_capacity(fit.kept.len());
1238 // The refiner's own inserted true-carrier points: the representative
1239 // candidate's TIGHT witnesses, held to this refiner's carrier floor.
1240 let mut insert_indices = Vec::with_capacity(inserted_points.len());
1241 let mut next_kept = 0usize;
1242 for (index, point) in section.iter().enumerate() {
1243 // The kept stations are a subsequence of the run, in run order
1244 // (insertion only adds points), so a closed run's two equal ends
1245 // each match their own station.
1246 // An inserted point is a tight witness whether or not the previous
1247 // fit also kept it as a station (membership is independent).
1248 let inserted = inserted_points.iter().any(|candidate| same(point, candidate));
1249 if inserted {
1250 insert_indices.push(index);
1251 }
1252 if next_kept < fit.kept.len() && same(point, &fit.kept[next_kept]) {
1253 seeds.push(index);
1254 kept_seeds.push(index);
1255 next_kept += 1;
1256 } else if inserted {
1257 seeds.push(index);
1258 }
1259 }
1260 let maximum_points = section.len().min(MAX_FIT_STATIONS);
1261 // Every standing station must be found as a seed; if the pass missed
1262 // one, the seeded refit would silently lose it, so the section takes the
1263 // original full refit instead.
1264 refit_trials = 1;
1265 retry_ms = 0.0;
1266 let refit_source;
1267 let refit = if next_kept == fit.kept.len() {
1268 let (refit, path) = if original_consistent {
1269 let (refit, path, cost) = fit_split_timed(FitSplitLane::Representatives, || crate::fit::fit_polyline_representatives_with_witnesses(
1270 section,
1271 &kept_seeds,
1272 &insert_indices,
1273 floor,
1274 fit_tolerance,
1275 maximum_points,
1276 local_fit,
1277 section_fit_degree(),
1278 ))?;
1279 // MIXED UNITS, named as such: the candidate's own
1280 // `fit_station_set` interpolations plus one per call of the
1281 // full refit `fit_polyline_of_degree`, which may itself run
1282 // several ladder and recovery interpolations that are NOT
1283 // counted here. Whole-job cost is the matched timing.
1284 refit_cost = cost;
1285 refit_trials = cost.candidate_fits + usize::from(cost.full_refit);
1286 (refit, path)
1287 } else {
1288 refit_cost = crate::fit::RepresentativeCost::default();
1289 fit_split_timed(FitSplitLane::Seeded, || crate::fit::fit_polyline_seeded_with_path(
1290 section,
1291 &seeds,
1292 fit_tolerance,
1293 maximum_points,
1294 local_fit,
1295 section_fit_degree(),
1296 ))?
1297 };
1298 // Only an ACCEPTED seeded fit can be retried in full: a fallback
1299 // already is the full refit, and retrying it would repeat it.
1300 seeded_refit = matches!(path, crate::fit::SeededPath::Accepted { .. });
1301 representative_fit = original_consistent && seeded_refit;
1302 refit_source = if original_consistent { RefinedFitSource::Representatives(path) } else { RefinedFitSource::Seeded(path) };
1303 representative_ran |= representative_fit;
1304 if round_census.is_some() {
1305 refit_path = if original_consistent { format!("representatives-call:{path:?}") } else { format!("seeded-call:{path:?}") };
1306 }
1307 refit
1308 } else {
1309 seeded_refit = false;
1310 representative_fit = false;
1311 refit_cost = crate::fit::RepresentativeCost::default();
1312 refit_path = String::from("full-unmatched");
1313 refit_source = RefinedFitSource::FullRefit;
1314 fit_split_timed(FitSplitLane::FullRefit, || fit_polyline_of_degree(section, fit_tolerance, maximum_points, local_fit, section_fit_degree()))?
1315 };
1316 if round_census.is_some() {
1317 // Trace only: how many seeds the refit kept. This does NOT tell an
1318 // accepted seeded fit from the fallback (a full refit can keep
1319 // every seed too); the returned `SeededPath`, traced as `fit`, does.
1320 refit_seeds = seeds.len();
1321 refit_seeds_kept = seeds.iter().filter(|&&index| refit.kept.iter().any(|kept| same(kept, §ion[index]))).count();
1322 // Seeds the full refit's pool would NOT interpolate (simplified
1323 // or conditioned away): forced stations only the seeded fit keeps.
1324 let pool = crate::fit::polyline_station_pool(section, fit_tolerance);
1325 refit_pool = pool.len();
1326 refit_pool_discarded = section.len() - pool.len();
1327 refit_pool_floor = crate::fit::polyline_station_pool_floor(section, fit_tolerance);
1328 refit_seeds_outside_pool = seeds.iter().filter(|index| !pool.contains(index)).count();
1329 // Seeds are in run order; the closest consecutive pair is what a
1330 // near-duplicate conditioning floor would act on.
1331 refit_seed_min_chord = seeds
1332 .windows(2)
1333 .map(|pair| section[pair[1]].sub(section[pair[0]]).length())
1334 .fold(f64::INFINITY, f64::min);
1335 let is_insert = |index: usize| inserted_points.iter().any(|inserted| same(inserted, §ion[index]));
1336 refit_seed_min_pair = seeds
1337 .windows(2)
1338 .min_by(|a, b| {
1339 section[a[1]].sub(section[a[0]]).length().total_cmp(§ion[b[1]].sub(section[b[0]]).length())
1340 })
1341 .map_or("n/a", |pair| match (is_insert(pair[0]), is_insert(pair[1])) {
1342 (false, false) => "station-station",
1343 (false, true) => "station-insert",
1344 (true, false) => "insert-station",
1345 (true, true) => "insert-insert",
1346 });
1347 // Every rejected seed, classified. `station_pool` starts from this
1348 // same simplification, so a non-member that is not in it was
1349 // simplified away, and one that is in it was conditioned away.
1350 let simplified = crate::fit::simplify_polyline_indices(section, fit_tolerance);
1351 refit_rejected = [0; 4];
1352 for &index in seeds.iter().filter(|index| !pool.contains(index)) {
1353 let conditioned = simplified.binary_search(&index).is_ok();
1354 refit_rejected[2 * usize::from(is_insert(index)) + usize::from(conditioned)] += 1;
1355 }
1356 }
1357 previous = Some((std::mem::replace(&mut fit, refit), worst, missing));
1358 previous_source = std::mem::replace(&mut fit_source, refit_source);
1359 rounds += 1;
1360 }
1361}
1362use super::*;
1363use super::gate_census::{census_pair, fit_census_enabled};
1364
1365pub fn build_imprints(
1366 solid_a: &BrepSolid,
1367 solid_b: &BrepSolid,
1368 options: &ImprintOptions,
1369) -> Result<ImprintResultRecord, KernelRefusal> {
1370 build_imprints_impl(solid_a, solid_b, options, false, super::pcurve_cache::enabled())
1371}
1372
1373/// Imprint temporary open offset sheets. A section riding one sheet's trim
1374/// still cuts the other sheet when no boundary copy supplied that section.
1375pub(crate) fn build_carrier_imprints(
1376 solid_a: &BrepSolid,
1377 solid_b: &BrepSolid,
1378 options: &ImprintOptions,
1379 tree_seeds: bool,
1380) -> Result<ImprintResultRecord, KernelRefusal> {
1381 build_imprints_with_carrier_seeds(
1382 solid_a, solid_b, options, true, super::pcurve_cache::enabled(), tree_seeds, None,
1383 )
1384}
1385
1386/// Lane P3b row 6 (2026-10-26): imprint the faces of ONE open sheet solid
1387/// against each other in a single call. `solid` is both operands (operand 0
1388/// and 1 tag the same faces); a face pair is processed once, as
1389/// (first index, second index) in the solid's face order with first < second,
1390/// and only when `admit(first, second)` says so. Every post-pass then runs
1391/// once over the whole piece set instead of once per pair.
1392pub(crate) fn build_carrier_imprints_within(
1393 solid: &BrepSolid,
1394 admit: &dyn Fn(usize, usize) -> bool,
1395 options: &ImprintOptions,
1396 tree_seeds: bool,
1397) -> Result<ImprintResultRecord, KernelRefusal> {
1398 build_imprints_with_carrier_seeds(
1399 solid, solid, options, true, super::pcurve_cache::enabled(), tree_seeds, Some(admit),
1400 )
1401}
1402
1403/// `BREP_IMPRINT_PLANE_POINT_SKIP` (lane X row 5): skip the march for two
1404/// exact parallelogram faces whose contact is one point
1405/// ([`super::support::planar_point_contact`]) once their seeds are accepted.
1406/// On by default; `0`/`off` disables it.
1407pub(super) fn plane_point_skip_enabled() -> bool {
1408 switch!(live "BREP_IMPRINT_PLANE_POINT_SKIP", true)
1409}
1410
1411thread_local! {
1412 static PLANE_POINT_SKIPS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
1413}
1414
1415/// Pairs skipped by [`plane_point_skip_enabled`] on this thread since the last call.
1416pub(super) fn take_plane_point_skips() -> u64 {
1417 PLANE_POINT_SKIPS.with(|count| count.replace(0))
1418}
1419
1420
1421pub(super) fn build_imprints_impl(
1422 solid_a: &BrepSolid,
1423 solid_b: &BrepSolid,
1424 options: &ImprintOptions,
1425 open_carriers: bool,
1426 pcurve_cache: bool,
1427) -> Result<ImprintResultRecord, KernelRefusal> {
1428 build_imprints_with_carrier_seeds(solid_a, solid_b, options, open_carriers, pcurve_cache, false, None)
1429}
1430
1431// A carved offset can have a narrow transverse section beside its fold.
1432// Seed its knot spans without enabling ordered/consumed starts on other paths.
1433fn build_imprints_with_carrier_seeds(
1434 solid_a: &BrepSolid,
1435 solid_b: &BrepSolid,
1436 options: &ImprintOptions,
1437 open_carriers: bool,
1438 pcurve_cache: bool,
1439 carrier_tree_seeds: bool,
1440 // `Some`: `solid_a` IS `solid_b` and only face pairs (first < second) the
1441 // predicate admits are processed ([`build_carrier_imprints_within`]).
1442 self_pairs: Option<&dyn Fn(usize, usize) -> bool>,
1443) -> Result<ImprintResultRecord, KernelRefusal> {
1444 let _wall = crate::wall_profile::Stage::open("imprint");
1445 let _profile_sink = ProfileSink::open();
1446 let _work = super::work_profile::Scope::open("imprint");
1447 let classify_before = crate::pair_classification::classify_reads();
1448 let setup_started = profile_enabled().then(Instant::now);
1449 let windows_before = window_reads();
1450 let _pcurve_cache = super::pcurve_cache::FitCacheScope::open(pcurve_cache);
1451 let _edge_hulls = super::support::EdgeHullScope::open(super::support::edge_hull_memo_enabled());
1452 let _intersection_cache = super::intersection_cache::Scope::open();
1453 let reuse_whole_miss = open_carriers &&
1454 std::env::var("BREP_OFFSET_IMPRINT_READINGS").map_or(true, |v| v != "0" && v != "off");
1455 let mut section_evidence = false;
1456 let edges = edge_map(solid_a, solid_b);
1457 // FEATURE SCALE (`BREP_FEATURE_SCALE_IMPRINT`, default on, `=0` off):
1458 // the slivers between almost parallel plane faces of the two operands,
1459 // measured before any section is processed so that every band the
1460 // imprint applies sees them. A sliver a quarter of which is under the
1461 // tolerance cannot be resolved by any band, so the boolean refuses
1462 // rather than merge it away.
1463 let feature_scale = if crate::feature_scale::imprint_enabled() {
1464 let scale = crate::feature_scale::FeatureScale::between(solid_a, solid_b, options.tolerance)
1465 .or_refuse(KernelStage::Intersect, "feature_scale")?;
1466 if let Some(thinnest) = scale.thinnest() {
1467 if crate::feature_scale::FRACTION * thinnest < options.tolerance {
1468 return Err(KernelRefusal::new(
1469 RefusalClass::IllPosed { what: "feature size".into() },
1470 KernelStage::Intersect,
1471 format!(
1472 "boolean: a sliver {thinnest:.3e} thick between almost parallel faces is smaller than the \
1473 section's resolution (tolerance {:.1e})",
1474 options.tolerance
1475 ),
1476 ));
1477 }
1478 }
1479 scale
1480 } else {
1481 Default::default()
1482 };
1483 // The tail passes (coincident-piece merge, junction canonicalisation)
1484 // run after the builder is consumed; they read the same scale.
1485 let tail_feature_scale = feature_scale.clone();
1486 let mut builder = ImprintBuilder {
1487 feature_scale,
1488 open_carriers,
1489 edges,
1490 tolerance: options.tolerance,
1491 barrier_edges: HashSet::default(),
1492 overlap_ridden_edges: HashSet::default(),
1493 scale: solid_scale(solid_a).max(solid_scale(solid_b)),
1494 vertices: Vec::new(),
1495 vertex_radii: HashMap::default(),
1496 vertex_sources: HashMap::default(),
1497 pieces: Vec::new(),
1498 by_face: HashMap::default(),
1499 edge_splits: HashMap::default(),
1500 marched_pieces: HashSet::default(),
1501 exact_section: false,
1502 emitted_pcurves: HashSet::default(),
1503 exact_pcurve_check: false,
1504 next_id: 1,
1505 };
1506 let requested_hermite_lane = hermite_sections(options);
1507 let hermite_lane = requested_hermite_lane || super::hermite::carried_uv_enabled();
1508 let hermite_min_stations = if hermite_lane { super::hermite::carried_uv_min_stations() } else { 2 };
1509 let exact_iso = exact_iso_pcurves(options);
1510 let (ordered_starts, consume_starts, tree_seeds) = march_starts(options, carrier_tree_seeds);
1511 builder.exact_pcurve_check = exact_iso == ExactIsoPcurves::Check;
1512 // The Hermite lane needs the march's span test; with no budget asked for
1513 // it carries the one the catalogue names, the pair's `intersection_fit`.
1514 let march_span_budget = span_budget(options, builder.scale)
1515 .or_else(|| requested_hermite_lane.then(|| crate::KernelTolerances::for_scale(builder.scale, options.tolerance).intersection_fit));
1516 // UV reuse must never request a different trace. Its stricter emission
1517 // check is applied after clipping; an unqualified run keeps the fit route.
1518 let emission_budget = super::hermite::carried_uv_emission_budget(march_span_budget,
1519 crate::KernelTolerances::for_scale(builder.scale, options.tolerance).intersection_fit);
1520 let mut first_faces = faces(solid_a, 0);
1521 let mut second_faces = faces(solid_b, 1);
1522 // Per-face trim-window carriers, computed once: tighter BVH bounds, and
1523 // the seed/march stages walk the window instead of the full carrier
1524 // (identical geometry and parameterization inside the window).
1525 //
1526 // A window LIFTED across a closed direction's seam (`restricted_carrier`)
1527 // is the exception to "same parameterization": it is the trim's frame, a
1528 // period off the carrier's on one side. Its face carries it as its
1529 // `chart`, and every uv the imprint reads for that face — the clip's
1530 // containment, the sections' pcurves, a seed's normal — is read there.
1531 let first_restricted: Vec<Option<MarchWindow>> = first_faces
1532 .iter()
1533 .map(|tagged| restricted_carrier_memoized(tagged.face))
1534 .collect();
1535 let second_restricted: Vec<Option<MarchWindow>> = second_faces
1536 .iter()
1537 .map(|tagged| restricted_carrier_memoized(tagged.face))
1538 .collect();
1539 for (tagged, window) in first_faces.iter_mut().zip(&first_restricted) {
1540 tagged.lifted = window.as_ref().filter(|window| window.lifted).map(|window| &window.surface);
1541 }
1542 for (tagged, window) in second_faces.iter_mut().zip(&second_restricted) {
1543 tagged.lifted = window.as_ref().filter(|window| window.lifted).map(|window| &window.surface);
1544 }
1545 let first_faces = first_faces;
1546 let second_faces = second_faces;
1547 // The lifted charts, by key, for the post-passes that rebuild a pcurve
1548 // after the mint (junction canonicalization, the origin dissolve, the
1549 // truncation bridge). A face absent here is read on its own carrier.
1550 let charts: FaceCharts<'_> = first_faces
1551 .iter()
1552 .chain(second_faces.iter())
1553 .filter_map(|tagged| tagged.lifted.map(|surface| (tagged.key(), surface)))
1554 .collect();
1555 let first_bounds = first_faces
1556 .iter()
1557 .zip(&first_restricted)
1558 .map(|(tagged, restricted)| {
1559 face_bounds(
1560 tagged.face,
1561 restricted.as_ref().map(|window| &window.surface),
1562 &builder.edges,
1563 tagged.operand,
1564 options.tolerance,
1565 )
1566 })
1567 .collect::<Result<Vec<_>, _>>()?;
1568 let second_bounds = second_faces
1569 .iter()
1570 .zip(&second_restricted)
1571 .map(|(tagged, restricted)| {
1572 face_bounds(
1573 tagged.face,
1574 restricted.as_ref().map(|window| &window.surface),
1575 &builder.edges,
1576 tagged.operand,
1577 options.tolerance,
1578 )
1579 })
1580 .collect::<Result<Vec<_>, _>>()?;
1581 let second_bvh = Bvh::build(&second_bounds);
1582 // Per-face classifier data and edge lists/subcurves, computed once and
1583 // reused across every pair the face participates in.
1584 let first_classify = first_faces
1585 .iter()
1586 .map(|tagged| SurfaceClassifyData::memoized(&tagged.face.surface, options.tolerance))
1587 .collect::<Result<Vec<_>, _>>().or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
1588 let second_classify = second_faces
1589 .iter()
1590 .map(|tagged| SurfaceClassifyData::memoized(&tagged.face.surface, options.tolerance))
1591 .collect::<Result<Vec<_>, _>>().or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
1592 let mut face_edge_lists: HashMap<FaceKey, Vec<&EdgeRecord>> = HashMap::default();
1593 for tagged in first_faces.iter().chain(second_faces.iter()) {
1594 face_edge_lists.insert(tagged.key(), face_edges(*tagged, &builder.edges)?);
1595 }
1596 let mut subcurves: HashMap<(u8, u64), NurbsCurve> = HashMap::default();
1597 for (&key, edge) in &builder.edges {
1598 if !edge.degenerate {
1599 // Failures fall through: the use sites recompute and surface the
1600 // original error exactly where the uncached code did.
1601 if let Ok(curve) = edge_subcurve(edge) {
1602 subcurves.insert(key, curve);
1603 }
1604 }
1605 }
1606 let cached_subcurve = |operand: u8, edge: &EdgeRecord| -> Result<NurbsCurve, KernelRefusal> {
1607 match subcurves.get(&(operand, edge.id)) {
1608 Some(curve) => Ok(curve.clone()),
1609 None => edge_subcurve(edge),
1610 }
1611 };
1612 if let Some(started) = setup_started { profile_line!("imprint.setup ms={:.3} faces={} classify_reads={} windows_built={}", started.elapsed().as_secs_f64()*1000.0, first_faces.len()+second_faces.len(), crate::pair_classification::classify_reads()-classify_before, window_reads()-windows_before); }
1613 let mut profile = ImprintProfile::new();
1614 let mut march_swap_rescues = 0_u64;
1615 let mut clip_swap_rescues = 0_u64;
1616 // `imprint.fits`: the pcurve fits the pairs record, read through a nested
1617 // scope that folds into the operation's own ledger on close.
1618 let pairs_fits = profile.enabled.then(crate::PcurveFitScope::open);
1619 let mut tangent_nodes: Vec<Vec3> = Vec::new();
1620 let mut tangent_sections: Vec<TangentSectionRecord> = Vec::new();
1621 // Planar tangent runs to IMPRINT once every pair has minted its pieces
1622 // (`ImprintOptions::tangent_curve_imprint`): (section index, run, pair).
1623 let mut tangent_imprint_candidates: Vec<(usize, Vec<Vec3>, TaggedFace<'_>, TaggedFace<'_>)> = Vec::new();
1624 let mut cosurface_pairs: Vec<(FaceKey, FaceKey)> = Vec::new();
1625 let mut deferred_boundary_spans = Vec::new();
1626 let mut work_pairs = super::work_profile::Pairs::new(first_faces.len() * second_faces.len());
1627 let mut paired = Vec::new();
1628 for (first_index, first) in first_faces.iter().enumerate() {
1629 let first = *first;
1630 paired.clear();
1631 second_bvh.overlapping(
1632 first_bounds[first_index],
1633 options.tolerance * 100.0,
1634 &mut paired,
1635 );
1636 paired.sort_unstable();
1637 let debug_pairs = std::env::var("BREP_DEBUG_PAIRS").is_ok();
1638 if debug_pairs {
1639 eprintln!(
1640 "bvh first_face={} -> {} candidate pairs {:?}",
1641 first.face.id,
1642 paired.len(),
1643 paired
1644 .iter()
1645 .map(|&index| second_faces[index].face.id)
1646 .collect::<Vec<_>>()
1647 );
1648 }
1649 for &second_index in &paired {
1650 if let Some(admit) = self_pairs {
1651 if second_index <= first_index || !admit(first_index, second_index) {
1652 continue;
1653 }
1654 }
1655 let second = second_faces[second_index];
1656 profile.pairs += 1;
1657 let _work_pair = super::work_profile::Pair::open(&mut work_pairs, first.face, second.face);
1658 // Set when this pair's section passes through an isolated TANGENT
1659 // NODE inside both trims (see the classification below). The march
1660 // is attempted anyway — that is the whole point — but a marcher
1661 // that cannot get through a node must refuse in the tangent-node
1662 // class naming the node, not leak its own step-budget message.
1663 let mut tangent_node: Option<Vec3> = None;
1664 // The pair's branches came from the sub-gate re-march below: a
1665 // run end that stalls in the tangency band then lands on a trim
1666 // edge the carriers share exactly as a tangent-node pair's does.
1667 let mut grazing_march = false;
1668 let mut lap_start = None;
1669 profile.lap(&mut lap_start);
1670 let pair_classification = classify_surface_pair_cached(
1671 &first.face.surface,
1672 &first_classify[first_index],
1673 &second.face.surface,
1674 &second_classify[second_index],
1675 options.tolerance,
1676 PAIR_ANGULAR_TOLERANCE,
1677 ).or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
1678 profile.classify += profile.lap(&mut lap_start);
1679 if pair_classification.relation == SurfacePairRelation::Disjoint {
1680 if debug_pairs {
1681 eprintln!(
1682 "pair {}x{}: DISJOINT-cull sep={:.4e}",
1683 first.face.id, second.face.id, pair_classification.minimum_separation
1684 );
1685 }
1686 continue;
1687 }
1688 // Coincident carriers are never marched: their true intersection
1689 // is a 2D region, not a curve, so anything the marcher traces on
1690 // them is noise ("similar faces we do not intersect" — Golovanov
1691 // §6.2). The sampled classification catches coincident pairs
1692 // the strict reconstruction test misses (partial overlaps,
1693 // differing parameterizations); both route to the boundary-curve
1694 // exchange below.
1695 let is_cosurface = pair_classification.relation == SurfacePairRelation::Cosurface
1696 || cosurface_pair(&first.face.surface, &second.face.surface, options.tolerance)?;
1697 profile.cosurface += profile.lap(&mut lap_start);
1698 if is_cosurface {
1699 if debug_pairs {
1700 eprintln!("pair {}x{}: cosurface", first.face.id, second.face.id);
1701 }
1702 cosurface_pairs.push((first.key(), second.key()));
1703 for edge in &face_edge_lists[&second.key()] {
1704 if !edge.degenerate {
1705 builder.process_curve(
1706 cached_subcurve(second.operand, edge)?,
1707 first,
1708 second,
1709 &[first],
1710 &[first],
1711 false,
1712 None,
1713 )?;
1714 }
1715 }
1716 for edge in &face_edge_lists[&first.key()] {
1717 if !edge.degenerate {
1718 builder.process_curve(
1719 cached_subcurve(first.operand, edge)?,
1720 first,
1721 second,
1722 &[second],
1723 &[second],
1724 false,
1725 None,
1726 )?;
1727 }
1728 }
1729 profile.process_curve += profile.lap(&mut lap_start);
1730 continue;
1731 }
1732
1733 // Exact lanes discard sections riding an ordinary trim boundary:
1734 // that face already owns the edge, but the other face still needs
1735 // the overlapping span as a cut. Keep the existing whole-edge
1736 // exchange order, and append missing partial spans after all pairs
1737 // have run so duplicate sections retain their established IDs and
1738 // fragment names. Seams are retained by the exact lane itself.
1739 let planar_iso = planar_iso_section(
1740 &first.face.surface,
1741 &second.face.surface,
1742 options.tolerance,
1743 )?;
1744 profile.planar_iso += profile.lap(&mut lap_start);
1745 let analytic = crate::intersect_analytic_pair(
1746 &first.face.surface,
1747 &second.face.surface,
1748 options.tolerance,
1749 );
1750 profile.analytic += profile.lap(&mut lap_start);
1751 let exact_section = planar_iso.is_some() || analytic.is_some();
1752
1753 for (source, target) in [(first, second), (second, first)] {
1754 for edge in &face_edge_lists[&source.key()] {
1755 if edge.degenerate {
1756 continue;
1757 }
1758 let curve = cached_subcurve(source.operand, edge)?;
1759 let spans = curve_spans_on_face(
1760 curve.clone(), target.face, &face_edge_lists[&target.key()],
1761 options.tolerance, exact_section,
1762 )?;
1763 let seam = source.face.loops.iter().flat_map(|loop_record| &loop_record.coedges)
1764 .filter(|coedge| coedge.edge_id == edge.id).count() >= 2;
1765 if spans.is_empty() && exact_section && !seam {
1766 // The whole-only call above returned empty: its exact
1767 // curve/carrier reading was false. Nothing has mutated.
1768 let partial = if reuse_whole_miss {
1769 profile.whole_reading_reuses += 1;
1770 curve_spans_after_whole_miss(
1771 curve, target.face, &face_edge_lists[&target.key()], options.tolerance,
1772 )?
1773 } else {
1774 curve_spans_on_face(
1775 curve, target.face, &face_edge_lists[&target.key()], options.tolerance, false,
1776 )?
1777 };
1778 for span in partial {
1779 deferred_boundary_spans.push((span, first, second, target));
1780 }
1781 }
1782 for span in spans {
1783 builder.process_curve(span, first, second, &[target], &[target], false, None)?;
1784 }
1785 }
1786 }
1787 profile.lies_on += profile.lap(&mut lap_start);
1788 // CENSUS ONLY (`BREP_FIT_CENSUS=1`): the lane this pair takes under
1789 // the count-keyed and the geometry-keyed gates, and — when it is
1790 // marched here and answered exactly there — the point-set comparison
1791 // of the two, printed when `pair_census` is dropped. `imprint/gate_census.rs`.
1792 let mut pair_census = if fit_census_enabled() {
1793 census_pair(first, second, options.tolerance)?
1794 } else {
1795 None
1796 };
1797 if let Some(IsoSection { curve, pcurves }) = planar_iso {
1798 if debug_pairs {
1799 eprintln!("pair {}x{}: planar_iso", first.face.id, second.face.id);
1800 }
1801 // Slice 1: the exact pcurves ride along with the curve when
1802 // the lane is on (or checked). A face read in a lifted chart
1803 // declines: the iso line is in the carrier's own frame, and
1804 // the lifted chart's period is not the carrier's.
1805 let exact_pcurves = match (exact_iso, pcurves) {
1806 (ExactIsoPcurves::Off, _) => None,
1807 (_, Some(pcurves)) if first.lifted.is_none() && second.lifted.is_none() => {
1808 profile.exact_iso_sections += 1;
1809 Some(pcurves)
1810 }
1811 _ => {
1812 profile.exact_iso_declined += 1;
1813 None
1814 }
1815 };
1816 let lane_fits = profile.enabled.then(crate::PcurveFitScope::open);
1817 builder.process_curve(
1818 curve,
1819 first,
1820 second,
1821 &[first, second],
1822 &[first, second],
1823 true,
1824 exact_pcurves,
1825 )?;
1826 if let Some(scope) = lane_fits {
1827 profile.exact_lane_fits += scope.close().fits;
1828 }
1829 profile.process_curve += profile.lap(&mut lap_start);
1830 continue;
1831 }
1832
1833 // Recognized analytic pairs produce their exact intersection
1834 // curves (lines, circles, ellipses) directly — no marching, no
1835 // polyline fitting, no chord-sag drift. An empty result is a
1836 // proof of non-intersection and also skips the marcher. (Read
1837 // above, with the planar-iso curve.)
1838 if let Some(curves) = analytic {
1839 if debug_pairs {
1840 eprintln!(
1841 "pair {}x{}: analytic x{}",
1842 first.face.id,
1843 second.face.id,
1844 curves.len()
1845 );
1846 }
1847 // TANGENT-CURVE LANE. An exact lane's "provably empty" is a
1848 // proof about TRANSVERSE curves: a plane at exactly the
1849 // cylinder's radius from its axis has no generatrix pair, and
1850 // the pair would be dropped here without a word while the
1851 // carriers touch along a whole line. Where the classification
1852 // read the pair as near-tangent, the lane looks for the
1853 // extended contact and records it typed.
1854 if curves.is_empty()
1855 && options.tangent_curve_lane
1856 && pair_classification.relation == SurfacePairRelation::NearTangent
1857 {
1858 if let Some((mut section, run)) = tangent_curve_section(
1859 first, second, options, &first_faces, &second_faces, &face_edge_lists, debug_pairs,
1860 )? {
1861 if options.tangent_curve_imprint && section.planar && run.len() >= 2 {
1862 tangent_imprint_candidates.push((tangent_sections.len(), run, first, second));
1863 }
1864 tangent_sections.push(section);
1865 }
1866 }
1867 let lane_fits = profile.enabled.then(crate::PcurveFitScope::open);
1868 builder.exact_section = true;
1869 for curve in curves {
1870 let processed = builder.process_curve(
1871 curve,
1872 first,
1873 second,
1874 &[first, second],
1875 &[first, second],
1876 true,
1877 None,
1878 );
1879 if processed.is_err() {
1880 builder.exact_section = false;
1881 }
1882 processed?;
1883 }
1884 builder.exact_section = false;
1885 if let Some(scope) = lane_fits {
1886 profile.exact_lane_fits += scope.close().fits;
1887 }
1888 profile.process_curve += profile.lap(&mut lap_start);
1889 continue;
1890 }
1891
1892 // A pair whose every touching sample is TANGENTIAL cannot
1893 // contain a transverse intersection curve: the marcher would
1894 // walk the tangency band's noise, which neither closes nor
1895 // reaches a boundary (the distance-0 glue-extrude runaway).
1896 // Shared topology at a tangential contact comes from the
1897 // boundary-curve exchange above ("similar faces we do not
1898 // intersect" extended to tangential contacts — Golovanov §6.2;
1899 // where surfaces CROSS through a tangency, off-line samples
1900 // have non-parallel normals and the pair still marches).
1901 if pair_classification.relation == SurfacePairRelation::NearTangent
1902 && pair_classification.tangential_only
1903 {
1904 // The coarse 5×5 classifier can flag `tangential_only` off an
1905 // incidental tangential KISS between two curved carriers and
1906 // miss the transverse loop where they actually cross (two
1907 // overlapping tori: their tubes cross while their inner walls
1908 // just touch). Before honouring the skip, a GATED supplemental
1909 // detector (denser two-sided seeding, tangency-band seeds
1910 // rejected, trace-exhaustion swallowed, transverse-only
1911 // branches) checks whether a genuine transverse curve of
1912 // meaningful length lies inside BOTH trims.
1913 //
1914 // Such a pair's intersection is SINGULAR where a tangency
1915 // sits on it, and what the imprint can do about that depends
1916 // entirely on the SHAPE of the tangency — see
1917 // `imprint/tangent_contact.rs`. An isolated NODE (two branches
1918 // crossing, as every equal-radius pair produces) is already
1919 // assembled correctly by the ordinary march plus the 2D
1920 // arrangement's pinch carving; an EXTENDED contact along a
1921 // whole curve has no section to imprint at all and tears the
1922 // shell. Both are classified below and only the second is
1923 // refused. Skipping either silently would DROP the
1924 // intersection (torus∪torus double-counts; torus−torus removes
1925 // nothing — the two structural bugs the semantic oracle
1926 // found), so nothing here ever falls through quietly.
1927 let first_march = first_restricted[first_index]
1928 .as_ref()
1929 .map(|window| &window.surface)
1930 .unwrap_or(&first.face.surface);
1931 let second_march = second_restricted[second_index]
1932 .as_ref()
1933 .map(|window| &window.surface)
1934 .unwrap_or(&second.face.surface);
1935 let supplemental = super::work_profile::supplemental(
1936 first_march,
1937 second_march,
1938 &SurfaceIntersectionOptions {
1939 tolerance: options.tolerance,
1940 maximum_step: options.maximum_ssi_step,
1941 span_budget: march_span_budget,
1942 ..Default::default()
1943 },
1944 ).or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
1945 let mut dropped_length = 0.0f64;
1946 let mut clipped: Vec<Vec<Vec3>> = Vec::new();
1947 for branch in &supplemental {
1948 for run in clip_branch_to_trims(&branch.points, first, second)? {
1949 if run.len() < 2 {
1950 continue;
1951 }
1952 let length: f64 = run
1953 .windows(2)
1954 .map(|pair| pair[1].sub(pair[0]).length())
1955 .sum();
1956 dropped_length = dropped_length.max(length);
1957 clipped.push(run);
1958 }
1959 }
1960 if dropped_length <= options.tolerance * 100.0 {
1961 if debug_pairs {
1962 eprintln!(
1963 "pair {}x{}: tangential-only contact, march skipped",
1964 first.face.id, second.face.id
1965 );
1966 }
1967 // TANGENT-CURVE LANE: the skip is right for a transverse
1968 // march, which has nothing to walk here, but the contact
1969 // may still be a CURVE inside both trims (the same
1970 // generatrix on a carrier no exact lane recognises).
1971 // Recognise and record it, typed, rather than lose it.
1972 if options.tangent_curve_lane {
1973 if let Some((mut section, run)) = tangent_curve_section(
1974 first, second, options, &first_faces, &second_faces, &face_edge_lists, debug_pairs,
1975 )? {
1976 if options.tangent_curve_imprint && section.planar && run.len() >= 2 {
1977 tangent_imprint_candidates.push((tangent_sections.len(), run, first, second));
1978 }
1979 tangent_sections.push(section);
1980 }
1981 }
1982 continue;
1983 }
1984 // A transverse curve EXISTS. What would make it unimprintable is
1985 // a tangent NODE on it — and the node need not lie inside the
1986 // trims. Two equal-radius pipe arms are tangent to each other
1987 // where their axes' common perpendicular leaves the junction,
1988 // and a joint ball wider than the arms trims that crotch off
1989 // both faces; what is left inside the trims is an ordinary
1990 // crossing. So the question is not "does a transverse curve
1991 // exist" but "does the curve INSIDE BOTH TRIMS reach a
1992 // tangency". Only the latter is the checkerboard case.
1993 let mut tangency_inside_trims = false;
1994 let mut tangency_witness = Vec3::default();
1995 'clipped: for run in &clipped {
1996 for (index, &point) in run.iter().enumerate() {
1997 if pair_normals_parallel_at(first, second, point)? {
1998 tangency_inside_trims = true;
1999 tangency_witness = point;
2000 if debug_pairs {
2001 eprintln!(
2002 "pair {}x{}: TANGENCY at run index {}/{} ({:.6},{:.6},{:.6}) dropped_len={:.4e}",
2003 first.face.id, second.face.id, index, run.len(),
2004 point.x, point.y, point.z, dropped_length
2005 );
2006 }
2007 break 'clipped;
2008 }
2009 }
2010 }
2011 if tangency_inside_trims {
2012 // WHAT SHAPE is the tangency? The witness is only a marched
2013 // sample within the transverse-seed angular gate, so it is
2014 // REFINED onto the contact before being classified — at the
2015 // raw witness a G1 cylinder/torus join and a genuine
2016 // equal-radius node are eighteen-fold apart, which would be
2017 // a band; at the refined contact they are thirty orders
2018 // apart, which is a rank question. See
2019 // `imprint/tangent_contact.rs`.
2020 let contact = classify_tangent_contact(
2021 &first.face.surface,
2022 &second.face.surface,
2023 tangency_witness,
2024 options.tolerance,
2025 )?;
2026 if debug_pairs {
2027 eprintln!(
2028 "pair {}x{}: contact classification {:?}",
2029 first.face.id, second.face.id, contact
2030 );
2031 }
2032 match contact {
2033 // An isolated node: the section is a curve everywhere
2034 // but that one point, so the ordinary march below runs
2035 // and the 2D arrangement carves the pinch on both
2036 // faces. `tangent_node` records it so a march that
2037 // cannot get through still refuses in THIS class rather
2038 // than leaking the marcher's own message.
2039 Some(contact) if contact.is_isolated_node() => {
2040 tangent_node = Some(contact.point);
2041 tangent_nodes.push(contact.point);
2042 }
2043 // An extended contact (rank-deficient) or a witness we
2044 // could not refine onto any contact at all (None — so
2045 // nothing is proven and the pre-classification refusal
2046 // stands). Neither has a section curve the imprint can
2047 // represent.
2048 other => {
2049 let shape = match other {
2050 Some(contact) => contact.describe(),
2051 None => format!(
2052 "the tangency near ({:.6},{:.6},{:.6}) could not be refined \
2053 onto a contact, so its shape is unproven",
2054 tangency_witness.x, tangency_witness.y, tangency_witness.z
2055 ),
2056 };
2057 return Err(KernelRefusal::new(
2058 RefusalClass::TangentNodeSingularity,
2059 KernelStage::Intersect,
2060 format!(
2061 "boolean: unsupported singular/tangent-node surface intersection \
2062 between faces {} and {}: {shape}",
2063 first.face.id, second.face.id
2064 ),
2065 ));
2066 }
2067 }
2068 }
2069 if debug_pairs && tangent_node.is_none() {
2070 // Transverse the whole way inside both trims: the coarse
2071 // 5x5 classifier only saw the tangency the trims cut away.
2072 eprintln!(
2073 "pair {}x{}: classified tangential-only, but the curve inside both \
2074 trims is transverse ({dropped_length:.4}) — marching",
2075 first.face.id, second.face.id
2076 );
2077 }
2078 }
2079
2080 // The trim-window carriers: same surface and parameterization
2081 // over the window, so hit (u, v) values remain valid on the
2082 // originals; out-of-window intersections could never survive
2083 // clip_branch_to_trims and are not walked at all.
2084 let first_march = first_restricted[first_index]
2085 .as_ref()
2086 .map(|window| &window.surface)
2087 .unwrap_or(&first.face.surface);
2088 let second_march = second_restricted[second_index]
2089 .as_ref()
2090 .map(|window| &window.surface)
2091 .unwrap_or(&second.face.surface);
2092 let pair_hermite_lane = hermite_lane && super::hermite::refinement_carriers_eligible(
2093 &first.face.surface, &second.face.surface,
2094 );
2095 let mut seed_points = Vec::new();
2096 // Smallest |edge_tangent · surface_normal| over accepted seeds — the
2097 // local grazing measure at the trim crossings (near 0 = the edge
2098 // pierces the other surface tangentially). Gates the near-tangent
2099 // clip-order rescue below to genuinely grazing pairs.
2100 let mut min_seed_tangency = f64::INFINITY;
2101 // Two grazing crossings of one trim edge bound a real, narrow
2102 // interval. It cannot tolerate a section fit that misses either
2103 // crossing, even when a coarse whole-model distance looks small.
2104 let mut paired_grazing_crossings = false;
2105 // Every hit the seed gate rejected, for the whole pair: the
2106 // point, its `|tan·n|`, the edge (its operand, its id) and the
2107 // parameter on it, and whether that edge belongs to `first`.
2108 // Read only when the march comes back empty, below, where the
2109 // genuine crossings among them are told from lies-on samples.
2110 let mut grazing_hits: Vec<(Vec3, f64, u8, u64, f64, bool)> = Vec::new();
2111 for (face, other_march, other) in
2112 [(first, second_march, second), (second, first_march, first)]
2113 {
2114 for edge in &face_edge_lists[&face.key()] {
2115 if edge.degenerate {
2116 continue;
2117 }
2118 let mut grazing_crossings: Vec<Vec3> = Vec::new();
2119 // LIES-ON SKIP (2026-10-17, the shelled-box report's
2120 // offset bore against the offset blend walls): an edge
2121 // that LIES ON the other carrier — the test the exchange
2122 // stage above already trusted to hand the whole edge over
2123 // as a shared span — returns a hit per sample from
2124 // `intersect_curve_surface`, every one grazing (|tan.n| = 0
2125 // on a curve in the surface) and every one rejected: three
2126 // such edges answered 8 580 of that pair's 8 586 seed hits
2127 // at 0.36 s. Such an edge contributes no seed, no tangency
2128 // minimum and no section evidence; its one effect is the
2129 // paired-grazing flag, set when two of its grazing points
2130 // sit more than the weld apart, which is reproduced here
2131 // from the edge's own extent. `BREP_IMPRINT_LIES_ON_SEED_SKIP=0`
2132 // runs the solve as before.
2133 if lies_on_seed_skip_enabled() {
2134 let subcurve = cached_subcurve(face.operand, edge)?;
2135 if super::support::curve_lies_on_surface(&subcurve, other_march, options.tolerance)? {
2136 let [start, end] = subcurve.domain().or_refuse(KernelStage::Intersect, "domain")?;
2137 let mut probes = Vec::with_capacity(5);
2138 for fraction in [0.0, 0.25, 0.5, 0.75, 1.0] {
2139 probes.push(subcurve.evaluate(start + (end - start) * fraction).or_refuse(KernelStage::Intersect, "evaluate")?);
2140 }
2141 let weld = assembler_weld(options.tolerance);
2142 if probes.iter().any(|a| probes.iter().any(|b| a.sub(*b).length() > weld)) {
2143 paired_grazing_crossings = true;
2144 }
2145 if debug_pairs {
2146 eprintln!(
2147 "pair {}x{}: seed edge {} lies on the other carrier — seed solve skipped",
2148 first.face.id, second.face.id, edge.id
2149 );
2150 }
2151 continue;
2152 }
2153 }
2154 for hit in intersect_curve_surface(&edge.curve, other_march, options.tolerance).or_refuse(KernelStage::Intersect, "intersect_curve_surface")?
2155 {
2156 if hit.t < edge.t0 - 1e-9 || hit.t > edge.t1 + 1e-9 {
2157 continue;
2158 }
2159 let derivative = edge.curve.derivatives(hit.t, 1).or_refuse(KernelStage::Intersect, "derivatives")?[1];
2160 // A STATIONARY POINT on the edge curve is not a reason
2161 // to refuse the boolean. An involute flank starts at
2162 // the base circle with an exactly zero tangent, so a
2163 // gear tooth's profile carries one cusp per flank, and
2164 // a hit landing on that parameter used to propagate
2165 // "Vec3.normalized: zero-length vector" out of the
2166 // whole union (the 2026-09-14 herringbone report: the
2167 // second tooth band's z=0 cap edge meets the first
2168 // band's flank exactly at the cusp). The direction is
2169 // still defined as the one-sided limit, so read it from
2170 // just inside the edge's own range; an edge with no
2171 // direction at all simply contributes no seed, exactly
2172 // as a hit whose face normal is unavailable already
2173 // does two lines below. Seeds are marcher hints, so a
2174 // dropped one costs a hint, never an answer.
2175 // Escape hatch for tamper-verification:
2176 // BREP_CUSP_TANGENT_RESCUE=0 restores the refusal.
2177 let tangent = match derivative.normalized() {
2178 Ok(tangent) => tangent,
2179 Err(error) => {
2180 if std::env::var("BREP_CUSP_TANGENT_RESCUE").as_deref() == Ok("0") {
2181 return Err(error)
2182 .or_refuse(KernelStage::Intersect, "normalized");
2183 }
2184 match edge.curve.stationary_tangent_rescue(hit.t, edge.t0, edge.t1)
2185 {
2186 Some(tangent) => tangent,
2187 None => continue,
2188 }
2189 }
2190 };
2191 // `hit` was solved on the other face's MARCH WINDOW, so
2192 // its uv is in that face's chart — a period off the
2193 // carrier's where the window is lifted, and `normal`
2194 // clamps a parameter past its domain.
2195 let normal = match other.chart().normal(hit.u, hit.v) {
2196 Ok(normal) => normal,
2197 Err(_) => continue,
2198 };
2199 if debug_pairs {
2200 eprintln!(
2201 "pair {}x{}: seed edge {} t={:.6} p=({:.5},{:.5},{:.5}) |tan.n|={:.4} {}",
2202 first.face.id,
2203 second.face.id,
2204 edge.id,
2205 hit.t,
2206 hit.point.x,
2207 hit.point.y,
2208 hit.point.z,
2209 tangent.dot(normal).abs(),
2210 if tangent.dot(normal).abs() >= 0.1 { "ACCEPT" } else { "reject" }
2211 );
2212 }
2213 if tangent.dot(normal).abs() >= 0.1 {
2214 seed_points.push(hit.point);
2215 min_seed_tangency = min_seed_tangency.min(tangent.dot(normal).abs());
2216 section_evidence = true;
2217 } else {
2218 if grazing_crossings.iter().any(|point| {
2219 point.sub(hit.point).length() > assembler_weld(options.tolerance)
2220 }) {
2221 paired_grazing_crossings = true;
2222 }
2223 grazing_crossings.push(hit.point);
2224 grazing_hits.push((
2225 hit.point,
2226 tangent.dot(normal).abs(),
2227 face.operand,
2228 edge.id,
2229 hit.t,
2230 face.operand == first.operand && face.face.id == first.face.id,
2231 ));
2232 }
2233 }
2234 }
2235 }
2236 profile.seeds += profile.lap(&mut lap_start);
2237 // POINT CONTACT (`BREP_IMPRINT_PLANE_POINT_SKIP`, lane X row 5):
2238 // two exact parallelograms meeting at one point have no section
2239 // curve. Once a seed is accepted the pair's contact evidence is
2240 // already recorded and the march could only trace that point, so
2241 // it is skipped; an unseeded pair still marches.
2242 if !seed_points.is_empty()
2243 && plane_point_skip_enabled()
2244 && super::support::planar_point_contact(&first.face.surface, &second.face.surface, options.tolerance) == Some(true)
2245 {
2246 PLANE_POINT_SKIPS.with(|count| count.set(count.get() + 1));
2247 if debug_pairs {
2248 eprintln!("pair {}x{}: planar point contact, march skipped", first.face.id, second.face.id);
2249 }
2250 continue;
2251 }
2252 profile.marched_pairs += 1;
2253 if debug_pairs {
2254 eprintln!(
2255 "pair {}x{}: marching (relation {:?})...",
2256 first.face.id, second.face.id, pair_classification.relation
2257 );
2258 }
2259 let marched = super::work_profile::trace(
2260 first_march,
2261 second_march,
2262 &SurfaceIntersectionOptions {
2263 tolerance: options.tolerance,
2264 maximum_step: march_maximum_step(
2265 &pair_classification,
2266 options.maximum_ssi_step,
2267 builder.scale,
2268 ),
2269 seed_points: seed_points.clone(),
2270 span_budget: march_span_budget,
2271 ordered_starts,
2272 consume_starts,
2273 tree_seeds,
2274 ..Default::default()
2275 },
2276 );
2277 // A pair carrying a tangent node keeps its OWN refusal class when
2278 // the march fails: the node is why the trace cannot close, and the
2279 // equal-radius torus pair depends on being told so rather than on
2280 // reading a step-budget message it cannot act on.
2281 let marched = match (marched, tangent_node) {
2282 (Err(error), Some(node)) => {
2283 return Err(KernelRefusal::new(
2284 RefusalClass::TangentNodeSingularity,
2285 KernelStage::Intersect,
2286 format!(
2287 "boolean: unsupported singular/tangent-node surface intersection \
2288 between faces {} and {}: the section through the tangent node at \
2289 ({:.6},{:.6},{:.6}) could not be marched ({error})",
2290 first.face.id, second.face.id, node.x, node.y, node.z
2291 ),
2292 ));
2293 }
2294 (marched, _) => marched,
2295 };
2296 let mut branches = marched
2297 .map_err(|error| {
2298 format!(
2299 "{error} (marching faces {} and {})",
2300 first.face.id, second.face.id
2301 )
2302 }).or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
2303 profile.march += profile.lap(&mut lap_start);
2304 let rescue_started = profile.enabled.then(Instant::now);
2305 // MARCH-ORDER SWAP RESCUE (hatch BREP_MARCH_SWAP_RESCUE=0).
2306 // `intersect_surfaces` is not order-symmetric: the coupled Newton
2307 // trace can fail to start/continue from a valid seed when the two
2308 // surfaces are presented in one operand order yet succeed in the
2309 // other. This is the sole reason t217's `subtract(sphere, step)`
2310 // fails while every other op passes — pair 118x842 (sphere-first)
2311 // marches ZERO branches, while 842x118 (step-first, the working
2312 // a\b order) marches the section from the IDENTICAL accepted pierce
2313 // seeds. When the forward order returns no branch AND an accepted
2314 // transverse pierce seed exists (so a real section provably crosses
2315 // both trims), retry the march with the surfaces swapped — i.e.
2316 // reproduce the exact call the working operand order makes for this
2317 // pair (measured: seed_only alone does NOT recover it — the section
2318 // is found from an auto-grid start, not from the near-tangent pierce
2319 // seeds, which the seed normal-cross gate rejects). The returned
2320 // branch points are 3D and therefore order-independent, so no
2321 // parameter remap is needed; they flow through the identical clip /
2322 // process_curve gates below, which drop any out-of-trim or
2323 // sub-length run exactly as today. The rescue only ever runs when
2324 // the forward order found NOTHING, so it cannot alter a pair that
2325 // already marched.
2326 if branches.is_empty()
2327 && !seed_points.is_empty()
2328 && pair_classification.relation == SurfacePairRelation::Candidate
2329 && std::env::var("BREP_MARCH_SWAP_RESCUE").as_deref() != Ok("0")
2330 {
2331 // FAIL-SOFT: the forward order already returned Ok(empty); a
2332 // swapped-march error (trace-exhaustion is a real error class —
2333 // `intersect_surfaces_supplemental` swallows it for exactly this
2334 // reason) must NOT convert that graceful empty into a hard error.
2335 // On Err, keep the (empty) forward result and carry on.
2336 let swapped = super::work_profile::trace(
2337 second_march,
2338 first_march,
2339 &SurfaceIntersectionOptions {
2340 tolerance: options.tolerance,
2341 maximum_step: march_maximum_step(
2342 &pair_classification,
2343 options.maximum_ssi_step,
2344 builder.scale,
2345 ),
2346 seed_points: seed_points.clone(),
2347 span_budget: march_span_budget,
2348 ordered_starts,
2349 consume_starts,
2350 tree_seeds,
2351 ..Default::default()
2352 },
2353 )
2354 .unwrap_or_default();
2355 if debug_pairs {
2356 eprintln!(
2357 "pair {}x{}: MARCH-SWAP RESCUE attempt -> {} branches, pts {:?}",
2358 first.face.id,
2359 second.face.id,
2360 swapped.len(),
2361 swapped.iter().map(|b| b.points.len()).collect::<Vec<_>>()
2362 );
2363 }
2364 if !swapped.is_empty() {
2365 branches = swapped;
2366 march_swap_rescues += 1;
2367 }
2368 }
2369 // NEAR-TANGENT CLIP-ORDER RESCUE (hatch BREP_MARCH_SWAP_CLIP_RESCUE=0).
2370 // Companion to the empty-branch MARCH-SWAP RESCUE above:
2371 // `intersect_surfaces` is order-asymmetric not only in WHETHER it
2372 // marches, but in the exact sample positions of the section
2373 // polyline. On a NEAR-TANGENT graze, `clip_branch_to_trims`
2374 // classifies those samples against the mutual trims, and a sample
2375 // landing just past the near-tangent boundary is dropped as a
2376 // false-Outside that TRUNCATES the clipped section. The two operand
2377 // orders drop DIFFERENT near-tangent tail samples, so one order
2378 // yields a section ~0.1-0.2mm shorter at ONE endpoint (t660: b\a's
2379 // step-first order clips pairs 223x105/399x105 ~0.22/0.14mm short of
2380 // the section a\b's cyl-first order keeps, stranding edge 173
2381 // one-use). Near-tangent clip errors are almost exclusively
2382 // false-Outside (a point truly outside a trim rarely projects to an
2383 // in-trim uv), so the order with the LONGER clipped section suffered
2384 // fewer drops and is the more complete one. When the swapped order
2385 // marches the SAME branch structure with a meaningfully longer
2386 // clipped total, adopt its branches (3D points, so they flow through
2387 // the identical clip/process_curve gates below). Gated to Candidate
2388 // pairs (the near-tangent/ambiguous class; clean Transverse
2389 // crossings clip identically in both orders and never differ) with
2390 // an accepted GRAZING pierce seed (min |tan·n| < 0.5 — a real
2391 // section provably crosses, and does so near-tangentially, the only
2392 // regime where the clip wobbles; this also bounds the extra march to
2393 // grazing pairs), and requires a >0.1%-of-length margin so
2394 // raw-sampling jitter (t660: 0.02%) cannot flip a clean pair.
2395 // Fail-soft: any swapped-march or comparison-clip error keeps the
2396 // forward result.
2397 if !branches.is_empty()
2398 && !seed_points.is_empty()
2399 && min_seed_tangency < 0.5
2400 && pair_classification.relation == SurfacePairRelation::Candidate
2401 && std::env::var("BREP_MARCH_SWAP_CLIP_RESCUE").as_deref() != Ok("0")
2402 {
2403 let swapped = super::work_profile::trace(
2404 second_march,
2405 first_march,
2406 &SurfaceIntersectionOptions {
2407 tolerance: options.tolerance,
2408 maximum_step: march_maximum_step(
2409 &pair_classification,
2410 options.maximum_ssi_step,
2411 builder.scale,
2412 ),
2413 seed_points: seed_points.clone(),
2414 span_budget: march_span_budget,
2415 ordered_starts,
2416 consume_starts,
2417 tree_seeds,
2418 ..Default::default()
2419 },
2420 )
2421 .unwrap_or_default();
2422 // Only a swap that reproduces the SAME branch count — a
2423 // refined-endpoint variant of the same section, not a different
2424 // branch decomposition (guards against adopting a spurious
2425 // extra branch as "longer").
2426 if !swapped.is_empty() && swapped.len() == branches.len() {
2427 let totals = (|| -> Result<(f64, f64), KernelRefusal> {
2428 let mut fwd = 0.0;
2429 for b in &branches {
2430 let refined = insert_seed_points_into_branch(
2431 &b.points,
2432 &seed_points,
2433 options.tolerance,
2434 );
2435 for run in clip_branch_to_trims(&refined, first, second)? {
2436 fwd += run
2437 .windows(2)
2438 .map(|p| p[1].sub(p[0]).length())
2439 .sum::<f64>();
2440 }
2441 }
2442 let mut swp = 0.0;
2443 for b in &swapped {
2444 let refined = insert_seed_points_into_branch(
2445 &b.points,
2446 &seed_points,
2447 options.tolerance,
2448 );
2449 for run in clip_branch_to_trims(&refined, first, second)? {
2450 swp += run
2451 .windows(2)
2452 .map(|p| p[1].sub(p[0]).length())
2453 .sum::<f64>();
2454 }
2455 }
2456 Ok((fwd, swp))
2457 })();
2458 if let Ok((fwd_clip, swp_clip)) = totals {
2459 let margin = fwd_clip.max(swp_clip) * 1.0e-3;
2460 let adopt = swp_clip > fwd_clip + margin;
2461 if debug_pairs {
2462 eprintln!(
2463 "pair {}x{}: CLIP-SWAP RESCUE fwd_clip={:.6} swp_clip={:.6} margin={:.6}{}",
2464 first.face.id,
2465 second.face.id,
2466 fwd_clip,
2467 swp_clip,
2468 margin,
2469 if adopt { " ADOPT" } else { "" }
2470 );
2471 }
2472 if adopt {
2473 branches = swapped;
2474 clip_swap_rescues += 1;
2475 }
2476 }
2477 }
2478 }
2479 if let Some(started) = rescue_started {
2480 profile.rescue_march += started.elapsed();
2481 }
2482 // CROSSING-NODE SPLIT (`BREP_SSI_NODE_SPLIT`, default on, `=0` off;
2483 // `intersect/crossing_node.rs`): a branch the marcher carried
2484 // through a saddle tangent node is cut at every passage, so the
2485 // node is a vertex where the four half-branches meet.
2486 if crate::crossing_node_split_enabled() && !branches.is_empty() {
2487 let (split, nodes) = crate::split_at_crossing_nodes(
2488 first_march,
2489 second_march,
2490 std::mem::take(&mut branches),
2491 options.tolerance,
2492 )
2493 .or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
2494 branches = split;
2495 for node in nodes {
2496 if debug_pairs {
2497 eprintln!(
2498 "pair {}x{}: crossing node ({:.9},{:.9},{:.9}) split the march",
2499 first.face.id, second.face.id, node.x, node.y, node.z
2500 );
2501 }
2502 tangent_node.get_or_insert(node);
2503 if !tangent_nodes.iter().any(|known| known.sub(node).length() <= options.tolerance * 10.0) {
2504 tangent_nodes.push(node);
2505 }
2506 }
2507 }
2508 if branches.iter().any(|branch| branch.points.len() >= 2) {
2509 section_evidence = true;
2510 }
2511 if debug_pairs {
2512 eprintln!(
2513 "pair {}x{}: MARCH {} branches, pts {:?}",
2514 first.face.id,
2515 second.face.id,
2516 branches.len(),
2517 branches.iter().map(|b| b.points.len()).collect::<Vec<_>>()
2518 );
2519 }
2520 // GRAZING PIERCE, NO SECTION (2026-10-17, the revolve-into-bore
2521 // report). A trim edge of one face pierces the other carrier at
2522 // an angle below the seed gate, inside BOTH trims, and the pair
2523 // marched nothing: the pierce proves a section crosses the faces
2524 // there, the empty march says the crossing is too near tangency
2525 // for the marcher's start gate to admit (the -4° revolve of a
2526 // bored box's top face about its back edge, unioned back: the
2527 // torus hole wall continues the bore tangentially along their
2528 // shared rim and crosses it again at 0.002 rad — a third-order
2529 // contact, `boolean::attribute_to_tangent_node`). Nothing is
2530 // imprinted here; the contact is RECORDED, with the faces across
2531 // the pierced edges, so a tear downstream of it is attributed to
2532 // the contact in the tangent-node class — which declines the
2533 // perturbation ladder — instead of leaking as an anonymous
2534 // open-edge arrangement and burning the ladder's rungs (12.5 s
2535 // on the report, every rung refused by its mirror). A pair whose
2536 // pierce the trims do not both contain is left alone, as is one
2537 // with an accepted seed: those pairs the march could and should
2538 // have answered. `BREP_GRAZING_PIERCE_RECORD=0` is the hatch.
2539 if branches.is_empty()
2540 && seed_points.is_empty()
2541 && !grazing_hits.is_empty()
2542 && options.tangent_curve_lane
2543 && std::env::var("BREP_GRAZING_PIERCE_RECORD").as_deref() != Ok("0")
2544 {
2545 let mut inside: Vec<(Vec3, f64, u8, u64)> = Vec::new();
2546 for &(point, tangency, operand, edge_id, t, on_first) in &grazing_hits {
2547 // A pierce, not a lies-on sample: the edge must CROSS the
2548 // carrier at the hit (a rim lying on the other carrier
2549 // reads |tan·n| = 0 at every sample and never changes
2550 // side) — and inside both trims.
2551 let (owner, other_march) = if on_first { (first, second_march) } else { (second, first_march) };
2552 let Some(edge) = face_edge_lists[&owner.key()].iter().find(|edge| edge.id == edge_id) else {
2553 continue;
2554 };
2555 let crosses = edge_crosses_carrier(edge, &face_edge_lists[&owner.key()], t, other_march, options.tolerance)?;
2556 // The two halves of a split edge report one pierce at
2557 // their shared vertex: it is one section point, once.
2558 if crosses && inside.iter().any(|(seen, ..)| seen.sub(point).length() <= options.tolerance) {
2559 continue;
2560 }
2561 let contained = crosses && inside_both_trims(point, first, second)?;
2562 if debug_pairs && crosses {
2563 eprintln!(
2564 "pair {}x{}: grazing pierce op={operand} edge={edge_id} ({:.6},{:.6},{:.6}) |tan.n|={tangency:.4} inside both trims: {contained}",
2565 first.face.id, second.face.id, point.x, point.y, point.z
2566 );
2567 }
2568 if contained {
2569 inside.push((point, tangency, operand, edge_id));
2570 }
2571 }
2572 // SUB-GATE RE-MARCH (2026-10-17, the same report, second
2573 // half). The pierces are PROVEN section points, so the march
2574 // is retried from them alone, admitted below the marcher's
2575 // start gate: the crossing is resolvable while its lateral
2576 // uncertainty `tol / α` fits inside the tangency band's
2577 // half-width `√(2 R tol)`, i.e. `α ≥ √(tol / 2R)`, and no
2578 // carrier is curved tighter than the model, so the bound is
2579 // taken at `R = scale`. Grid starts stay gated (their
2580 // tangency-band runaway is the gate's reason). A branch that
2581 // comes back is imprinted exactly as a marched one; only when
2582 // none does is the contact recorded for the attribution. The
2583 // retry is fail-soft like the swap rescue: an Err keeps the
2584 // empty forward result. Hatch: BREP_GRAZING_SEED_MARCH=0.
2585 if !inside.is_empty() && std::env::var("BREP_GRAZING_SEED_MARCH").as_deref() != Ok("0") {
2586 let bound = (options.tolerance / (2.0 * builder.scale.max(options.tolerance))).sqrt();
2587 let pierces: Vec<Vec3> = inside.iter().map(|(point, _, _, _)| *point).collect();
2588 let rescued = super::work_profile::trace(
2589 first_march,
2590 second_march,
2591 &SurfaceIntersectionOptions {
2592 tolerance: options.tolerance,
2593 maximum_step: march_maximum_step(
2594 &pair_classification,
2595 options.maximum_ssi_step,
2596 builder.scale,
2597 ),
2598 seed_points: pierces.clone(),
2599 seed_only: true,
2600 seed_start_cross: Some(bound),
2601 span_budget: march_span_budget,
2602 ordered_starts,
2603 consume_starts,
2604 tree_seeds,
2605 ..Default::default()
2606 },
2607 )
2608 .unwrap_or_default();
2609 if debug_pairs {
2610 eprintln!(
2611 "pair {}x{}: SUB-GATE MARCH from {} pierce(s) at bound {bound:.3e} -> {} branches, pts {:?}",
2612 first.face.id,
2613 second.face.id,
2614 pierces.len(),
2615 rescued.len(),
2616 rescued.iter().map(|b| b.points.len()).collect::<Vec<_>>()
2617 );
2618 }
2619 if !rescued.is_empty() {
2620 branches = rescued;
2621 grazing_march = true;
2622 // The pierces are the branches' exact trim crossings,
2623 // inserted below like any accepted seed.
2624 seed_points.extend(pierces);
2625 inside.clear();
2626 }
2627 }
2628 if !inside.is_empty() {
2629 if debug_pairs {
2630 eprintln!(
2631 "pair {}x{}: {} grazing pierce(s) inside both trims and no marched branch: {}",
2632 first.face.id,
2633 second.face.id,
2634 inside.len(),
2635 inside
2636 .iter()
2637 .map(|(point, tangency, operand, edge)| format!(
2638 "op={operand} edge={edge} ({:.6},{:.6},{:.6}) |tan.n|={tangency:.4}",
2639 point.x, point.y, point.z
2640 ))
2641 .collect::<Vec<_>>()
2642 .join(", ")
2643 );
2644 }
2645 // The extended contact the lane can name, when it finds
2646 // one (the shared rim here); else the record stands on
2647 // the pierces alone.
2648 let mut section = match tangent_curve_section(
2649 first, second, options, &first_faces, &second_faces, &face_edge_lists, debug_pairs,
2650 )? {
2651 Some((section, _run)) => section,
2652 None => TangentSectionRecord {
2653 first: first.key(),
2654 second: second.key(),
2655 start: inside[0].0,
2656 end: inside[inside.len() - 1].0,
2657 length: 0.0,
2658 closed: false,
2659 stations: 0,
2660 planar: false,
2661 faces: vec![first.key(), second.key()],
2662 piece_ids: Vec::new(),
2663 grazing: Vec::new(),
2664 },
2665 };
2666 // The faces across each pierced edge: the pierce is where
2667 // the missing section meets that edge, so those faces are
2668 // the ones a tear downstream of it will name.
2669 for (_, _, operand, edge_id) in &inside {
2670 let siblings = if *operand == first.operand { &first_faces } else { &second_faces };
2671 for sibling in siblings.iter() {
2672 let shares = sibling
2673 .face
2674 .loops
2675 .iter()
2676 .flat_map(|loop_record| &loop_record.coedges)
2677 .any(|coedge| coedge.edge_id == *edge_id);
2678 if shares && !section.faces.contains(&sibling.key()) {
2679 section.faces.push(sibling.key());
2680 }
2681 }
2682 }
2683 section.grazing = inside.iter().map(|(point, tangency, _, _)| (*point, *tangency)).collect();
2684 tangent_sections.push(section);
2685 }
2686 }
2687 if profile.enabled {
2688 // Net metadata only: observing carriers must not warm their
2689 // recognition/projection caches before the section fitter.
2690 profile_line!("imprint.march_pair first={:?} second={:?} ids={}x{} degrees={}x{}/{}x{} nets={}x{}/{}x{} stations={:?}",
2691 first.face.name, second.face.name, first.face.id, second.face.id,
2692 first.face.surface.degree_u, first.face.surface.degree_v,
2693 second.face.surface.degree_u, second.face.surface.degree_v,
2694 first.face.surface.control_points.len(), first.face.surface.control_points.first().map_or(0, Vec::len),
2695 second.face.surface.control_points.len(), second.face.surface.control_points.first().map_or(0, Vec::len),
2696 branches.iter().map(|branch| branch.points.len()).collect::<Vec<_>>());
2697 }
2698 // GRAZING PIERCES BOUND CLIP INTERVALS TOO (hosted report
2699 // 2026-11-01, "Boolean seems to take way too long": the cage-box
2700 // union with the cylinder moved 0.165 off the cage's axis). The
2701 // seed gate rejects a pierce whose `|tan·n|` is under 0.1 as a
2702 // march START, but the point is still an exact section point on
2703 // a trim edge: the end of a trim interval. The cylinder's bottom
2704 // rim crosses the cage's flat-to-second-order side twice, 0.036
2705 // apart, at `|tan·n|` = 0.0035, so the disc keeps a 0.036-long
2706 // piece of the y = 0 section there. Marched from the accepted
2707 // seeds with ~0.9 stations, that interval held no station, the
2708 // clip dropped it, the disc's two lenses stayed unbounded and
2709 // the arrangement tore: an open-edge refusal that sent the union
2710 // up the perturbation ladder (64 imprints, ~39 s native, every
2711 // rung refused). Inserting the rejected hits — gated, like every
2712 // seed, to the ones lying on the branch — gives such an interval
2713 // its endpoints. Only a RESOLVABLE pierce is inserted: its
2714 // position along the edge is known to `tol / |tan·n|`, so the
2715 // same bound the sub-gate re-march below admits a start at,
2716 // `|tan·n| >= √(tol / 2·scale)`, keeps the lateral uncertainty
2717 // inside the tangency band. Below it the hits are lies-on
2718 // samples (an edge in the other carrier reads |tan·n| = 0 at
2719 // every sample) or near-tangent brushes whose along-edge
2720 // position is a guess: the 2026-10-25 offset-shell history
2721 // (`successive_fillet_history_shells_under_the_one_call_imprint`)
2722 // inserts 25 pairs' worth of such points at |tan·n| ≤ 1.1e-4 and
2723 // its O.S10 pcurve fit then misses its stations by 2.6e-3.
2724 // `BREP_GRAZING_CLIP_REFINE=0` restores the accepted seeds alone.
2725 let clip_points: std::borrow::Cow<'_, [Vec3]> = if grazing_hits.is_empty()
2726 || std::env::var("BREP_GRAZING_CLIP_REFINE").as_deref() == Ok("0")
2727 {
2728 std::borrow::Cow::Borrowed(&seed_points)
2729 } else {
2730 let resolvable = (options.tolerance / (2.0 * builder.scale.max(options.tolerance))).sqrt();
2731 // A crease fading to tangency along a rim brushes it at |tan.n|
2732 // around 1e-4 for its last stretch, and those hits cut the
2733 // section into micro-pieces whose ends disagree; the interval
2734 // this path exists for pierced at 3.5e-3. `BREP_GRAZING_CLIP_FLOOR=0`
2735 // restores the resolvable bound alone.
2736 let resolvable = if switch!("BREP_GRAZING_CLIP_FLOOR", true) { resolvable.max(1e-3) } else { resolvable };
2737 let mut points = seed_points.clone();
2738 points.extend(grazing_hits.iter().filter(|hit| hit.1 >= resolvable).map(|hit| hit.0));
2739 if debug_pairs && points.len() != seed_points.len() {
2740 eprintln!(
2741 "pair {}x{}: {} grazing pierce(s) at |tan.n| >= {resolvable:.3e} join the trim clip",
2742 first.face.id, second.face.id, points.len() - seed_points.len()
2743 );
2744 }
2745 std::borrow::Cow::Owned(points)
2746 };
2747 for branch in branches {
2748 // The pierce seeds are the section's exact trim-crossing
2749 // points — insert them so no trim interval shorter than the
2750 // march step is invisible to the point-classification clip.
2751 let refined_points =
2752 insert_seed_points_into_branch(&branch.points, &clip_points, options.tolerance);
2753 let clip_started = profile.enabled.then(Instant::now);
2754 let mut runs = clip_branch_to_trims(&refined_points, first, second)?;
2755 // TANGENT-RIM LANDING: a run the march stalled short of a trim
2756 // edge the two carriers are tangent along ends ON that edge
2757 // (see `land_run_ends_on_tangent_rim`).
2758 // Reached for the pairs whose march can stall short: one
2759 // carrying an admitted node, one marched from sub-gate
2760 // pierces, or one the classifier read as near-tangent (the
2761 // −10° sibling: no node admitted, an ordinary march that
2762 // stops 4e-5 short of the rim it is tangent along).
2763 if tangent_node.is_some()
2764 || grazing_march
2765 || pair_classification.relation == SurfacePairRelation::NearTangent
2766 {
2767 for run in runs.iter_mut() {
2768 for (operand, edge_id, parameter, from, to) in land_run_ends_on_tangent_node(
2769 run,
2770 first,
2771 second,
2772 &face_edge_lists,
2773 options.tolerance,
2774 )? {
2775 if debug_pairs {
2776 eprintln!(
2777 "pair {}x{}: tangent-node landing: run end ({:.6},{:.6},{:.6}) -> \
2778 node ({:.9},{:.9},{:.9}) on operand {operand} edge {edge_id} at t={parameter:.9}",
2779 first.face.id, second.face.id, from.x, from.y, from.z, to.x, to.y, to.z
2780 );
2781 }
2782 let edge = builder.edges[&(operand, edge_id)];
2783 builder.add_edge_split(operand, edge, parameter)?;
2784 }
2785 for (operand, edge_id, parameter, from, to) in land_run_ends_on_tangent_rim(
2786 run,
2787 tangent_node,
2788 first,
2789 second,
2790 &face_edge_lists,
2791 options.tolerance,
2792 )? {
2793 if debug_pairs {
2794 eprintln!(
2795 "pair {}x{}: tangent-rim landing: run end ({:.6},{:.6},{:.6}) -> \
2796 ({:.6},{:.6},{:.6}) on operand {operand} edge {edge_id} at t={parameter:.6}",
2797 first.face.id, second.face.id, from.x, from.y, from.z, to.x, to.y, to.z
2798 );
2799 }
2800 let edge = builder.edges[&(operand, edge_id)];
2801 builder.add_edge_split(operand, edge, parameter)?;
2802 }
2803 }
2804 }
2805 if debug_pairs {
2806 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<_>>();
2807 eprintln!(
2808 "pair {}x{}: DUPS branch {:?} seeded {:?} runs {:?} seeds {} branch_pts {} seeded_pts {}",
2809 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()
2810 );
2811 }
2812 if let Some(started) = clip_started {
2813 profile.clip += started.elapsed();
2814 }
2815 for run in runs {
2816 if debug_pairs {
2817 eprintln!(
2818 "pair {}x{}: clip run len_pts={} length={:.4e}",
2819 first.face.id,
2820 second.face.id,
2821 run.len(),
2822 run.windows(2)
2823 .map(|pair| pair[1].sub(pair[0]).length())
2824 .sum::<f64>()
2825 );
2826 }
2827 if run.len() < 2 {
2828 continue;
2829 }
2830 let length: f64 = run
2831 .windows(2)
2832 .map(|pair| pair[1].sub(pair[0]).length())
2833 .sum();
2834 if length <= options.tolerance * 100.0 {
2835 continue;
2836 }
2837 let shared_started = profile.enabled.then(Instant::now);
2838 let follows_shared = branch_follows_shared_boundary(
2839 &run,
2840 first,
2841 second,
2842 &builder.edges,
2843 options.tolerance,
2844 builder.scale,
2845 )?;
2846 if let Some(started) = shared_started {
2847 profile.shared_boundary += started.elapsed();
2848 }
2849 if follows_shared {
2850 if debug_pairs {
2851 eprintln!(
2852 "pair {}x{}: run dropped (follows shared boundary)",
2853 first.face.id, second.face.id
2854 );
2855 }
2856 continue;
2857 }
2858 if let Some(census) = pair_census.as_mut() {
2859 census.record_stations(&run);
2860 }
2861 let pieces_before = builder.pieces.len();
2862 let chunk_points = options.fit_chunk_points.unwrap_or(run.len()).max(2);
2863 let mut start = 0;
2864 while start + 1 < run.len() {
2865 let end = (start + chunk_points - 1).min(run.len() - 1);
2866 // HERMITE EMISSION (`imprint/hermite.rs`): with the lane on
2867 // and every span of this run within the budget, the section
2868 // is the march's own cubic Hermite with its two parameter
2869 // Hermites, and the fit lane below is skipped.
2870 let hermite_started = profile.enabled.then(Instant::now);
2871 let hermite: Option<Result<super::hermite::HermiteSection, super::hermite::HermiteDecline>> = if pair_hermite_lane && end + 1 - start >= hermite_min_stations {
2872 Some(match emission_budget {
2873 Some(budget) => super::hermite::hermite_section(&run[start..=end], &branch, first_march, second_march, budget)
2874 .or_refuse(KernelStage::Intersect, "csg.imprint.driver.hermite")?,
2875 None => Err(super::hermite::HermiteDecline::NoTangents),
2876 })
2877 } else {
2878 None
2879 };
2880 if let Some(started) = hermite_started {
2881 profile.hermite += started.elapsed();
2882 }
2883 let (section_curve, exact_pcurves) = match hermite {
2884 Some(Ok(emitted)) => {
2885 super::work_profile::add(|c| { c.carried_uv_curves += 2; c.carried_uv_stations += 2 * emitted.stations as u64; });
2886 profile.hermite_runs += 1;
2887 span_census(|| {
2888 format!(
2889 "pair {}x{} budget {} branch_stations {} run_stations {} kept {} span_deviation {:.3e} tangents true route None met_requested true lane hermite inserted {}",
2890 first.face.id,
2891 second.face.id,
2892 march_span_budget.map_or("none".to_string(), |budget| format!("{budget:.3e}")),
2893 branch.points.len(),
2894 emitted.stations,
2895 emitted.stations,
2896 emitted.deviation,
2897 emitted.inserted,
2898 )
2899 });
2900 (emitted.curve, Some([emitted.pcurve_a, emitted.pcurve_b]))
2901 }
2902 other => {
2903 let hermite_decline = other.map(|lane| lane.err());
2904 profile.fit_runs += 1;
2905 let fit_started = profile.enabled.then(Instant::now);
2906 let fit_tolerance = options.tolerance.max(1e-7);
2907 let (mut section, mut fit, recovered, evening) = even_sliver_ends_measured(
2908 run[start..=end].to_vec(),
2909 &first.face.surface,
2910 &second.face.surface,
2911 options.tolerance,
2912 fit_tolerance,
2913 options.maximum_fit_points,
2914 options.local_fit,
2915 )
2916 .or_refuse(KernelStage::Intersect, "csg.imprint.driver.sliver_end")?;
2917 if evening != SliverEvening::NoSliver {
2918 if let Ok(path) = std::env::var("BREP_SECTION_SLIVER_CENSUS") {
2919 use std::io::Write;
2920 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
2921 let _ = writeln!(file, "sliver-end census: pair {}x{} {evening:?} stations {}", first.face.id, second.face.id, section.len());
2922 }
2923 }
2924 }
2925 // A recovered fit may reproduce every march point yet
2926 // still wander off the carriers between sparse stations.
2927 // Measure it against both supports before accepting that
2928 // recovery, and use the existing bounded SSI refiner.
2929 let met_requested = fit.report.met_requested_tolerance();
2930 let refine_route = section_refine_route(
2931 section_refine_gate(),
2932 recovered,
2933 || {
2934 paired_grazing_crossings
2935 && std::env::var("BREP_GRAZE_SECTION_REFINE").as_deref() != Ok("0")
2936 },
2937 met_requested,
2938 )
2939 .or_else(|| {
2940 // `BREP_SECTION_CARRIER_MISS=0` restores the fast path
2941 // for an attribution A/B only (default on).
2942 if std::env::var("BREP_SECTION_CARRIER_MISS").as_deref() == Ok("0") {
2943 return None;
2944 }
2945 let (station_upper, midspan_upper) =
2946 fit_split_timed(FitSplitLane::CarrierReadings, || fast_path_carrier_readings(&fit, §ion, &first.face.surface, &second.face.surface));
2947 carrier_miss_route(station_upper, midspan_upper, fit_tolerance)
2948 });
2949 if let Some((gate, route)) = refine_route {
2950 let stations_before = fit.kept.len();
2951 let refine_census = std::env::var("BREP_SECTION_REFINE_CENSUS").ok();
2952 let (census_head, census_tail) = (section[0], section[section.len() - 1]);
2953 let refine_started = refine_census.is_some().then(Instant::now);
2954 // The wrapper's own two lines, inlined so the receipt
2955 // can be read (behaviour identical to
2956 // `refine_section_against_carriers`).
2957 let representatives = std::env::var("BREP_SECTION_REPRESENTATIVES").as_deref() != Ok("0");
2958 let mut receipt = RefineReceipt::default();
2959 let (refined, worst, unrefined, rounds, exit) = refine_section_against_carriers_with(
2960 &mut section,
2961 fit,
2962 &first.face.surface,
2963 &second.face.surface,
2964 fit_tolerance,
2965 options.tolerance,
2966 options.local_fit,
2967 gate * fit_tolerance,
2968 representatives,
2969 &mut receipt,
2970 ).or_refuse(KernelStage::Intersect, "csg.imprint.driver.midspan")?;
2971 let _ = &receipt;
2972 // `BREP_SECTION_REFINE_CENSUS=<file>`: one line per section,
2973 // appended, so a corpus replay (whose children's stderr is
2974 // captured) can be read for trip counts and growth.
2975 // The route, whether the standing fit met its request, and
2976 // the refiner's own wall time close the line, so a run can
2977 // total the UNMET-REQUEST route's cost apart from the
2978 // global gate's (which reads every section).
2979 if let Some(path) = refine_census {
2980 use std::io::Write;
2981 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
2982 let _ = writeln!(
2983 file,
2984 "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})",
2985 first.face.id, second.face.id, exit, rounds, stations_before, refined.kept.len(), unrefined, worst,
2986 route, met_requested, refine_started.map_or(0.0, |started| started.elapsed().as_secs_f64() * 1e3),
2987 census_head.x, census_head.y, census_head.z, census_tail.x, census_tail.y, census_tail.z
2988 );
2989 }
2990 }
2991 if debug_pairs && (rounds > 0 || !matches!(exit, MidspanExit::Converged | MidspanExit::BelowGate)) {
2992 eprintln!(
2993 "pair {}x{}: mid-span refinement {:?} after {} round(s): {} stations, worst {:.3e}",
2994 first.face.id, second.face.id, exit, rounds, section.len(), worst
2995 );
2996 }
2997 fit = refined;
2998 }
2999 span_census(|| {
3000 format!(
3001 "pair {}x{} budget {} branch_stations {} run_stations {} kept {} span_deviation {} tangents {} route {:?} met_requested {} lane fit hermite {}",
3002 first.face.id,
3003 second.face.id,
3004 march_span_budget.map_or("none".to_string(), |budget| format!("{budget:.3e}")),
3005 branch.points.len(),
3006 section.len(),
3007 fit.kept.len(),
3008 branch.span_deviation.map_or("none".to_string(), |deviation| format!("{deviation:.3e}")),
3009 branch.tangents.is_some(),
3010 refine_route.map(|(_, route)| route),
3011 met_requested,
3012 hermite_decline.map_or("off".to_string(), |decline| decline.map_or("none".to_string(), |decline| format!("{decline:?}"))),
3013 )
3014 });
3015 // `BREP_SECTION_OVERSHOOT_CENSUS=<file>` (debug only; nothing
3016 // reads it): EVERY section's final fit, on any route, is read
3017 // against both carriers at its kept stations and at its
3018 // mid-spans, so a fit that stands further off the carriers
3019 // between its stations than at them (fixture 28 t217: 5.07e-4
3020 // shipped against a 1.32e-4 worst station) is counted, not
3021 // guessed. Projector distances are upper readings; the line
3022 // says so by its field names.
3023 {
3024 if let Ok(path) = std::env::var("BREP_SECTION_OVERSHOOT_CENSUS") {
3025 // An unreadable station makes the whole reading NaN
3026 // (f64::max would silently drop it).
3027 let read = |surface: &NurbsSurface, point: Vec3| {
3028 project_point_to_surface(surface, point).map_or(f64::NAN, |projection| projection.distance)
3029 };
3030 let mut station_worst = 0.0f64;
3031 for point in &fit.kept {
3032 let (a, b) = (read(&first.face.surface, *point), read(&second.face.surface, *point));
3033 station_worst = if a.is_nan() || b.is_nan() || station_worst.is_nan() { f64::NAN } else { station_worst.max(a).max(b) };
3034 }
3035 let (run_worst, _) = fast_path_carrier_readings(&fit, §ion, &first.face.surface, &second.face.surface);
3036 let interior_worst = knot_interior_carrier_upper(&fit.curve, &first.face.surface, &second.face.surface);
3037 let misses = midspan_misses_only(&fit, &first.face.surface, &second.face.surface).unwrap_or_default();
3038 let (worst_span, midspan_worst) = misses
3039 .iter()
3040 .copied()
3041 .enumerate()
3042 .fold((usize::MAX, 0.0f64), |best, (span, miss)| if miss > best.1 { (span, miss) } else { best });
3043 let line = format!(
3044 "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",
3045 first.face.id, second.face.id, refine_route.map(|(_, route)| route), fit.kept.len(), misses.len(), station_worst, midspan_worst,
3046 if worst_span == usize::MAX { -1 } else { worst_span as i64 }, fit_tolerance,
3047 midspan_worst / station_worst.max(fit_tolerance),
3048 section.len(),
3049 section[0].x, section[0].y, section[0].z,
3050 section[section.len() - 1].x, section[section.len() - 1].y, section[section.len() - 1].z
3051 );
3052 use std::io::Write;
3053 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
3054 let _ = file.write_all(line.as_bytes());
3055 }
3056 }
3057 }
3058 if let Some(started) = fit_started {
3059 profile.fit += started.elapsed();
3060 }
3061 if let Some(census) = pair_census.as_mut() {
3062 census.record_fit(&fit);
3063 }
3064 (fit.curve, None)
3065 }
3066 };
3067 let process_started = profile.enabled.then(Instant::now);
3068 builder.process_curve(
3069 section_curve,
3070 first,
3071 second,
3072 &[first, second],
3073 &[first, second],
3074 true,
3075 exact_pcurves,
3076 )?;
3077 if let Some(started) = process_started {
3078 profile.process += started.elapsed();
3079 }
3080 start = end;
3081 }
3082 for piece in &builder.pieces[pieces_before..] {
3083 builder.marched_pieces.insert(piece.id);
3084 }
3085 if debug_pairs {
3086 eprintln!(
3087 "pair {}x{}: run -> {} pieces",
3088 first.face.id,
3089 second.face.id,
3090 builder.pieces.len() - pieces_before
3091 );
3092 }
3093 }
3094 }
3095 profile.clip_and_fit += profile.lap(&mut lap_start);
3096 }
3097 }
3098 for (span, first, second, target) in deferred_boundary_spans {
3099 builder.process_curve(span, first, second, &[target], &[target], false, None)?;
3100 }
3101 if let Some(scope) = pairs_fits {
3102 profile.pairs_fits = scope.close().fits;
3103 }
3104 super::work_profile::faces(first_faces.iter().chain(&second_faces)
3105 .map(|face| (face.face, builder.by_face.contains_key(&face.key()))));
3106 profile.report();
3107 let hull_culls = super::builder::take_hull_culls();
3108 if profile.enabled && super::builder::hull_cull_enabled() {
3109 profile_line!("imprint.hull_culls edges={hull_culls}");
3110 }
3111 // The row counters are drained only when they are printed, so a test
3112 // reading them after a whole operation sees every call's count.
3113 if profile.enabled && super::builder::pierce_cull_enabled() {
3114 profile_line!("imprint.pierce_culls edges={}", super::builder::take_pierce_culls());
3115 }
3116 if profile.enabled {
3117 let overlap_hull_skips = super::sections::take_overlap_hull_skips();
3118 if overlap_hull_skips > 0 {
3119 profile_line!("imprint.overlap_hull_skips stations={overlap_hull_skips}");
3120 }
3121 let exchange_hull_skips = super::builder::take_exchange_hull_skips();
3122 if exchange_hull_skips > 0 {
3123 profile_line!("imprint.exchange_hull_skips answered={exchange_hull_skips}");
3124 }
3125 }
3126 // SELF-TOUCH SPLIT: a face whose loops touch at an edge interior (a hole
3127 // tangent to a fillet setback) gets a vertex minted at the touch on BOTH
3128 // edges, so the fragment arrangement's pinch resolution assembles — see
3129 // `imprint/self_touch.rs`. Escape hatch: BREP_SELF_TOUCH_SPLIT=0.
3130 let mut tail_lap: Option<Instant> = None;
3131 // Beside each stage's time, the pcurve fits it recorded (a nested
3132 // `PcurveFitScope` per stage, folded into the operation's ledger).
3133 let mut tail_fits: Option<crate::PcurveFitScope> = None;
3134 let mut tail_ms = Vec::<(&str, f64, u64)>::new();
3135 let mut lap = |name: &'static str,
3136 started: &mut Option<Instant>,
3137 fits: &mut Option<crate::PcurveFitScope>,
3138 tail_ms: &mut Vec<(&str, f64, u64)>| {
3139 if profile.enabled {
3140 let now = Instant::now();
3141 let recorded = fits.take().map_or(0, |scope| scope.close().fits);
3142 if let Some(previous) = *started {
3143 tail_ms.push((name, (now - previous).as_secs_f64() * 1_000.0, recorded));
3144 }
3145 *started = Some(now);
3146 *fits = Some(crate::PcurveFitScope::open());
3147 }
3148 };
3149 lap("pairs", &mut tail_lap, &mut tail_fits, &mut tail_ms);
3150 if std::env::var("BREP_SELF_TOUCH_SPLIT").as_deref() != Ok("0") {
3151 let scans_before = super::self_touch::touch_scans();
3152 builder.split_self_touching_loops(&first_faces, &face_edge_lists, &cached_subcurve)?;
3153 builder.split_self_touching_loops(&second_faces, &face_edge_lists, &cached_subcurve)?;
3154 if profile.enabled {
3155 profile_line!("imprint.self_touch faces={} scanned={}", first_faces.len() + second_faces.len(), super::self_touch::touch_scans() - scans_before);
3156 }
3157 }
3158 // PIECE-ENDPOINT EXCHANGE post-pass: with every pair's process_curve
3159 // done, the piece set is final for the ridden-edge class — imprint each
3160 // open riding piece's junction endpoints onto the ridden edges (see the
3161 // method doc; hatch BREP_OVERLAP_PIECE_ENDPOINT_SPLIT=0).
3162 builder.exchange_piece_endpoint_junctions()?;
3163 // TANGENT-SECTION IMPRINT (`ImprintOptions::tangent_curve_imprint`), after
3164 // every pair: whether a run splits a face is read against the face's trim
3165 // AND the pieces the other pairs minted on it, so the runs are minted
3166 // once the piece set is complete (the through-bore's contact ends on the
3167 // bore's cap circles, which the box's top and bottom minted).
3168 for (index, run, first, second) in std::mem::take(&mut tangent_imprint_candidates) {
3169 let closed = tangent_sections[index].closed;
3170 tangent_sections[index].piece_ids = imprint_tangent_run(
3171 &mut builder,
3172 &run,
3173 closed,
3174 first,
3175 second,
3176 &face_edge_lists,
3177 options,
3178 std::env::var("BREP_DEBUG_PAIRS").is_ok(),
3179 )?;
3180 }
3181 // TANGENT-SECTION T-JUNCTIONS (`ImprintOptions::tangent_curve_imprint`):
3182 // an imprinted contact ends on operand edges, which `process_curve` split,
3183 // and on earlier SECTIONS of the same carrier, which nothing splits — the
3184 // through-bore's contact line ends on the two cap circles the box's top
3185 // and bottom minted on the bore. Split those pieces at the contact's end
3186 // vertices so both faces of each circle carry the vertex the bore's
3187 // arrangement needs there (see `split_pieces_at_tangent_ends`).
3188 if tangent_sections.iter().any(|section| !section.piece_ids.is_empty()) {
3189 split_pieces_at_tangent_ends(
3190 &mut builder,
3191 &tangent_sections,
3192 &first_faces,
3193 &second_faces,
3194 options,
3195 std::env::var("BREP_DEBUG_PAIRS").is_ok(),
3196 )?;
3197 }
3198 lap("self_touch", &mut tail_lap, &mut tail_fits, &mut tail_ms);
3199 let mut edge_splits = builder
3200 .edge_splits
3201 .into_iter()
3202 .map(|((operand, edge_id), mut parameters)| {
3203 parameters.sort_by(f64::total_cmp);
3204 EdgeSplitRecord {
3205 operand,
3206 edge_id,
3207 parameters,
3208 }
3209 })
3210 .collect::<Vec<_>>();
3211 edge_splits.sort_by_key(|record| (record.operand, record.edge_id));
3212 let mut by_face = builder
3213 .by_face
3214 .into_iter()
3215 .map(|(face, piece_ids)| FaceImprints {
3216 operand: face.operand,
3217 face_id: face.face_id,
3218 piece_ids,
3219 })
3220 .collect::<Vec<_>>();
3221 by_face.sort_by_key(|record| (record.operand, record.face_id));
3222 let section_evidence = section_evidence || !builder.pieces.is_empty();
3223 let marched_pieces = std::mem::take(&mut builder.marched_pieces);
3224 let mut emitted_pcurves = std::mem::take(&mut builder.emitted_pcurves);
3225 let junction_evidence = JunctionEvidence {
3226 radii: std::mem::take(&mut builder.vertex_radii),
3227 sources: std::mem::take(&mut builder.vertex_sources),
3228 };
3229 let mut result = ImprintResultRecord {
3230 tangent_nodes,
3231 tangent_sections,
3232 vertices: builder.vertices,
3233 pieces: builder.pieces,
3234 by_face,
3235 edge_splits,
3236 barrier_edges: builder.barrier_edges.into_iter().collect(),
3237 section_evidence,
3238 cosurface_pairs,
3239 };
3240 RESCUE_FIRINGS.with(|cell| cell.set((march_swap_rescues, clip_swap_rescues)));
3241 if profile.enabled {
3242 profile_line!("imprint.rescues march_swap={march_swap_rescues} clip_swap={clip_swap_rescues}");
3243 }
3244 // COINCIDENT-PIECE MERGE (and the generic one-circle-from-many-pairs
3245 // class): the SAME section curve can be minted by several pairs — a
3246 // cosurface boundary-edge copy (the cylinder cap ring lying ON the
3247 // inscribed sphere) AND the cap-plane's analytic section ring are one
3248 // circle minted twice, each carrying only its own pair's
3249 // supports/pcurves. Assembly then builds duplicate edges that cannot both
3250 // be two-use → one-use strands. Merge pieces whose curves coincide along
3251 // their whole span (bidirectional max deviation within the weld band):
3252 // keep the first, union the supports/pcurves/by_face registrations of the
3253 // rest into it. Escape hatch: BREP_COINCIDENT_PIECE_MERGE=0.
3254 lap("collect", &mut tail_lap, &mut tail_fits, &mut tail_ms);
3255 if std::env::var("BREP_COINCIDENT_PIECE_MERGE").as_deref() != Ok("0") {
3256 let weld = assembler_weld(options.tolerance).max(options.tolerance * 10.0);
3257 // Two pieces that coincide along their WHOLE spans (deviation within
3258 // the weld both ways) also have matching end points, up to direction
3259 // and a weld or two of overhang, so a piece whose ends are nowhere
3260 // near the other's needs no 33-station projection sweep. The sweep is
3261 // what made this pass quadratic in wall time: 630 pieces cost 3.2 s
3262 // of a 3.8 s imprint on the 2026-09-12 mesh-import report, all of it
3263 // rejecting pairs an end-point look already rules out.
3264 let mut ends: Vec<[Vec3; 2]> = Vec::with_capacity(result.pieces.len());
3265 for piece in &result.pieces {
3266 let [d0, d1] = piece.curve.domain().or_refuse(KernelStage::Intersect, "domain")?;
3267 ends.push([
3268 piece.curve.evaluate(d0).or_refuse(KernelStage::Intersect, "evaluate")?,
3269 piece.curve.evaluate(d1).or_refuse(KernelStage::Intersect, "evaluate")?,
3270 ]);
3271 }
3272 let end_band = 4.0 * weld;
3273 // CONTROL-HULL CULL (2026-10-10): the plate minus 36 cylinders mints 72
3274 // CLOSED rings, every one of which took the full sweep against every other —
3275 // 2 556 pairs, 230 of the imprint's 288 ms — although rings 16 units apart
3276 // cannot coincide. A NURBS curve lies in the convex hull of its control
3277 // points, so two curves within `weld` of each other along their whole spans
3278 // have control boxes that overlap once each is grown by `weld`; a pair whose
3279 // boxes do not is skipped before any projection. Exact, so the merged set is
3280 // the one the sweep alone found. `BREP_COINCIDENT_MERGE_BOX=0` restores the
3281 // unculled sweep for a before/after reading.
3282 let box_cull = std::env::var("BREP_COINCIDENT_MERGE_BOX").as_deref() != Ok("0");
3283 let mut boxes: Vec<[Vec3; 2]> = Vec::with_capacity(result.pieces.len());
3284 for piece in &result.pieces {
3285 boxes.push(control_hull_box(&piece.curve, weld));
3286 }
3287 // A CLOSED piece (a full ring) has no end points to speak of: two
3288 // rings of the same circle seamed at different azimuths coincide
3289 // along their whole spans while their domain ends sit anywhere on
3290 // the ring, so a closed piece always takes the full sweep.
3291 let ends_match = |a: &[Vec3; 2], b: &[Vec3; 2]| -> bool {
3292 let near = |p: Vec3, q: Vec3| p.sub(q).length() <= end_band;
3293 near(a[0], a[1])
3294 || near(b[0], b[1])
3295 || (near(a[0], b[0]) && near(a[1], b[1]))
3296 || (near(a[0], b[1]) && near(a[1], b[0]))
3297 };
3298 let mut removed: Vec<u64> = Vec::new();
3299 let mut index = 0;
3300 while index < result.pieces.len() {
3301 let mut other = index + 1;
3302 while other < result.pieces.len() {
3303 let coincide = (!box_cull || boxes_overlap(&boxes[index], &boxes[other]))
3304 && ends_match(&ends[index], &ends[other])
3305 && {
3306 let a = &result.pieces[index];
3307 let b = &result.pieces[other];
3308 // FEATURE SCALE B2 (`BREP_FEATURE_SCALE_IMPRINT`):
3309 // the weld (1e-6) equals the hairline slot's width,
3310 // so the two walls' pieces read as one; beside a
3311 // feature the weld is capped at a quarter of it.
3312 let weld = if tail_feature_scale.is_empty() {
3313 weld
3314 } else {
3315 tail_feature_scale.cap(ends[index][0], weld).min(tail_feature_scale.cap(ends[index][1], weld))
3316 };
3317 max_curve_deviation(&a.curve, &b.curve)? <= weld
3318 && max_curve_deviation(&b.curve, &a.curve)? <= weld
3319 };
3320 if coincide {
3321 let absorbed = result.pieces.remove(other);
3322 ends.remove(other);
3323 boxes.remove(other);
3324 removed.push(absorbed.id);
3325 let keeper = &mut result.pieces[index];
3326 for pcurve in absorbed.pcurves {
3327 if !keeper
3328 .pcurves
3329 .iter()
3330 .any(|existing| {
3331 existing.operand == pcurve.operand
3332 && existing.face_id == pcurve.face_id
3333 })
3334 {
3335 let key = FaceKey { operand: pcurve.operand, face_id: pcurve.face_id };
3336 if emitted_pcurves.remove(&(absorbed.id, key)) {
3337 emitted_pcurves.insert((keeper.id, key));
3338 }
3339 keeper.pcurves.push(pcurve);
3340 }
3341 }
3342 let keeper_id = keeper.id;
3343 for record in &mut result.by_face {
3344 if let Some(position) =
3345 record.piece_ids.iter().position(|&id| id == absorbed.id)
3346 {
3347 if record.piece_ids.contains(&keeper_id) {
3348 record.piece_ids.remove(position);
3349 } else {
3350 record.piece_ids[position] = keeper_id;
3351 }
3352 }
3353 }
3354 } else {
3355 other += 1;
3356 }
3357 }
3358 index += 1;
3359 }
3360 if !removed.is_empty() && std::env::var("BREP_DEBUG_BOOL").is_ok() {
3361 eprintln!("coincident-piece merge: absorbed {:?}", removed);
3362 }
3363 }
3364 // Rescue near-tangent SSI truncations BEFORE canonicalization so the added
3365 // bridge pieces' endpoints (existing crossing/stub vertices) fold into the
3366 // same junction merges as every other section.
3367 lap("coincident_merge", &mut tail_lap, &mut tail_fits, &mut tail_ms);
3368 extend_truncated_sections(
3369 &mut result,
3370 &face_edge_lists,
3371 solid_a,
3372 solid_b,
3373 &charts,
3374 options.tolerance,
3375 )?;
3376 lap("extend_truncated", &mut tail_lap, &mut tail_fits, &mut tail_ms);
3377 canonicalize_imprint_junctions(
3378 &mut result,
3379 solid_a,
3380 solid_b,
3381 &charts,
3382 options.tolerance,
3383 &emitted_pcurves,
3384 &junction_evidence,
3385 junction_combine_mode(options),
3386 &tail_feature_scale,
3387 // The composite one-call carrier imprint keeps the per-pair contract:
3388 // no residual-evidence vertex merge across its admitted pairs (see
3389 // the parameter doc on `canonicalize_imprint_junctions`).
3390 self_pairs.is_none(),
3391 )?;
3392 lap("canonicalize", &mut tail_lap, &mut tail_fits, &mut tail_ms);
3393 // ONE RIM, ONE EDGE: with vertex identity final, dissolve the section
3394 // vertices that no operand edge passes through — the marcher's own
3395 // parameterization origin, which `process_curve` cannot tell from a
3396 // junction because it sees one pair at a time. See the method doc.
3397 let dissolved = dissolve_section_origin_vertices(
3398 &mut result,
3399 &marched_pieces,
3400 solid_a,
3401 solid_b,
3402 &charts,
3403 options.tolerance,
3404 )?;
3405 if dissolved > 0 && std::env::var("BREP_DEBUG_BOOL").is_ok() {
3406 eprintln!("origin-dissolve: {dissolved} parameterization vertex/vertices removed");
3407 }
3408 lap("origin_dissolve", &mut tail_lap, &mut tail_fits, &mut tail_ms);
3409 // B2: reuse an existing boundary edge as the shared section edge wherever a
3410 // section coincides with one along its whole span (vertices are final after
3411 // canonicalization; `face_edge_lists` holds each face's boundary edges on
3412 // the healed operands). Runs here so both operands reference ONE edge.
3413 reuse_boundary_section_edges(
3414 &mut result,
3415 &face_edge_lists,
3416 solid_a,
3417 solid_b,
3418 options.tolerance,
3419 )?;
3420 // Capstone step 1 — instrumentation only, zero behavior change: report
3421 // every (section piece × boundary edge) contact where the piece runs
3422 // within the scale-derived band of the edge over a real span. Measuring
3423 // the bands here first validates the graze-contact model on the
3424 // acceptance suite before step 2's common-block machinery replaces a
3425 // grazed overlap with a shared edge.
3426 lap("reuse_boundary", &mut tail_lap, &mut tail_fits, &mut tail_ms);
3427 report_graze_contacts(&result, &face_edge_lists, solid_a, solid_b, options.tolerance)?;
3428 if profile.enabled {
3429 if let Some(scope) = tail_fits.take() {
3430 scope.close();
3431 }
3432 let line = tail_ms
3433 .iter()
3434 .map(|(name, ms, _)| format!("{name}={ms:.2}"))
3435 .collect::<Vec<_>>()
3436 .join(" ");
3437 profile_line!("imprint.stages {line}");
3438 let split = imprint_split_take();
3439 let ms = |d: Duration| d.as_secs_f64() * 1_000.0;
3440 profile_line!(
3441 "imprint.split pierce={:.3} pierce_edges={} overlap={:.3} overlap_edges={} crossing={:.3} crossing_edges={} recover={:.3} recover_pierces={} lies_whole={:.3} lies_whole_calls={} lies_whole_hits={} lies_span={:.3} lies_span_calls={} reuse_edges={}",
3442 ms(split.pierce), split.pierce_edges, ms(split.overlap), split.overlap_edges, ms(split.crossing), split.crossing_edges,
3443 ms(split.recover), split.recover_pierces, ms(split.lies_whole), split.lies_whole_calls, split.lies_whole_hits,
3444 ms(split.lies_span), split.lies_span_calls, split.reuse_edges,
3445 );
3446 let fits = tail_ms
3447 .iter()
3448 .map(|(name, _, fits)| format!("{name}={fits}"))
3449 .collect::<Vec<_>>()
3450 .join(" ");
3451 profile_line!("imprint.stage_fits {fits}");
3452 }
3453 if let Ok(path) = std::env::var("BREP_IMPRINT_SECTION_CENSUS") {
3454 use std::io::Write;
3455 // A private observation of the complete imprint, including section
3456 // curves, pcurves and junctions. Canonicalize only the set-valued
3457 // barrier list on a COPY; leave every returned record untouched.
3458 let mut observed = result.clone();
3459 observed.barrier_edges.sort_unstable();
3460 let bytes = serde_json::to_vec(&observed).expect("serialize imprint census");
3461 let digest = bytes.iter().fold(0xcbf29ce484222325_u64,
3462 |hash, byte| (hash ^ u64::from(*byte)).wrapping_mul(0x100000001b3));
3463 let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)
3464 .expect("open imprint section census");
3465 writeln!(file, "imprint.sections digest={digest:016x} bytes={} pieces={} vertices={}",
3466 bytes.len(), observed.pieces.len(), observed.vertices.len()).expect("append imprint section census");
3467 }
3468 Ok(result)
3469}
3470
3471/// Debug-only graze-contact survey (`BREP_DEBUG_GRAZE=1`): for each section
3472/// piece and each boundary edge of its support faces, sample the piece and
3473/// measure distance to the edge; report contacts whose in-band span exceeds
3474/// both the weld scale and 4× the minimum deviation (span-wise proximity, not
3475/// a point touch). `band_cap` reuses the residual-merge `sep_cap` ceiling —
3476/// measured, never grown. The output is the raw material for capstone step 2
3477/// (partial-span common-block): which contacts exist, their spans, and their
3478/// measured bands.
3479fn report_graze_contacts(
3480 result: &ImprintResultRecord,
3481 face_edge_lists: &HashMap<FaceKey, Vec<&EdgeRecord>>,
3482 solid_a: &BrepSolid,
3483 solid_b: &BrepSolid,
3484 tolerance: f64,
3485) -> Result<(), KernelRefusal> {
3486 if std::env::var("BREP_DEBUG_GRAZE").as_deref() != Ok("1") {
3487 return Ok(());
3488 }
3489 let raw_extent = raw_solid_extent(solid_a).max(raw_solid_extent(solid_b));
3490 let (_, band_cap) = residual_merge_bands(raw_extent, tolerance);
3491 const SAMPLES: usize = 17;
3492 for piece in &result.pieces {
3493 let [t0, t1] = [piece.t0, piece.t1];
3494 if !(t1 > t0) {
3495 continue;
3496 }
3497 for key in piece.support_faces {
3498 let Some(edges) = face_edge_lists.get(&key) else {
3499 continue;
3500 };
3501 for edge in edges {
3502 if edge.degenerate {
3503 continue;
3504 }
3505 let mut in_band = 0usize;
3506 let mut min_dev = f64::INFINITY;
3507 let mut max_dev_in_band = 0.0f64;
3508 let mut span = 0.0f64;
3509 let mut prev: Option<(bool, Vec3)> = None;
3510 for k in 0..SAMPLES {
3511 let t = t0 + (t1 - t0) * k as f64 / (SAMPLES - 1) as f64;
3512 let point = piece.curve.evaluate(t).or_refuse(KernelStage::Intersect, "evaluate")?;
3513 let deviation = project_point_to_curve(&edge.curve, point).or_refuse(KernelStage::Intersect, "project_point_to_curve")?.distance;
3514 min_dev = min_dev.min(deviation);
3515 let inside = deviation <= band_cap;
3516 if inside {
3517 in_band += 1;
3518 max_dev_in_band = max_dev_in_band.max(deviation);
3519 if let Some((true, prev_point)) = prev {
3520 span += point.sub(prev_point).length();
3521 }
3522 }
3523 prev = Some((inside, point));
3524 }
3525 // Span-wise contact: several consecutive samples in band and a
3526 // span that dwarfs the closest-approach (not a transversal
3527 // crossing, which dips in and out at one sample).
3528 if in_band >= 3 && span > (4.0 * min_dev).max(assembler_weld(tolerance)) {
3529 eprintln!(
3530 "graze: piece {} sup=[{}:{},{}:{}] ~ edge {}:{} span={:.3e} band=[{:.3e},{:.3e}] samples_in_band={}/{}",
3531 piece.id,
3532 piece.support_faces[0].operand,
3533 piece.support_faces[0].face_id,
3534 piece.support_faces[1].operand,
3535 piece.support_faces[1].face_id,
3536 key.operand,
3537 edge.id,
3538 span,
3539 min_dev,
3540 max_dev_in_band,
3541 in_band,
3542 SAMPLES
3543 );
3544 }
3545 }
3546 }
3547 }
3548 Ok(())
3549}
3550
3551/// Does `edge` CROSS `carrier` at parameter `t` — leave one side of the
3552/// surface for the other — rather than touch it or lie on it? Read from the
3553/// signed distance of the edge to the carrier a step either side of `t`
3554/// (one hundredth of the edge's live window): a crossing changes sign with
3555/// both readings clear of the tolerance; a lies-on edge reads zero on both
3556/// sides and a kiss reads one sign on both. A hit at the edge's end has no
3557/// far side and is not a crossing, UNLESS the face's boundary continues the
3558/// same curve there: `siblings` are the face's edges, and one that carries
3559/// this edge's curve bit for bit over the adjoining parameter window is the
3560/// other half of an edge a vertex was put on (`apply_edge_splits` keeps the
3561/// parent curve and narrows the window). The live window is then the whole
3562/// chain's, so the step and both readings are the ones the unsplit edge
3563/// gave. Fixture 31's union on its own split operands: pad edge 2 pierces
3564/// the model's blend at 0.0525 rad inside both trims, the imprint had put a
3565/// vertex at that very pierce, both halves then read "at the end", the
3566/// sub-gate march never started and the section it imprints on the unsplit
3567/// operands was missing (4 open edges). The signed distance is read against the
3568/// carrier itself (projection), never against the tangent plane at the hit,
3569/// where a curve lying ON a curved carrier would read a same-signed sagitta
3570/// on both sides that outweighs a grazing crossing's first-order departure.
3571/// A CLOSED edge (a cap's rim circle) has no end: a hit within a step of its
3572/// seam reads its far side across the seam (the G1 elbow's arm cap pierces
3573/// the lower torus half at t = 0.0037 of its rim circle).
3574fn edge_crosses_carrier(
3575 edge: &EdgeRecord,
3576 siblings: &[&EdgeRecord],
3577 t: f64,
3578 carrier: &NurbsSurface,
3579 tolerance: f64,
3580) -> Result<bool, KernelRefusal> {
3581 if !(edge.t1 - edge.t0 > 0.0) {
3582 return Ok(false);
3583 }
3584 let (t0, t1) = same_curve_chain_window(edge, siblings);
3585 let window = t1 - t0;
3586 let delta = window * 1e-2;
3587 let (mut before, mut after) = (t - delta, t + delta);
3588 if before < t0 || after > t1 {
3589 let start = edge.curve.evaluate(t0).or_refuse(KernelStage::Intersect, "evaluate")?;
3590 let end = edge.curve.evaluate(t1).or_refuse(KernelStage::Intersect, "evaluate")?;
3591 if start.sub(end).length() > tolerance {
3592 return Ok(false);
3593 }
3594 if before < t0 {
3595 before += window;
3596 }
3597 if after > t1 {
3598 after -= window;
3599 }
3600 }
3601 let mut readings = [0.0f64; 2];
3602 for (slot, parameter) in [before, after].into_iter().enumerate() {
3603 let point = edge.curve.evaluate(parameter).or_refuse(KernelStage::Intersect, "evaluate")?;
3604 let projection = project_point_to_surface(carrier, point).or_refuse(KernelStage::Intersect, "project_point_to_surface")?;
3605 let normal = match carrier.normal(projection.u, projection.v) {
3606 Ok(normal) => normal,
3607 Err(_) => return Ok(false),
3608 };
3609 readings[slot] = point.sub(projection.point).dot(normal);
3610 }
3611 let clear = tolerance * 10.0;
3612 Ok(readings[0] * readings[1] < 0.0 && readings[0].abs() > clear && readings[1].abs() > clear)
3613}
3614
3615/// The parameter window of the chain of `siblings` that continue `edge`'s own
3616/// curve: edges with a bit-identical curve whose windows adjoin end to start.
3617/// `edge`'s own window when nothing continues it.
3618fn same_curve_chain_window(edge: &EdgeRecord, siblings: &[&EdgeRecord]) -> (f64, f64) {
3619 let same_curve = |other: &EdgeRecord| other.id != edge.id && !other.degenerate && same_curve_bits(&other.curve, &edge.curve);
3620 let halves: Vec<&&EdgeRecord> = siblings.iter().filter(|other| same_curve(other)).collect();
3621 let (mut t0, mut t1) = (edge.t0, edge.t1);
3622 for _ in 0..halves.len() {
3623 let mut grew = false;
3624 for other in &halves {
3625 if other.t1 == t0 && other.t0 < t0 {
3626 t0 = other.t0;
3627 grew = true;
3628 } else if other.t0 == t1 && other.t1 > t1 {
3629 t1 = other.t1;
3630 grew = true;
3631 }
3632 }
3633 if !grew {
3634 break;
3635 }
3636 }
3637 (t0, t1)
3638}
3639
3640/// `BREP_IMPRINT_LIES_ON_SEED_SKIP=0` runs the seed solve on an edge that lies
3641/// on the other carrier as before (see the seed loop); unset or anything else
3642/// skips it. Read once.
3643fn lies_on_seed_skip_enabled() -> bool {
3644 switch!("BREP_IMPRINT_LIES_ON_SEED_SKIP", true)
3645}
3646
3647/// TANGENT-RIM LANDING (hosted report 2026-10-17, "boolean fails": a box
3648/// with a bore, its top face revolved 25° about a top edge and subtracted).
3649///
3650/// The revolve turns the profile's hole into a torus whose start rim IS the
3651/// bore's top rim, so the bore cylinder and the torus are tangent along that
3652/// whole circle — a shared TRIM EDGE of both faces, not an interior contact —
3653/// and inside the trims they cross along two transverse branches that leave
3654/// the rim at the two points where the torus's revolution-direction curvature
3655/// vanishes against the cylinder's. The classifier admits those as isolated
3656/// nodes (`tangent_contact.rs`: a third-order cusp reads as a saddle at every
3657/// stopping rule) and the branch is marched — but the marcher's step direction
3658/// `n_a × n_b` collapses as it nears the node, so the trace STALLS a few 1e-5
3659/// short of the rim instead of running past the trim with its usual slack.
3660/// Measured: the run ends 6.95e-5 and 1.17e-4 below y = 20, which on the
3661/// cylinder's 30-tall chart is 2.3e-6 and 3.9e-6 — just over the arrangement's
3662/// 1.4e-6 band — so the cut never reaches the boundary, the cylinder face is
3663/// not split, and six edges stand one-use (the torus faces, whose pcurve is
3664/// clamped at the rim's domain wall, DID split there, which is the asymmetry
3665/// the one-use count reads). The exact `A − B` is a manifold solid (two
3666/// slivers of box material inside the torus tube pinching to a zero-dihedral
3667/// edge along the rim), so the refusal was wrong, not the model.
3668///
3669/// A transverse section cannot end strictly inside both trims; a run end that
3670/// stops within the on-boundary clearance (`polish_endpoint`'s band) of a trim
3671/// edge which lies on BOTH carriers at the foot of that stop has merely been
3672/// truncated, and the foot — on the edge, hence on both carriers exactly — is
3673/// where the exact section continues to. Land the end there and split the
3674/// edge at it, so the ordinary machinery (pcurve projection, the arrangement's
3675/// boundary split, the assembler's vertex weld) takes the junction from here.
3676/// Reach: pairs carrying an admitted tangent node (the only place the march
3677/// stalls short — elsewhere it overshoots the trim), a foot within
3678/// `1e-2·(1 + |foot|)` of that node, and an end not already on the edge. The
3679/// moved sample is the run's own last station, so the section polyline keeps
3680/// every marched point and the landing segment is at most the clearance long.
3681/// Returns the splits to register, one per edge sharing the landing point:
3682/// `(operand, edge id, parameter, from, to)`.
3683///
3684/// Without an admitted node (`node` is `None`: the pair was marched from
3685/// sub-gate pierces, or its branch simply ran into a tangency — the −10°
3686/// sibling of the revolve-into-bore report, a near-tangent pair with no node
3687/// admitted whose ordinary march stalled 4e-5 and 2.4e-4 short of its rim),
3688/// the certificate is the run end itself: it must sit where the carriers are
3689/// TANGENT (`|n_a × n_b|` under the seed gate), which is the only place a
3690/// march stalls short. With or without a node the reach is `1e-2 (1 +
3691/// |point|)`, and a gap beyond the plain clearance is earned sample by
3692/// sample: the straight landing segment must stay within the on-boundary
3693/// clearance of BOTH carriers at every sample — a section through a tangency
3694/// band is within tolerance of both carriers all the way to the rim, so a
3695/// segment that leaves either is not the missing stretch (the −20° sibling's
3696/// 7x6 end stops 3e-4 short, over the plain clearance of 2.1e-4). A gap
3697/// within the plain clearance lands exactly as before.
3698fn land_run_ends_on_tangent_rim(
3699 run: &mut [Vec3],
3700 node: Option<Vec3>,
3701 first: TaggedFace<'_>,
3702 second: TaggedFace<'_>,
3703 face_edge_lists: &HashMap<FaceKey, Vec<&EdgeRecord>>,
3704 tolerance: f64,
3705) -> Result<Vec<(u8, u64, f64, Vec3, Vec3)>, KernelRefusal> {
3706 let mut landings = Vec::new();
3707 if run.len() < 2 || std::env::var("BREP_TANGENT_RIM_LANDING").as_deref() == Ok("0") {
3708 return Ok(landings);
3709 }
3710 for index in [0, run.len() - 1] {
3711 let point = run[index];
3712 let clearance = (tolerance * 100.0).max(1e-6) * (1.0 + point.length());
3713 // No node: the end must stand in the tangency band, and the reach
3714 // beyond the plain clearance is earned sample by sample below.
3715 let stalled = match node {
3716 Some(_) => true,
3717 None => pair_normals_parallel_at(first, second, point)?,
3718 };
3719 if !stalled {
3720 continue;
3721 }
3722 let reach = 1e-2 * (1.0 + point.length());
3723 // Every qualifying foot over both faces' trim edges; the nearest is
3724 // where the end lands, and every edge whose foot coincides with it
3725 // (the two operands' copies of the shared rim) is split there, so
3726 // neither side depends on its pcurve happening to clamp at a domain
3727 // wall for its vertex.
3728 let mut feet: Vec<(f64, u8, u64, f64, Vec3)> = Vec::new();
3729 for face in [first, second] {
3730 let Some(edges) = face_edge_lists.get(&face.key()) else {
3731 continue;
3732 };
3733 for edge in edges {
3734 if edge.degenerate {
3735 continue;
3736 }
3737 let projection = project_point_to_curve(&edge.curve, point)
3738 .or_refuse(KernelStage::Intersect, "project_point_to_curve")?;
3739 let parameter = projection.u.clamp(edge.t0.min(edge.t1), edge.t0.max(edge.t1));
3740 let foot = edge.curve.evaluate(parameter).or_refuse(KernelStage::Intersect, "evaluate")?;
3741 let gap = foot.sub(point).length();
3742 if gap <= tolerance || gap > reach {
3743 continue;
3744 }
3745 if let Some(node) = node {
3746 if foot.sub(node).length() > 1e-2 * (1.0 + foot.length()) {
3747 continue;
3748 }
3749 }
3750 if gap > clearance
3751 && !landing_segment_within(&first.face.surface, &second.face.surface, point, foot, clearance)
3752 {
3753 continue;
3754 }
3755 // The edge must ride BOTH carriers at the foot: that is what
3756 // makes the foot a point of the section.
3757 let on_both = [first, second].iter().all(|face| {
3758 project_point_to_surface(&face.face.surface, foot)
3759 .map(|projection| projection.distance <= clearance)
3760 .unwrap_or(false)
3761 });
3762 if !on_both {
3763 continue;
3764 }
3765 feet.push((gap, face.operand, edge.id, parameter, foot));
3766 }
3767 }
3768 let Some(&(_, _, _, _, landing)) = feet.iter().min_by(|a, b| a.0.total_cmp(&b.0)) else {
3769 continue;
3770 };
3771 for (_, operand, edge_id, parameter, foot) in feet {
3772 if foot.sub(landing).length() <= tolerance * 10.0 {
3773 landings.push((operand, edge_id, parameter, point, landing));
3774 }
3775 }
3776 run[index] = landing;
3777 }
3778 Ok(landings)
3779}
3780/// TANGENT-NODE LANDING (hosted report 2026-11-01, "Boolean fails": a NURBS
3781/// Solid BOX cage, 10³ with 8 segments and 3 rings, unioned with a radius-5
3782/// cylinder standing on `y = 0`).
3783///
3784/// Three collinear cage points per side make the body flat to second order at
3785/// each side midpoint, and the cylinder's bottom rim passes through all four:
3786/// the two carriers touch at four isolated higher-order NODES that lie ON a
3787/// trim edge. Two section branches leave each node, mutually tangent there
3788/// (`z = ±c·y^{3/2}` on the wall). The march cannot place a station near such
3789/// a node: across the contact the carriers separate only as `δ² κ / 2`, so at
3790/// the model tolerance a station is uncertain by `√(2·tol/κ)` along the rim
3791/// (`1e-3` here). Measured: the four arcs ended ON the rim but 1.4e-5..2e-4
3792/// along it from their nodes, so each node's two arrivals split the rim twice
3793/// and left `On` slivers between, and the arcs into the node on the cylinder's
3794/// seam wobbled ACROSS the seam at `y ≈ 1.3e-3` (the true branch touches the
3795/// seam only at the node), leaving seam-hugging slivers. Six open corners and
3796/// a torn shell; [`land_run_ends_on_tangent_rim`] does not reach it because
3797/// those ends are already on the edge — they stopped at the wrong place ALONG
3798/// it, not short of it.
3799///
3800/// So: a run end where the carriers are tangent is refined onto its contact
3801/// (`classify_tangent_contact`); when that contact is an isolated node within
3802/// the landing reach that lies on a trim edge of either face, the stations
3803/// inside the node's ambiguity radius `3·√(2·tol/κ)` are dropped — they carry
3804/// no position the march could resolve — and the end is the node itself,
3805/// provided the closing chord stays on both carriers
3806/// ([`landing_segment_within`]). Every edge through the node is split exactly
3807/// there, so both branches arriving at a node share one vertex. Returns the
3808/// splits as [`land_run_ends_on_tangent_rim`] does.
3809fn land_run_ends_on_tangent_node(
3810 run: &mut Vec<Vec3>,
3811 first: TaggedFace<'_>,
3812 second: TaggedFace<'_>,
3813 face_edge_lists: &HashMap<FaceKey, Vec<&EdgeRecord>>,
3814 tolerance: f64,
3815) -> Result<Vec<(u8, u64, f64, Vec3, Vec3)>, KernelRefusal> {
3816 let mut landings = Vec::new();
3817 if run.len() < 2 || std::env::var("BREP_TANGENT_NODE_LANDING").as_deref() == Ok("0") {
3818 return Ok(landings);
3819 }
3820 for at_end in [false, true] {
3821 let end = if at_end { run[run.len() - 1] } else { run[0] };
3822 if !pair_normals_parallel_at(first, second, end)? {
3823 continue;
3824 }
3825 let reach = 1e-2 * (1.0 + end.length());
3826 let Some(contact) =
3827 classify_tangent_contact(&first.face.surface, &second.face.surface, end, tolerance)?
3828 else {
3829 continue;
3830 };
3831 let node = contact.point;
3832 if !contact.is_isolated_node() || node.sub(end).length() > reach || contact.curvature_scale <= 0.0 {
3833 continue;
3834 }
3835 let clearance = (tolerance * 100.0).max(1e-6) * (1.0 + node.length());
3836 // Every trim edge of either face the node lies on.
3837 let mut splits: Vec<(u8, u64, f64)> = Vec::new();
3838 for face in [first, second] {
3839 let Some(edges) = face_edge_lists.get(&face.key()) else {
3840 continue;
3841 };
3842 for edge in edges {
3843 if edge.degenerate {
3844 continue;
3845 }
3846 let projection = project_point_to_curve(&edge.curve, node)
3847 .or_refuse(KernelStage::Intersect, "project_point_to_curve")?;
3848 let parameter = projection.u.clamp(edge.t0.min(edge.t1), edge.t0.max(edge.t1));
3849 let foot = edge.curve.evaluate(parameter).or_refuse(KernelStage::Intersect, "evaluate")?;
3850 if foot.sub(node).length() <= tolerance * 10.0
3851 && !splits.iter().any(|&(operand, id, _)| operand == face.operand && id == edge.id)
3852 {
3853 splits.push((face.operand, edge.id, parameter));
3854 }
3855 }
3856 }
3857 if splits.is_empty() {
3858 continue;
3859 }
3860 // The ambiguity radius: the stations inside it are noise along the
3861 // contact and are dropped outright. Beyond it, keep dropping while
3862 // the closing chord to the node still rides both carriers to ten
3863 // times the tolerance: near the node the branch is TANGENT to the
3864 // contact direction (on the seam node, to the seam itself), so its
3865 // true offset from that direction grows only as `r^{3/2}` and a
3866 // station must be far enough out for that offset to dominate the
3867 // corrector's noise — or the fitted piece wobbles across the seam.
3868 let ambiguity = 3.0 * (2.0 * tolerance / contact.curvature_scale).sqrt();
3869 let tight = tolerance * 10.0;
3870 let mut kept: Vec<Vec3> = if at_end { run.clone() } else { run.iter().rev().copied().collect() };
3871 while kept.len() > 1 && kept[kept.len() - 1].sub(node).length() <= ambiguity {
3872 kept.pop();
3873 }
3874 let last = kept[kept.len() - 1];
3875 if last.sub(node).length() > reach
3876 || !landing_segment_within(&first.face.surface, &second.face.surface, last, node, clearance)
3877 {
3878 continue;
3879 }
3880 while kept.len() > 2 {
3881 let further = kept[kept.len() - 2];
3882 if further.sub(node).length() > reach
3883 || !landing_segment_within(&first.face.surface, &second.face.surface, further, node, tight)
3884 {
3885 break;
3886 }
3887 kept.pop();
3888 }
3889 kept.push(node);
3890 if !at_end {
3891 kept.reverse();
3892 }
3893 *run = kept;
3894 for (operand, edge_id, parameter) in splits {
3895 landings.push((operand, edge_id, parameter, end, node));
3896 }
3897 }
3898 Ok(landings)
3899}
3900
3901/// The widened landing's certificate: is the straight segment from the
3902/// stalled run end `from` to the foot `to` within `clearance` of BOTH
3903/// carriers at every interior sample?
3904///
3905/// Why this is a construction guarantee and not a reading: the foot is on a
3906/// trim edge that rides both carriers (checked by the caller), so it is a
3907/// point of the carriers' intersection set to within the clearance. The run
3908/// end is a marched station, on both carriers to the corrector's residual.
3909/// A segment whose every sample is within the clearance of both carriers is
3910/// a chord of the band where the two carriers are within tolerance of each
3911/// other — exactly the set a section through a tangency is confined to —
3912/// and the section polyline's last chord is held to the same deviation every
3913/// marched chord is. A segment that leaves either carrier mid-way (a chord
3914/// across a curved rim: its sagitta clears the clearance by orders) is NOT
3915/// the missing stretch, whatever its ends read, and is refused here.
3916fn landing_segment_within(
3917 first: &NurbsSurface,
3918 second: &NurbsSurface,
3919 from: Vec3,
3920 to: Vec3,
3921 clearance: f64,
3922) -> bool {
3923 const SAMPLES: usize = 8;
3924 (1..SAMPLES).all(|k| {
3925 let sample = from.add(to.sub(from).scale(k as f64 / SAMPLES as f64));
3926 [first, second].iter().all(|surface| {
3927 project_point_to_surface(surface, sample)
3928 .map(|projection| projection.distance <= clearance)
3929 .unwrap_or(false)
3930 })
3931 })
3932}
3933
3934/// Are the two carriers TANGENT (normals parallel) where they both pass through
3935/// `point`?
3936///
3937/// The test the tangential-only refusal above needs: a point on the marched
3938/// intersection is a TANGENT NODE when the two surface normals there are
3939/// parallel. The threshold is the transversality bound the supplemental
3940/// detector already accepts seeds by (`TRANSVERSE_SEED_CROSS`), not the far
3941/// tighter pair-classifier bound — a node the march merely passes CLOSE to
3942/// still poisons the assembly, so this errs toward calling a pair singular.
3943/// The tangent-curve lane's recognition of one pair: find the pair's
3944/// tangential contact without a witness, keep only an EXTENDED one (a node or
3945/// a point contact is the existing paths' business), and with one planar
3946/// carrier march it along the null direction of `II_a − II_b` and clip the
3947/// curve to both trims. `None` when there is no extended contact, or its trace
3948/// has no run inside both trims (a contact on the carriers' extensions
3949/// imprints nothing, exactly as today).
3950///
3951/// The section is RECORDED, not imprinted: a tangent edge whose two sides
3952/// both survive is a four-coedge incidence the assembler cannot represent
3953/// (the pinch gate in `csg/boolean/mod.rs` names the subtract case), and a
3954/// tangent edge only one side of which survives dissolves into its face.
3955/// What the record buys is the boolean's attribution: a refusal whose faces
3956/// the contact involves names the contact instead of the arrangement's own
3957/// symptom. The involved faces are the two carriers' and every face sharing
3958/// an edge a run end lies on (`TangentSectionRecord::faces`).
3959#[allow(clippy::too_many_arguments)]
3960fn tangent_curve_section(
3961 first: TaggedFace<'_>,
3962 second: TaggedFace<'_>,
3963 options: &ImprintOptions,
3964 first_faces: &[TaggedFace<'_>],
3965 second_faces: &[TaggedFace<'_>],
3966 face_edge_lists: &HashMap<FaceKey, Vec<&EdgeRecord>>,
3967 debug_pairs: bool,
3968) -> Result<Option<(TangentSectionRecord, Vec<Vec3>)>, KernelRefusal> {
3969 let _clock = ImprintSubphaseClock::new("tangent_section");
3970 let Some(contact) =
3971 crate::find_tangent_contact(&first.face.surface, &second.face.surface, options.tolerance)
3972 .or_refuse(KernelStage::Intersect, "find_tangent_contact")?
3973 else {
3974 return Ok(None);
3975 };
3976 if debug_pairs {
3977 eprintln!(
3978 "pair {}x{}: tangent-curve lane contact {:?}",
3979 first.face.id, second.face.id, contact
3980 );
3981 }
3982 if !contact.is_extended() {
3983 return Ok(None);
3984 }
3985 let is_plane = |face: TaggedFace<'_>| {
3986 matches!(face.face.surface.analytic(), Some(crate::AnalyticSurface::Plane { .. }))
3987 };
3988 let planar = is_plane(first) || is_plane(second);
3989 if !planar {
3990 // The curved/curved branch: recognised and named, not traced.
3991 return Ok(Some((
3992 TangentSectionRecord {
3993 first: first.key(),
3994 second: second.key(),
3995 start: contact.point,
3996 end: contact.point,
3997 length: 0.0,
3998 closed: false,
3999 stations: 0,
4000 planar: false,
4001 faces: vec![first.key(), second.key()],
4002 piece_ids: Vec::new(),
4003 grazing: Vec::new(),
4004 },
4005 Vec::new(),
4006 )));
4007 }
4008 let Some(curve) = crate::trace_tangent_curve(
4009 &first.face.surface,
4010 &second.face.surface,
4011 &contact,
4012 &SurfaceIntersectionOptions {
4013 tolerance: options.tolerance,
4014 maximum_step: options.maximum_ssi_step,
4015 ..Default::default()
4016 },
4017 )
4018 .or_refuse(KernelStage::Intersect, "trace_tangent_curve")?
4019 else {
4020 return Ok(None);
4021 };
4022 let mut longest: Option<Vec<Vec3>> = None;
4023 let mut longest_length = 0.0f64;
4024 for run in clip_branch_to_trims(&curve.points, first, second)? {
4025 if run.len() < 2 {
4026 continue;
4027 }
4028 let length: f64 = run.windows(2).map(|pair| pair[1].sub(pair[0]).length()).sum();
4029 if length > longest_length {
4030 longest_length = length;
4031 longest = Some(run);
4032 }
4033 }
4034 let Some(run) = longest else {
4035 return Ok(None);
4036 };
4037 if longest_length <= options.tolerance * 100.0 {
4038 return Ok(None);
4039 }
4040 if debug_pairs {
4041 eprintln!(
4042 "pair {}x{}: tangent-curve lane traced {} stations, run of {} inside both trims, length {:.6}, closed {}",
4043 first.face.id, second.face.id, curve.points.len(), run.len(), longest_length, curve.closed
4044 );
4045 }
4046 let closed = curve.closed && run.len() == curve.points.len();
4047 // The faces the contact involves: the carriers' own, and across every
4048 // edge of theirs that a run end lies on, the faces sharing that edge.
4049 let mut faces = vec![first.key(), second.key()];
4050 if !closed {
4051 let band = options.tolerance * 100.0;
4052 for end in [run[0], run[run.len() - 1]] {
4053 for face in [first, second] {
4054 let Some(edges) = face_edge_lists.get(&face.key()) else {
4055 continue;
4056 };
4057 for edge in edges {
4058 if edge.degenerate {
4059 continue;
4060 }
4061 let Ok(projection) = project_point_to_curve(&edge.curve, end) else {
4062 continue;
4063 };
4064 if projection.distance > band {
4065 continue;
4066 }
4067 let siblings = if face.operand == first.operand { first_faces } else { second_faces };
4068 for sibling in siblings {
4069 let shares = sibling
4070 .face
4071 .loops
4072 .iter()
4073 .flat_map(|loop_record| &loop_record.coedges)
4074 .any(|coedge| coedge.edge_id == edge.id);
4075 if shares && !faces.contains(&sibling.key()) {
4076 faces.push(sibling.key());
4077 }
4078 }
4079 }
4080 }
4081 }
4082 }
4083 if debug_pairs {
4084 eprintln!(
4085 "pair {}x{}: tangent-curve lane involves faces {:?}",
4086 first.face.id, second.face.id, faces
4087 );
4088 }
4089 Ok(Some((
4090 TangentSectionRecord {
4091 first: first.key(),
4092 second: second.key(),
4093 start: run[0],
4094 end: run[run.len() - 1],
4095 length: longest_length,
4096 closed,
4097 stations: run.len(),
4098 planar: true,
4099 faces,
4100 piece_ids: Vec::new(),
4101 grazing: Vec::new(),
4102 },
4103 run,
4104 )))
4105}
4106
4107/// Imprint a traced tangent run ([`ImprintOptions::tangent_curve_imprint`]):
4108/// the run's stations become a section curve — the exact line through its
4109/// ends when they are collinear (a plane's contact with a cylinder or cone is
4110/// a generatrix), the marched lane's polyline fit otherwise (a torus parallel)
4111/// — and `process_curve` mints it on both carriers exactly as it mints a
4112/// transverse section: pieces with pcurves on each face, the operand edges the
4113/// ends lie on split there. Returns the ids of the pieces minted, in order.
4114///
4115/// Only a run that SPLITS both faces is minted: a closed run, or one each of
4116/// whose ends lies, on each carrier, on that face's trim or on a section
4117/// piece already minted on it (the through-bore's contact ends on the wall's
4118/// top and bottom edges and, on the bore, on the circles the box's top and
4119/// bottom cut there). A run with an end inside a face — the blind pocket's
4120/// contact ends on the bore's cap rings but inside the wall, where both of
4121/// its sides are one face — would be a slit, and a slit is not a boundary:
4122/// measured with the slit minted, the wall's four edges came out one-use and
4123/// the pocket refused `NonIntegralGenus`. Such a contact stays recorded only,
4124/// as before.
4125#[allow(clippy::too_many_arguments)]
4126fn imprint_tangent_run(
4127 builder: &mut ImprintBuilder<'_>,
4128 run: &[Vec3],
4129 closed: bool,
4130 first: TaggedFace<'_>,
4131 second: TaggedFace<'_>,
4132 face_edge_lists: &HashMap<FaceKey, Vec<&EdgeRecord>>,
4133 options: &ImprintOptions,
4134 debug: bool,
4135) -> Result<Vec<u64>, KernelRefusal> {
4136 if run.len() < 2 {
4137 return Ok(Vec::new());
4138 }
4139 if !closed {
4140 let band = options.tolerance * 100.0;
4141 let within = |curve: &NurbsCurve, t0: f64, t1: f64, point: Vec3| -> bool {
4142 project_point_to_curve(curve, point)
4143 .map(|projection| {
4144 projection.distance <= band
4145 && projection.u >= t0.min(t1) - 1e-9
4146 && projection.u <= t0.max(t1) + 1e-9
4147 })
4148 .unwrap_or(false)
4149 };
4150 let on_boundary = |face: TaggedFace<'_>, point: Vec3| -> bool {
4151 let on_trim = face_edge_lists
4152 .get(&face.key())
4153 .map(|edges| {
4154 edges
4155 .iter()
4156 .any(|edge| !edge.degenerate && within(&edge.curve, edge.t0, edge.t1, point))
4157 })
4158 .unwrap_or(false);
4159 on_trim
4160 || builder
4161 .pieces
4162 .iter()
4163 .any(|piece| piece.support_faces.contains(&face.key()) && within(&piece.curve, piece.t0, piece.t1, point))
4164 };
4165 let ends = [run[0], run[run.len() - 1]];
4166 for face in [first, second] {
4167 if !ends.iter().all(|end| on_boundary(face, *end)) {
4168 if debug {
4169 eprintln!(
4170 "pair {}x{}: tangent-curve lane run not imprinted: an end lies inside face {}:{}'s trim (a slit)",
4171 first.face.id, second.face.id, face.operand, face.face.id
4172 );
4173 }
4174 return Ok(Vec::new());
4175 }
4176 }
4177 }
4178 let start = run[0];
4179 let end = run[run.len() - 1];
4180 let chord = end.sub(start);
4181 let chord_length = chord.length();
4182 let fit_tolerance = options.tolerance.max(1e-7);
4183 let collinear = chord_length > fit_tolerance && {
4184 let direction = chord.scale(1.0 / chord_length);
4185 run.iter().all(|station| {
4186 let offset = station.sub(start);
4187 offset.sub(direction.scale(offset.dot(direction))).length() <= 10.0 * fit_tolerance
4188 })
4189 };
4190 let curve = if collinear {
4191 crate::make_line(start, end).or_refuse(KernelStage::Intersect, "make_line")?
4192 } else {
4193 fit_polyline_of_degree(
4194 run,
4195 fit_tolerance,
4196 run.len().min(MAX_FIT_STATIONS),
4197 options.local_fit,
4198 section_fit_degree(),
4199 )
4200 .or_refuse(KernelStage::Intersect, "fit_polyline_of_degree")?
4201 .curve
4202 };
4203 let before = builder.pieces.len();
4204 builder.process_curve(curve, first, second, &[first, second], &[first, second], true, None)?;
4205 let ids: Vec<u64> = builder.pieces[before..].iter().map(|piece| piece.id).collect();
4206 if debug {
4207 eprintln!(
4208 "pair {}x{}: tangent-curve lane imprinted the run as {} ({} piece(s) {:?})",
4209 first.face.id,
4210 second.face.id,
4211 if collinear { "a line" } else { "a fitted section" },
4212 ids.len(),
4213 ids
4214 );
4215 }
4216 Ok(ids)
4217}
4218
4219/// Split every piece an imprinted tangent section ENDS ON, at that end
4220/// (`ImprintOptions::tangent_curve_imprint`). A tangent run is clipped to both
4221/// trims, so its ends lie on operand edges — split by `process_curve` — or on
4222/// the trim of the OTHER carrier's neighbours, where they also lie on the
4223/// sections those neighbours minted on this carrier: the through-bore's
4224/// contact line ends on the circles the box's top and bottom cut on the bore,
4225/// at a point no edge pierce put a vertex on (the box edge grazes the bore
4226/// there, and a grazing pierce is deliberately not a junction). Without the
4227/// vertex, the bore's arrangement would split the circle where the line
4228/// meets it while the cap's arrangement keeps the circle whole — one closed
4229/// edge against two arcs, one-use at assembly. The piece is split here, at
4230/// the imprint, so every face it is minted on sees the vertex: the halves
4231/// take the tangent end's vertex id, the pcurves are split with the curve
4232/// where they were emitted on its knots and rebuilt on the face's chart
4233/// otherwise, and the piece's face lists and lane sets follow the halves. A
4234/// piece that resolves to an operand edge (`shared_edge`) is left as it is:
4235/// that edge's own split is the exchange pass's.
4236fn split_pieces_at_tangent_ends(
4237 builder: &mut ImprintBuilder<'_>,
4238 sections: &[TangentSectionRecord],
4239 first_faces: &[TaggedFace<'_>],
4240 second_faces: &[TaggedFace<'_>],
4241 options: &ImprintOptions,
4242 debug: bool,
4243) -> Result<(), KernelRefusal> {
4244 let tangent_ids: HashSet<u64> = sections.iter().flat_map(|section| section.piece_ids.iter().copied()).collect();
4245 if tangent_ids.is_empty() {
4246 return Ok(());
4247 }
4248 let overlap_limit = options.tolerance.max(1e-5);
4249 let vertex_points: HashMap<u64, Vec3> =
4250 builder.vertices.iter().map(|vertex| (vertex.id, vertex.point)).collect();
4251 // (end vertex, its point, the tangent piece's supports)
4252 let mut ends: Vec<(u64, Vec3, [FaceKey; 2])> = Vec::new();
4253 for piece in builder.pieces.iter().filter(|piece| tangent_ids.contains(&piece.id)) {
4254 for vertex_id in [piece.start_vertex_id, piece.end_vertex_id] {
4255 if let Some(point) = vertex_points.get(&vertex_id) {
4256 if !ends.iter().any(|(id, _, _)| *id == vertex_id) {
4257 ends.push((vertex_id, *point, piece.support_faces));
4258 }
4259 }
4260 }
4261 }
4262 let face_of = |key: FaceKey| {
4263 first_faces
4264 .iter()
4265 .chain(second_faces)
4266 .copied()
4267 .find(|face| face.key() == key)
4268 };
4269 let mut index = 0;
4270 while index < builder.pieces.len() {
4271 let piece = &builder.pieces[index];
4272 if tangent_ids.contains(&piece.id) || piece.shared_edge.is_some() || !(piece.t1 > piece.t0) {
4273 index += 1;
4274 continue;
4275 }
4276 let mut split: Option<(u64, f64, Vec3)> = None;
4277 for (vertex_id, point, supports) in &ends {
4278 if *vertex_id == piece.start_vertex_id || *vertex_id == piece.end_vertex_id {
4279 continue;
4280 }
4281 if !supports.iter().any(|support| piece.support_faces.contains(support)) {
4282 continue;
4283 }
4284 let projection = project_point_to_curve(&piece.curve, *point)
4285 .or_refuse(KernelStage::Intersect, "project_point_to_curve")?;
4286 if projection.distance > overlap_limit {
4287 continue;
4288 }
4289 let span = piece.t1 - piece.t0;
4290 let margin = (span * KNOT_IDENTITY_TOL).max(2e-9);
4291 if projection.u <= piece.t0 + margin || projection.u >= piece.t1 - margin {
4292 continue;
4293 }
4294 // Interior in PARAMETER but at an END in space: a closed piece's
4295 // origin seen from the far side of its domain.
4296 if projection.point.sub(vertex_points[&piece.start_vertex_id]).length() <= overlap_limit
4297 || projection.point.sub(vertex_points[&piece.end_vertex_id]).length() <= overlap_limit
4298 {
4299 continue;
4300 }
4301 split = Some((*vertex_id, projection.u, *point));
4302 break;
4303 }
4304 let Some((vertex_id, parameter, point)) = split else {
4305 index += 1;
4306 continue;
4307 };
4308 let piece = builder.pieces.remove(index);
4309 let (left, right) = piece.curve.split(parameter).or_refuse(KernelStage::Intersect, "split")?;
4310 let [left_t0, _] = left.domain().or_refuse(KernelStage::Intersect, "domain")?;
4311 let [_, right_t1] = right.domain().or_refuse(KernelStage::Intersect, "domain")?;
4312 let start_point = vertex_points[&piece.start_vertex_id];
4313 let end_point = vertex_points[&piece.end_vertex_id];
4314 let left = snap_curve_ends(&left, start_point, point)?;
4315 let right = snap_curve_ends(&right, point, end_point)?;
4316 let left_id = builder.next_id;
4317 let right_id = builder.next_id + 1;
4318 builder.next_id += 2;
4319 let mut left_pcurves = Vec::with_capacity(piece.pcurves.len());
4320 let mut right_pcurves = Vec::with_capacity(piece.pcurves.len());
4321 for face_pcurve in &piece.pcurves {
4322 let key = FaceKey { operand: face_pcurve.operand, face_id: face_pcurve.face_id };
4323 let emitted = builder.emitted_pcurves.remove(&(piece.id, key));
4324 let halves = if emitted {
4325 match face_pcurve.pcurve.split(parameter) {
4326 Ok((a, b)) => {
4327 builder.emitted_pcurves.insert((left_id, key));
4328 builder.emitted_pcurves.insert((right_id, key));
4329 Some((a, b))
4330 }
4331 Err(_) => None,
4332 }
4333 } else {
4334 None
4335 };
4336 let (a, b) = match halves {
4337 Some(halves) => halves,
4338 None => {
4339 let Some(face) = face_of(key) else {
4340 return Err(KernelRefusal::internal(
4341 KernelStage::Intersect,
4342 "imprint.tangent_split",
4343 format!("tangent-section split: piece {} has a pcurve on an unknown face {key:?}", piece.id),
4344 ));
4345 };
4346 (
4347 build_pcurve_on_surface_marched(face.chart(), &left)
4348 .or_refuse(KernelStage::Intersect, "build_pcurve_on_surface_marched")?,
4349 build_pcurve_on_surface_marched(face.chart(), &right)
4350 .or_refuse(KernelStage::Intersect, "build_pcurve_on_surface_marched")?,
4351 )
4352 }
4353 };
4354 left_pcurves.push(FacePcurve { operand: key.operand, face_id: key.face_id, pcurve: a });
4355 right_pcurves.push(FacePcurve { operand: key.operand, face_id: key.face_id, pcurve: b });
4356 }
4357 if builder.marched_pieces.remove(&piece.id) {
4358 builder.marched_pieces.insert(left_id);
4359 builder.marched_pieces.insert(right_id);
4360 }
4361 for ids in builder.by_face.values_mut() {
4362 if let Some(slot) = ids.iter().position(|id| *id == piece.id) {
4363 ids[slot] = left_id;
4364 ids.insert(slot + 1, right_id);
4365 }
4366 }
4367 if debug {
4368 eprintln!(
4369 "tangent-curve lane: piece {} split at vertex {} (t={parameter:.6}) into pieces {left_id} and {right_id}",
4370 piece.id, vertex_id
4371 );
4372 }
4373 builder.pieces.insert(
4374 index,
4375 ImprintPieceRecord {
4376 id: left_id,
4377 curve: left,
4378 t0: left_t0,
4379 t1: parameter,
4380 start_vertex_id: piece.start_vertex_id,
4381 end_vertex_id: vertex_id,
4382 pcurves: left_pcurves,
4383 support_faces: piece.support_faces,
4384 shared_edge: None,
4385 },
4386 );
4387 builder.pieces.insert(
4388 index + 1,
4389 ImprintPieceRecord {
4390 id: right_id,
4391 curve: right,
4392 t0: parameter,
4393 t1: right_t1,
4394 start_vertex_id: vertex_id,
4395 end_vertex_id: piece.end_vertex_id,
4396 pcurves: right_pcurves,
4397 support_faces: piece.support_faces,
4398 shared_edge: None,
4399 },
4400 );
4401 // Re-examine the halves: a run may end on the same piece twice.
4402 }
4403 Ok(())
4404}
4405
4406fn pair_normals_parallel_at(
4407 first: TaggedFace<'_>,
4408 second: TaggedFace<'_>,
4409 point: Vec3,
4410) -> Result<bool, KernelRefusal> {
4411 let mut normals = [Vec3::default(); 2];
4412 for (slot, face) in normals.iter_mut().zip([first, second]) {
4413 let projection = project_point_to_surface(&face.face.surface, point)
4414 .or_refuse(KernelStage::Intersect, "project_point_to_surface")?;
4415 let Ok(normal) = face.face.surface.normal(projection.u, projection.v) else {
4416 // A pole/singular parameter point cannot witness transversality;
4417 // treat it as tangential so the pair stays refused.
4418 return Ok(true);
4419 };
4420 *slot = normal;
4421 }
4422 Ok(normals[0].cross(normals[1]).length() <= crate::TRANSVERSE_SEED_CROSS)
4423}
4424