Skip to main content

brep_kernel/csg/fragment/
face_split.rs

1use crate::{KernelRefusal, KernelStage, OrRefuse};
2use super::*;
3use super::types::{Chain, ChainSource};
4use super::sampling::{enforce_boundary_crossing_parity, refine_near_hints, sag_refine_chain, sample_boundary_chain, sample_chain, trimmed_curve};
5use super::loops::{build_loop, edge_for, interior_points, pcurve_for, point_in_polygon, segment_tag, tag_chain, vertex_point};
6use super::loop_split::{fragments_agree, loop_split_census, loop_split_face, loop_split_mode, LoopSplitMode};
7/// `BREP_BOUNDARY_VERTEX_HINTS=0|off` restores boundary sampling that is blind
8/// to the face's other loops (see the BOUNDARY-VERTEX HINTS note in
9/// `fragment_face`).
10fn boundary_vertex_hints_enabled() -> bool {
11    switch!("BREP_BOUNDARY_VERTEX_HINTS", true)
12}
13
14/// Imprint id-index shared across every face of one operand. Built ONCE in
15/// `fragment_solid`; without it each face rebuilt `pieces_by_id`/`vertex_by_id`
16/// over ALL pieces (O(F*(P + P*V))) and linear-scanned `by_face`. Lookup-only,
17/// so every face gets the identical pieces/points the scans returned.
18/// `BREP_FRAGMENT_SMALL_RING` (default on; `0`/`off` restores the old
19/// thinning): a chain smaller than the arrangement's junction radius keeps its
20/// shape (see SMALL RING in `fragment_face_arranged`) instead of collapsing
21/// onto its ends.
22pub(crate) fn small_ring_enabled() -> bool {
23    switch!("BREP_FRAGMENT_SMALL_RING", true)
24}
25
26/// `BREP_BRIDGE_T_JUNCTION_ANCHOR` (default on; `=0` restores): a cut end lying
27/// on the run of another cut chain is a T-junction the arrangement nodes
28/// exactly, not half of a broken cut, so it is no bridge candidate. Without
29/// this, two independent T-ends close together on one chain read as a broken
30/// junction and are welded to their midpoint, or refused as a missing piece.
31fn bridge_t_junction_anchor_enabled() -> bool {
32    switch!("BREP_BRIDGE_T_JUNCTION_ANCHOR", true)
33}
34
35/// `BREP_BRIDGE_BOUNDARY_T_JUNCTION_ANCHOR` (default on; `=0` restores): a cut end lying on
36/// the run of one of the face's own BOUNDARY chains is a T-junction with the
37/// trim, which the arrangement nodes like any cut meeting the boundary, so it is
38/// no bridge candidate either. Cause (a reported box and pin model at 2.51 and
39/// 2.52): on the wall offset x = d, the bore and floor sections end on the
40/// opening edge 0.25 apart. Their other ends lie within the junction radius of
41/// the front offset's section (d - 2.5 = 0.01 from them), which reads them as
42/// anchored, so the two ends on the opening edge read as a broken cut and were
43/// welded to their midpoint, deleting the short bore arc there.
44fn bridge_boundary_t_junction_anchor_enabled() -> bool {
45    switch!("BREP_BRIDGE_BOUNDARY_T_JUNCTION_ANCHOR", true)
46}
47
48/// `BREP_BRIDGE_SKIP_CROSSING_PAIRS` (default on; `=0` restores): two loose cut ends whose
49/// chains cross each other are not a broken cut. The crossing is their
50/// junction, which the arrangement nodes, and its degree-1 pruning drops both
51/// overshoot stubs, so the pair is never bridged. Cause (a reported
52/// box and pin model at 2.54): on the floor offset, the wall and bore sections cross
53/// 0.061 from the wall section's end, inside the junction radius 0.064, so the
54/// overshoot exclusion did not see it; their ends lay 0.066 apart, just past
55/// that radius, so both read as loose and were welded to their midpoint, which
56/// left the loops open.
57fn bridge_skip_crossing_pairs_enabled() -> bool {
58    switch!("BREP_BRIDGE_SKIP_CROSSING_PAIRS", true)
59}
60
61pub(super) struct FragmentIndex<'a> {
62    pub(super) by_face: HashMap<(u8, u64), &'a [u64]>,
63    pub(super) pieces_by_id: HashMap<u64, &'a ImprintPieceRecord>,
64    pub(super) vertex_points: HashMap<u64, Vec3>,
65    pub(super) cosurface_pairs: HashSet<(FaceKey, FaceKey)>,
66}
67
68impl<'a> FragmentIndex<'a> {
69    pub(super) fn build(imprint: &'a ImprintResultRecord) -> Self {
70        let mut by_face: HashMap<(u8, u64), &'a [u64]> = HashMap::default();
71        for entry in &imprint.by_face {
72            // `.entry().or_insert` keeps the FIRST entry, matching the previous
73            // `by_face.iter().find(...)` (keys are unique in practice anyway).
74            by_face
75                .entry((entry.operand, entry.face_id))
76                .or_insert_with(|| entry.piece_ids.as_slice());
77        }
78        FragmentIndex {
79            by_face,
80            cosurface_pairs: imprint.cosurface_pairs.iter()
81                .flat_map(|&(a,b)| [(a,b),(b,a)]).collect(),
82            pieces_by_id: imprint
83                .pieces
84                .iter()
85                .map(|piece| (piece.id, piece))
86                .collect(),
87            vertex_points: imprint
88                .vertices
89                .iter()
90                .map(|vertex| (vertex.id, vertex.point))
91                .collect(),
92        }
93    }
94}
95
96/// Back-compat entry point: builds the imprint index for this single face.
97/// `fragment_solid` builds it once and calls `fragment_face_indexed` directly.
98pub fn fragment_face(
99    solid: &BrepSolid,
100    operand: u8,
101    face: &FaceRecord,
102    imprint: &ImprintResultRecord,
103) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
104    let index = FragmentIndex::build(imprint);
105    let mut profile = FragmentProfile::new();
106    let fragments = fragment_face_indexed(solid, operand, face, &index, &mut profile)?;
107    profile.report(operand);
108    Ok(fragments)
109}
110
111/// The winding the SOURCE face's own outer loop uses in its (u,v) chart:
112/// `Some(true)` when it is negative (clockwise), `Some(false)` when positive,
113/// `None` when the sign cannot be trusted.
114///
115/// `build_loop` orients a rebuilt fragment loop against the arrangement, whose
116/// outer cycles are ALWAYS positive-area (`arrangement.rs`: a cycle goes to
117/// `positive` only when `area > tol²`, and negative cycles become holes), and
118/// reversed it only for `!same_sense` — *material runs counter-clockwise in
119/// (u,v) exactly when `same_sense`*.
120///
121/// That rule is CORRECT and is this kernel's stated invariant: it is what
122/// `validate_uv_wire` checks (`brep/topology/validate.rs`, "wire winding
123/// disagrees with face sense"), what `watertight_tessellation::periodic::trim`
124/// cites as the authority for the same question, and what nine sites in `src/`
125/// compare a winding against. It is also forced by the definitions — with
126/// `n_s = S_u × S_v` and `n_f = same_sense ? n_s : −n_s`, material-on-the-left
127/// projected into the chart with `n_s` up leaves no freedom.
128///
129/// So a face carrying a CW outer loop with `same_sense = true` is MALFORMED,
130/// not merely unusual. This function exists because one arrives anyway: nothing
131/// refuses it on the way in, and `validate_uv_wire`'s verdict is filed under
132/// `ValidationReport::wire_warnings`, which `validate()` does not return — so
133/// every `validate().is_empty()` in the kernel reads clean on it.
134///
135/// Reading the source winding here is DEFENCE IN DEPTH, not a blessing of the
136/// chart: it stops the boolean refusing on such a face and builds the correct
137/// body instead. It does not make the face correct, and the real defect is
138/// upstream in whatever produced it.
139///
140/// Measured on the 2026-09-21 through-tunnel body: five of the stretched
141/// wedge's six faces wind positive, `Box_PZ` winds −1.2334e3 with `same_sense =
142/// true` (the plain box's `Box_PZ` winds +1.2143e3; `TF3` is what flips it).
143/// Rebuilding `Box_PZ` against the arrangement's CCW convention flips every
144/// coedge on it, which is the six same-sense edges the boolean then refuses on.
145///
146/// Restricted to OPEN charts on purpose. On a closed/periodic chart a raw
147/// shoelace is meaningless without the seam unwrap the arrangement scan does,
148/// and that path already has its own material-orientation authority in
149/// `full_span_loop_area`; leaving periodic faces on the old rule keeps this
150/// change to the case it was measured on. `None` also covers a loop with too
151/// few samples or an area too small to carry a reliable sign — the caller then
152/// keeps today's `!same_sense`.
153fn source_loop_winding_negative(face: &FaceRecord) -> Result<Option<bool>, KernelRefusal> {
154    let closed = face
155        .surface
156        .closed_directions()
157        .or_refuse(KernelStage::Fragment, "closed_directions")?;
158    if closed.0 || closed.1 {
159        return Ok(None);
160    }
161    // The OUTER loop is the one bounding the most parameter area; holes wind
162    // against it. Reading `loops[0]` would trust a convention this function
163    // exists to stop trusting.
164    let mut best: Option<f64> = None;
165    for loop_record in &face.loops {
166        let mut points: Vec<Vec2> = Vec::new();
167        for coedge in &loop_record.coedges {
168            let [d0, d1] = coedge
169                .pcurve
170                .domain()
171                .or_refuse(KernelStage::Fragment, "domain")?;
172            const SAMPLES: usize = 24;
173            for step in 0..SAMPLES {
174                let fraction = step as f64 / SAMPLES as f64;
175                // `d0 -> d1` ALWAYS. A pcurve is stored in its coedge's own
176                // traversal direction -- `reverse_fragment` flips `forward` and
177                // calls `pcurve.reversed()` together, and the loop-polygon
178                // builder below walks every pcurve `d0 -> d1` regardless of
179                // `forward`. Consulting `forward` here would reverse those
180                // segments in place and trace a zig-zag: on the plain box's
181                // `Box_NZ` (60.715 x 20 in parameter space) that yields 607.15,
182                // exactly half the 1214.3 the loop actually bounds, and the
183                // sign of a self-intersecting polygon is not the loop's
184                // winding.
185                let t = d0 + (d1 - d0) * fraction;
186                let point = coedge
187                    .pcurve
188                    .evaluate(t)
189                    .or_refuse(KernelStage::Fragment, "evaluate")?;
190                points.push(Vec2 { x: point.x, y: point.y });
191            }
192        }
193        if points.len() < 3 {
194            continue;
195        }
196        let mut area = 0.0;
197        for (index, point) in points.iter().enumerate() {
198            let next = points[(index + 1) % points.len()];
199            area += 0.5 * (crate::polygon::area_term([point.x, point.y], [next.x, next.y]));
200        }
201        if best.map(|current: f64| area.abs() > current.abs()).unwrap_or(true) {
202            best = Some(area);
203        }
204    }
205    // Scale the "is this sign real" floor to the chart, so a millimetre-wide
206    // face is not judged by a millimetre-squared absolute.
207    let u_domain = KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)
208        .or_refuse(KernelStage::Fragment, "new")?
209        .domain();
210    let v_domain = KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)
211        .or_refuse(KernelStage::Fragment, "new")?
212        .domain();
213    let chart = (u_domain[1] - u_domain[0]) * (v_domain[1] - v_domain[0]);
214    let floor = (chart.abs() * 1e-9).max(f64::MIN_POSITIVE);
215    Ok(match best {
216        Some(area) if area.abs() > floor => Some(area < 0.0),
217        _ => None,
218    })
219}
220
221/// A cut against the other operand's coincident copy of an incident face
222/// inherits that already-established face identity. This permits bounded rim
223/// repair without treating an unrelated nearby curve as the same boundary.
224fn boundary_has_cosurface_partner(
225    solid: &BrepSolid,
226    face: FaceKey,
227    boundary_edge: u64,
228    piece: &ImprintPieceRecord,
229    index: &FragmentIndex,
230) -> bool {
231    piece.support_faces.iter().filter(|other| other.operand != face.operand).any(|other| {
232        solid.shells.iter().flat_map(|shell| &shell.faces).any(|neighbour| {
233            neighbour.id != face.face_id
234                && index.cosurface_pairs.contains(&(FaceKey { operand:face.operand, face_id:neighbour.id }, *other))
235                && neighbour.loops.iter().flat_map(|lp| &lp.coedges)
236                    .any(|coedge| coedge.edge_id == boundary_edge)
237        })
238    })
239}
240
241/// Detect a distinct narrow strip before replacing its curves by chords.
242/// Search tolerance only nominates a nearby boundary; positive separation in
243/// model space and parallel interior tangents establish a resolvable strip.
244///
245/// FEATURE SCALE B4 (`BREP_FEATURE_SCALE_FRAGMENT`, default on, `=0` off): a
246/// strip between two CUTS is measured the same way. brep-bench
247/// tangent/hairline-slot: the saw's two walls cross the cube's top 1e-6
248/// apart, under the 1.4e-5 resolution of its 10-wide chart, so the
249/// arrangement read them as one line and the strip between them was never
250/// formed.
251fn thin_boundary_resolution(
252    solid: &BrepSolid,
253    face: &FaceRecord,
254    face_key: FaceKey,
255    piece_ids: &[u64],
256    index: &FragmentIndex,
257    search: f64,
258) -> (Option<(f64, f64)>, bool) {
259    let mut resolution: Option<(f64, f64)> = None;
260    // Whether a feature-scale measure (a near touch, or a strip between two
261    // cuts) found a strip: only then are the image bands narrowed.
262    let mut feature_found = false;
263    let mut take = |strip: Option<(f64, f64)>| {
264        if let Some((arrangement, sampling)) = strip {
265            resolution = Some(match resolution {
266                Some((a, s)) => (a.min(arrangement), s.min(sampling)),
267                None => (arrangement, sampling),
268            });
269        }
270    };
271    let cuts: Vec<&NurbsCurve> = piece_ids
272        .iter()
273        .filter_map(|piece_id| index.pieces_by_id.get(piece_id).and_then(|piece| pcurve_for(piece, face_key).ok()))
274        .collect();
275    for piece_id in piece_ids {
276        let Some(piece) = index.pieces_by_id.get(piece_id) else { continue };
277        let Ok(cut) = pcurve_for(piece, face_key) else { continue };
278        for boundary in face.loops.iter().flat_map(|lp| &lp.coedges) {
279            if boundary_has_cosurface_partner(solid, face_key, boundary.edge_id, piece, index) {
280                continue;
281            }
282            let strip = strip_between(face, &cut, &boundary.pcurve, search);
283            take(match strip {
284                None if crate::feature_scale::fragment_enabled() => {
285                    let touch = near_touch(face, &cut, &boundary.pcurve, search);
286                    if touch.is_some() && env_read!("BREP_DEBUG_FEATURE_SCALE").is_some() {
287                        eprintln!("feature_scale: face {} near touch cut p{piece_id} / boundary e{} -> {touch:?}", face.id, boundary.edge_id);
288                    }
289                    feature_found |= touch.is_some();
290                    touch
291                }
292                strip => strip,
293            });
294            if end_gap_enabled() {
295                let gap = end_gap(face, &cut, &boundary.pcurve, search);
296                if gap.is_some() && env_read!("BREP_DEBUG_FEATURE_SCALE").is_some() {
297                    eprintln!("feature_scale: face {} end gap cut p{piece_id} / boundary e{} -> {gap:?}", face.id, boundary.edge_id);
298                }
299                feature_found |= gap.is_some();
300                take(gap);
301            }
302        }
303    }
304    if crate::feature_scale::fragment_enabled() {
305        for (i, cut) in cuts.iter().enumerate() {
306            for other in &cuts[i + 1..] {
307                let strip = strip_between(face, cut, other, search);
308                let found = strip.or_else(|| near_touch(face, cut, other, search));
309                if found.is_some() && env_read!("BREP_DEBUG_FEATURE_SCALE").is_some() {
310                    eprintln!("feature_scale: face {} cut pair {i} {} -> {} {found:?}", face.id, if strip.is_some() { "strip" } else { "near touch" }, i);
311                }
312                feature_found |= found.is_some();
313                take(found);
314            }
315        }
316    }
317    (resolution, feature_found)
318}
319
320/// FEATURE SCALE, end gap (`BREP_FEATURE_SCALE_END_GAP`, default on, `=0` off): an
321/// open cut that ENDS near a boundary edge of the face without ending on it.
322/// `near_touch` declines every approach at a cut's end, because a marched cut
323/// stops a few 1e-6 short of the boundary it ends on and resolving to that
324/// slack kept its end from snapping to its junction. A section that ends at a
325/// genuine vertex a small but real distance off the boundary is another
326/// thing: a cube against a copy turned 0.01 deg has a section on the cube's
327/// side that ends where the turned top edge pierces it, 3.9e-4 below the
328/// side's own top edge, inside the chart's wide bands (1.4e-3 image band and
329/// 2.1e-2 junction radius on a 10-wide face), and the arrangement joined that
330/// end to the top edge: a vertex the solid does not have, and a valid shell
331/// 1.9e-5 out in volume. An end is a gap when it stands off the boundary by
332/// more than the weld floor in model space (so a marched cut's slack is not
333/// one) and within the wide bands in the chart; the face's resolutions are
334/// then taken to that gap, as for a near touch.
335pub(crate) fn end_gap_enabled() -> bool {
336    crate::switch!("BREP_FEATURE_SCALE_END_GAP", true)
337}
338
339fn end_gap(face: &FaceRecord, cut: &NurbsCurve, other: &NurbsCurve, search: f64) -> Option<(f64, f64)> {
340    // Planar faces whose cut and boundary are both straight: the regime the
341    // measure was read on. Curved faces and curved trims keep their bands
342    // (fillet corners, pole bridges and rim strips are tuned to them).
343    let straight = |curve: &NurbsCurve| curve.degree == 1 && curve.control_points.len() == 2;
344    if !face.surface.is_affine().ok()? || !straight(cut) || !straight(other) {
345        return None;
346    }
347    let [lo, hi] = cut.domain().ok()?;
348    let start = cut.evaluate(lo).ok()?;
349    let end = cut.evaluate(hi).ok()?;
350    if start.sub(end).length() <= search {
351        return None;
352    }
353    let u_domain = face.surface.domain_u().ok()?;
354    let v_domain = face.surface.domain_v().ok()?;
355    let diagonal = ((u_domain[1] - u_domain[0]).powi(2) + (v_domain[1] - v_domain[0]).powi(2)).sqrt();
356    let reach = (search * 100.0).max(diagonal * 1.5e-3);
357    let mut found: Option<(f64, f64)> = None;
358    for point in [start, end] {
359        let Ok(foot) = project_point_to_curve(other, point) else { continue };
360        if foot.distance > reach {
361            continue;
362        }
363        let (Ok(here), Ok(there)) = (face.surface.evaluate(point.x, point.y), face.surface.evaluate(foot.point.x, foot.point.y)) else {
364            continue;
365        };
366        if here.sub(there).length() <= crate::tolerance::WELD_FLOOR {
367            continue;
368        }
369        let identity = crate::classification::surface_uv_band(&face.surface, point.x, point.y, 1e-7);
370        if foot.distance <= identity * 8.0 {
371            continue;
372        }
373        let resolution = (search.min((foot.distance / 128.0).max(identity)), search.min(foot.distance / 8.0));
374        found = Some(match found {
375            Some((a, s)) => (a.min(resolution.0), s.min(resolution.1)),
376            None => resolution,
377        });
378    }
379    found
380}
381
382/// FEATURE SCALE B4, near touch (`BREP_FEATURE_SCALE_FRAGMENT`): the
383/// resolutions a cut needs where it comes closer to `other` than the
384/// arrangement resolves without meeting it. brep-bench
385/// tangent/bore-a-micron-from-the-wall: the bore's circle on each cap passes
386/// 1e-6 inside the rim circle at (5, 0), under the cap's 1.4e-5 resolution, so
387/// the two circles fused there and both cap loops opened. The gaps vary from
388/// 1e-6 to 2 around the circle, which `strip_between` reads as a tangency. The
389/// closest approach is found by 257 samples refined by golden section; it is
390/// a near touch when it lies within `8·search` in the chart yet more than 8e-7
391/// apart in model space and beyond the chart's identity band.
392fn near_touch(face: &FaceRecord, cut: &NurbsCurve, other: &NurbsCurve, search: f64) -> Option<(f64, f64)> {
393    // Curves lie in their control hulls: hulls farther apart than the reach
394    // cannot hold a near touch, and most pairs on a face are that far apart.
395    let hull = |curve: &NurbsCurve| crate::spatial::Aabb::from_curve_controls(curve).ok();
396    if !hull(cut)?.intersects(hull(other)?, search * 8.0) {
397        return None;
398    }
399    let [lo, hi] = cut.domain().ok()?;
400    let [other_lo, other_hi] = other.domain().ok()?;
401    // A cut that ENDS on `other` has no near touch with it, and saying so
402    // takes two projections instead of the search's 417 (brep-bench
403    // boolean/cross-drilled-cube: the Steinmetz arcs of crossing bores meet
404    // at their ends, and this search was 70% of that case's time). The search
405    // below samples the cut's ends exactly, so its closest approach is no
406    // wider in the chart than this end's gap (under 1e-3 of the reach and
407    // the identity band, below any gap it takes as a near touch), and the refined approach then lies
408    // at an open cut's end (`at_end`) or within 8e-7 in model space: None
409    // either way. The gap must be tiny in the chart AND in space (at a pole
410    // the chart's points meet in space while far apart in the chart). A cut
411    // that stops short of a boundary (a marched cut, by a few 1e-6) keeps
412    // the search.
413    let ends_on_other = [lo, hi].into_iter().any(|t| {
414        let Ok(point) = cut.evaluate(t) else { return false };
415        let Ok(foot) = project_point_to_curve(other, point) else { return false };
416        let identity = crate::classification::surface_uv_band(&face.surface, point.x, point.y, 1e-7);
417        if foot.distance > (search * 1e-3).min(identity) {
418            return false;
419        }
420        match (face.surface.evaluate(point.x, point.y), face.surface.evaluate(foot.point.x, foot.point.y)) {
421            (Ok(here), Ok(there)) => here.sub(there).length() <= 1e-9,
422            _ => false,
423        }
424    });
425    if ends_on_other {
426        return None;
427    }
428    let gap = |parameter: f64| -> Option<(f64, Vec3, Vec3, f64)> {
429        let point = cut.evaluate(parameter).ok()?;
430        let projected = project_point_to_curve(other, point).ok()?;
431        Some((projected.distance, point, projected.point, projected.u))
432    };
433    // A closed curve's ends are a seam, not a junction; an open one's are
434    // where it meets the rest of the arrangement.
435    let closed = |curve: &NurbsCurve, a: f64, b: f64| -> bool {
436        match (curve.evaluate(a), curve.evaluate(b)) {
437            (Ok(start), Ok(end)) => start.sub(end).length() <= search,
438            _ => false,
439        }
440    };
441    let (cut_closed, other_closed) = (closed(cut, lo, hi), closed(other, other_lo, other_hi));
442    const SAMPLES: usize = 256;
443    let step = (hi - lo) / SAMPLES as f64;
444    let mut best: Option<(f64, f64)> = None;
445    for i in 0..=SAMPLES {
446        let parameter = lo + step * i as f64;
447        if let Some((distance, _, _, _)) = gap(parameter) {
448            if best.is_none_or(|(_, d)| distance < d) {
449                best = Some((parameter, distance));
450            }
451        }
452    }
453    let (centre, _) = best?;
454    let (mut a, mut b) = ((centre - step).max(lo), (centre + step).min(hi));
455    let ratio = 0.5 * (5f64.sqrt() - 1.0);
456    for _ in 0..80 {
457        let c = b - ratio * (b - a);
458        let d = a + ratio * (b - a);
459        if gap(c)?.0 < gap(d)?.0 { b = d } else { a = c }
460    }
461    let parameter = 0.5 * (a + b);
462    let (low_gap, point, foot, foot_parameter) = gap(parameter)?;
463    if low_gap > search * 8.0 {
464        return None;
465    }
466    // Only an approach in the middle of both curves is a near touch. A marched
467    // cut stops a few 1e-6 short of the boundary it ends on (its slack past the
468    // trim), and resolving the arrangement to that gap kept its end from
469    // snapping to its junction (r16: eleven boolean fixtures refused).
470    let at_end = |t: f64, a: f64, b: f64| (t - a).abs() <= 0.01 * (b - a) || (b - t).abs() <= 0.01 * (b - a);
471    if (!cut_closed && at_end(parameter, lo, hi)) || (!other_closed && at_end(foot_parameter, other_lo, other_hi)) {
472        return None;
473    }
474    let actual = face.surface.evaluate(point.x, point.y).ok()?;
475    let rim = face.surface.evaluate(foot.x, foot.y).ok()?;
476    if actual.sub(rim).length() <= 8e-7 {
477        return None;
478    }
479    let identity = crate::classification::surface_uv_band(&face.surface, point.x, point.y, 1e-7);
480    if low_gap <= identity * 8.0 {
481        return None;
482    }
483    Some((search.min((low_gap / 128.0).max(identity)), search.min(low_gap / 8.0)))
484}
485
486/// The arrangement and sampling resolutions a strip between `cut` and
487/// `other` (both pcurves on `face`) needs, or `None` when they do not bound a
488/// strip: some sample of `cut` farther than `8·search` from `other`, a
489/// tangent not parallel to it, a sample within 8e-7 of it in model space or
490/// within the chart's identity band, or gaps varying more than fourfold (a
491/// crossing or an isolated tangency).
492fn strip_between(face: &FaceRecord, cut: &NurbsCurve, other: &NurbsCurve, search: f64) -> Option<(f64, f64)> {
493    let [lo, hi] = cut.domain().ok()?;
494    let middle = cut.evaluate((lo + hi) * 0.5).ok()?;
495    let nearest = project_point_to_curve(other, middle).ok()?;
496    if nearest.distance > search * 8.0 { return None; }
497    let mut low_gap = f64::INFINITY;
498    let mut high_gap = 0.0f64;
499    let mut identity = 0.0f64;
500    for fraction in [0.125, 0.25, 0.5, 0.75, 0.875] {
501        let parameter = lo + (hi - lo) * fraction;
502        let cut_sample = cut.derivatives(parameter, 1).ok()?;
503        let point = cut_sample[0];
504        let projected = project_point_to_curve(other, point).ok()?;
505        if projected.distance > search * 8.0 { return None; }
506        let other_sample = other.derivatives(projected.u, 1).ok()?;
507        let a = cut_sample[1];
508        let b = other_sample[1];
509        let lengths = a.length() * b.length();
510        if lengths <= 1e-30 || a.dot(b).abs() < 0.995 * lengths { return None; }
511        let actual = face.surface.evaluate(point.x, point.y).ok()?;
512        let rim = face.surface.evaluate(projected.point.x, projected.point.y).ok()?;
513        if actual.sub(rim).length() <= 8e-7 { return None; }
514        let floor = crate::classification::surface_uv_band(
515            &face.surface, point.x, point.y, 1e-7,
516        );
517        if projected.distance <= floor * 8.0 { return None; }
518        low_gap = low_gap.min(projected.distance);
519        high_gap = high_gap.max(projected.distance);
520        identity = identity.max(floor);
521    }
522    // A crossing or isolated tangency does not describe a strip.
523    if high_gap > low_gap * 4.0 { return None; }
524    Some((search.min((low_gap / 128.0).max(identity)), search.min(low_gap / 8.0)))
525}
526
527fn fragment_face_indexed(
528    solid: &BrepSolid,
529    operand: u8,
530    face: &FaceRecord,
531    index: &FragmentIndex,
532    profile: &mut FragmentProfile,
533) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
534    let face_started = profile.enabled.then(Instant::now);
535    let mut lap_start = face_started;
536    let result = fragment_face_stages(solid, operand, face, index, profile, &mut lap_start);
537    profile.face_done(face.id, face_started);
538    result
539}
540
541fn fragment_face_stages(
542    solid: &BrepSolid,
543    operand: u8,
544    face: &FaceRecord,
545    index: &FragmentIndex,
546    profile: &mut FragmentProfile,
547    lap_start: &mut Option<Instant>,
548) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
549    let face_key = FaceKey {
550        operand,
551        face_id: face.id,
552    };
553    let piece_ids = index
554        .by_face
555        .get(&(operand, face.id))
556        .copied()
557        .unwrap_or(&[]);
558    if piece_ids.is_empty() {
559        profile.uncut_probe_faces += 1;
560        let u = KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u).or_refuse(KernelStage::Fragment, "new")?.domain();
561        let v = KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v).or_refuse(KernelStage::Fragment, "new")?.domain();
562        let uv = Vec2 {
563            x: (u[0] + u[1]) / 2.0,
564            y: (v[0] + v[1]) / 2.0,
565        };
566        let probe_started = profile.enabled.then(Instant::now);
567        let centre_inside = parameter_point_in_face(face, uv, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? == PolygonClass::Inside;
568        if let Some(started) = probe_started { profile.uncut_probe += started.elapsed(); }
569        if !centre_inside {
570            // Trimmed faces need the arrangement path even without cuts to
571            // find a point in their actual outer loop.
572        } else {
573            let mut extra_test_points = Vec::new();
574            for (fu, fv) in [(0.35, 0.35), (0.65, 0.65)] {
575                let candidate = Vec2 {
576                    x: u[0] + (u[1] - u[0]) * fu,
577                    y: v[0] + (v[1] - v[0]) * fv,
578                };
579                let probe_started = profile.enabled.then(Instant::now);
580                let inside = parameter_point_in_face(face, candidate, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? == PolygonClass::Inside;
581                if let Some(started) = probe_started { profile.uncut_probe += started.elapsed(); }
582                if inside {
583                    extra_test_points.push(face.surface.evaluate(candidate.x, candidate.y).or_refuse(KernelStage::Fragment, "evaluate")?);
584                }
585            }
586            let untouched = vec![untouched_fragment(face, operand, uv, extra_test_points)?];
587            profile.untouched += profile.lap(lap_start);
588            return Ok(untouched);
589        }
590    }
591    if !piece_ids.is_empty() {
592        profile.cut_faces += 1;
593    }
594    // LOOP SPLIT (`fragment/loop_split.rs`): a face whose pieces are simple is
595    // split loop by loop; `check` runs both paths and returns the
596    // arrangement's; `on` returns the split's where it applies.
597    let mode = loop_split_mode();
598    let mut split_result: Option<Result<Vec<FaceFragmentRecord>, super::loop_split::Decline>> = None;
599    if mode != LoopSplitMode::Off && !piece_ids.is_empty() {
600        let started = profile.enabled.then(Instant::now);
601        let outcome = loop_split_face(solid, operand, face, face_key, piece_ids, index)?;
602        if let Some(started) = started {
603            profile.loop_split += started.elapsed();
604        }
605        match &outcome {
606            Ok(_) => profile.loop_split_faces += 1,
607            Err(_) => profile.loop_split_declined += 1,
608        }
609        if mode == LoopSplitMode::On {
610            if let Ok(fragments) = outcome {
611                *lap_start = profile.enabled.then(Instant::now);
612                return Ok(fragments);
613            }
614        }
615        split_result = Some(outcome);
616        *lap_start = profile.enabled.then(Instant::now);
617    }
618    let arranged = fragment_face_arranged(solid, operand, face, face_key, piece_ids, index, profile, lap_start);
619    // A face no section touches that the arrangement answered with NOTHING is
620    // not gone: it is one fragment, itself, and all it needs is a point of
621    // its own material, found beside its trim (`trim_interior_probe`). The
622    // arrangement keeps every face it does split (a pinched loop into two).
623    if piece_ids.is_empty() && trim_probe_enabled() && matches!(&arranged, Ok(fragments) if fragments.is_empty()) {
624        let u = KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u).or_refuse(KernelStage::Fragment, "new")?.domain();
625        let v = KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v).or_refuse(KernelStage::Fragment, "new")?.domain();
626        if let Some((uv, extra_test_points)) = trim_interior_probe(face, u, v)? {
627            return Ok(vec![untouched_fragment(face, operand, uv, extra_test_points)?]);
628        }
629    }
630    if let Some(split) = split_result {
631        let verdict = match (&split, &arranged) {
632            (Err(decline), _) => format!("declined {decline}"),
633            (Ok(_), Err(refusal)) => format!("MISMATCH split built {} fragments, arrangement refused: {refusal}", split.as_ref().map_or(0, Vec::len)),
634            (Ok(split), Ok(arranged)) => match fragments_agree(split, arranged) {
635                Ok(agreement) => agreement.to_string(),
636                Err(why) => format!("MISMATCH {why}"),
637            },
638        };
639        loop_split_census(|| format!("operand {operand} face {} pieces {} loops {} {verdict}", face.id, piece_ids.len(), face.loops.len()));
640    }
641    arranged
642}
643
644
645#[allow(clippy::too_many_arguments)]
646fn fragment_face_arranged(
647    solid: &BrepSolid,
648    operand: u8,
649    face: &FaceRecord,
650    face_key: FaceKey,
651    piece_ids: &[u64],
652    index: &FragmentIndex,
653    profile: &mut FragmentProfile,
654    lap_start: &mut Option<Instant>,
655) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
656
657    let u_domain = KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u).or_refuse(KernelStage::Fragment, "new")?.domain();
658    let v_domain = KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v).or_refuse(KernelStage::Fragment, "new")?.domain();
659    let diagonal =
660        ((u_domain[1] - u_domain[0]).powi(2) + (v_domain[1] - v_domain[0]).powi(2)).sqrt();
661    let original_resolution = 1e-7f64.max(diagonal * 1e-6);
662    let (strip_resolution, feature_strip) = thin_boundary_resolution(solid, face, face_key, piece_ids, index, original_resolution);
663    let (arrangement_tolerance, sampling_bound) = strip_resolution
664        .unwrap_or((original_resolution, original_resolution));
665    // FEATURE SCALE B4 (`BREP_FEATURE_SCALE_FRAGMENT`): with a thin strip
666    // between two cuts or a near touch on the face, a chain end is matched to another vertex's image only within
667    // the strip's own sampling bound in the chart and 1e-7 in model space
668    // (`strip_between` admits only strips over 8e-7 wide), not 100
669    // resolutions and 1e-5: the hairline slot's two walls end 1e-6 apart on
670    // the cube's top, and each wall's cut took the other's far end.
671    // Only for a strip a feature-scale measure found: a cut-against-boundary
672    // strip the fragmenter always measured kept its wide bands (narrowing them
673    // too opened loops on revolve ∪ wedge fixtures with coincident side planes).
674    let strip_snap = feature_strip;
675    let (image_band, image_band_3d) = if strip_snap {
676        (sampling_bound, 1e-7)
677    } else {
678        (arrangement_tolerance * 100.0, 1e-5)
679    };
680    let snap_threshold = 0.1 * diagonal;
681    let junction_radius = (arrangement_tolerance * 50.0).max(diagonal * 1.5e-3);
682    // WRAPPED-BAND RE-BASE: a face on a closed direction whose material is the
683    // wrapped COMPLEMENT of its trim hull (two full-wrap rims at the hull
684    // edges, material crossing the seam between them — trial 35's torus face:
685    // rims at v=0.25/1.0, material v∈[0,0.25]) cannot be arranged in the flat
686    // domain rectangle: a cut through the band ends at v=0 while its junction
687    // partner (the split rim vertex) sits at v=1, so every cut dangles, cycle
688    // extraction fails, and the face is dropped wholesale (one-use cascade).
689    // Re-base into the material-coherent frame [hull_high, hull_low+period]:
690    // shift every chain point on the low side of the hull window (+period), so
691    // rims and cuts junction in ONE unwrapped chart, and wrap region test
692    // points back into the domain on exit. Detection mirrors the imprint's
693    // restricted-carrier wrap guard: the hull-complement strip contains no
694    // loop curves, so one sample at its middle decides materiality exactly.
695    // Escape hatch: BREP_BAND_REBASE=0.
696    let band_rebase: [Option<(f64, f64)>; 2] = {
697        let mut rebase = [None, None];
698        let closed = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
699        if (closed.0 || closed.1) && std::env::var("BREP_BAND_REBASE").as_deref() != Ok("0") {
700            // Per-coedge hulls on each axis, plus whether any ISO-RIM
701            // (a coedge whose pcurve is near-constant on the axis) sits at a
702            // domain edge — the discriminator for the wrapped-band class.
703            // A seam-straddling boundary band (helmet Face_15, whose source
704            // pcurves are already unwrapped past the domain) has no such rim
705            // and its frame is already coherent; it must NOT be re-based.
706            let mut coverage: [Vec<[f64; 2]>; 2] = [Vec::new(), Vec::new()];
707            let mut edge_rim = [false, false];
708            let mut hull = [
709                [f64::INFINITY, f64::NEG_INFINITY],
710                [f64::INFINITY, f64::NEG_INFINITY],
711            ];
712            for coedge in face
713                .loops
714                .iter()
715                .flat_map(|loop_record| &loop_record.coedges)
716            {
717                let mut extent = [
718                    [f64::INFINITY, f64::NEG_INFINITY],
719                    [f64::INFINITY, f64::NEG_INFINITY],
720                ];
721                for control in &coedge.pcurve.control_points {
722                    if control.w.abs() <= 1e-300 {
723                        continue;
724                    }
725                    let point = [control.x / control.w, control.y / control.w];
726                    for axis in 0..2 {
727                        extent[axis][0] = extent[axis][0].min(point[axis]);
728                        extent[axis][1] = extent[axis][1].max(point[axis]);
729                        hull[axis][0] = hull[axis][0].min(point[axis]);
730                        hull[axis][1] = hull[axis][1].max(point[axis]);
731                    }
732                }
733                for axis in 0..2 {
734                    if !extent[axis][0].is_finite() {
735                        continue;
736                    }
737                    let domain = if axis == 0 { u_domain } else { v_domain };
738                    let span = domain[1] - domain[0];
739                    coverage[axis].push(extent[axis]);
740                    let flat = extent[axis][1] - extent[axis][0] <= 1e-3 * span;
741                    let at_edge = (extent[axis][0] - domain[0]).abs() <= 1e-6 * span
742                        || (extent[axis][1] - domain[1]).abs() <= 1e-6 * span;
743                    if flat && at_edge {
744                        edge_rim[axis] = true;
745                    }
746                }
747            }
748            for axis in 0..2 {
749                if !(if axis == 0 { closed.0 } else { closed.1 }) || !edge_rim[axis] {
750                    continue;
751                }
752                let domain = if axis == 0 { u_domain } else { v_domain };
753                let span = domain[1] - domain[0];
754                // Merge the coverage intervals (clamped into the domain) and
755                // find the largest uncovered gap. The gap contains no boundary
756                // curve, so it is uniformly material or uniformly void — one
757                // sample at its middle decides exactly. A strictly INTERIOR
758                // void gap means the material wraps through the domain edge.
759                let mut intervals: Vec<[f64; 2]> = coverage[axis]
760                    .iter()
761                    .map(|window| {
762                        [window[0].max(domain[0]), window[1].min(domain[1])]
763                    })
764                    .filter(|window| window[1] >= window[0])
765                    .collect();
766                intervals.sort_by(|a, b| a[0].total_cmp(&b[0]));
767                let mut gaps: Vec<[f64; 2]> = Vec::new();
768                let mut reach = domain[0];
769                for window in &intervals {
770                    if window[0] > reach {
771                        gaps.push([reach, window[0]]);
772                    }
773                    reach = reach.max(window[1]);
774                }
775                if reach < domain[1] {
776                    gaps.push([reach, domain[1]]);
777                }
778                let other_mid = (hull[1 - axis][0].max(if axis == 0 {
779                    v_domain[0]
780                } else {
781                    u_domain[0]
782                }) + hull[1 - axis][1].min(if axis == 0 {
783                    v_domain[1]
784                } else {
785                    u_domain[1]
786                })) / 2.0;
787                let mut void: Option<[f64; 2]> = None;
788                for gap in gaps {
789                    if gap[1] - gap[0] <= 1e-6 * span {
790                        continue;
791                    }
792                    let mid = (gap[0] + gap[1]) / 2.0;
793                    let sample = if axis == 0 {
794                        Vec2 {
795                            x: mid,
796                            y: other_mid,
797                        }
798                    } else {
799                        Vec2 {
800                            x: other_mid,
801                            y: mid,
802                        }
803                    };
804                    if parameter_point_in_face(face, sample, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? != PolygonClass::Inside
805                        && void
806                            .map(|best| gap[1] - gap[0] > best[1] - best[0])
807                            .unwrap_or(true)
808                    {
809                        void = Some(gap);
810                    }
811                }
812                if let Some([g0, g1]) = void {
813                    // The material+boundary arc wraps the domain edge exactly
814                    // when boundary coverage touches BOTH edge representations
815                    // (the on-seam rim at one, the material-side chains at the
816                    // other) with the void between them. An ordinary band
817                    // (material inside the rims) touches only one edge and
818                    // must keep the flat frame.
819                    let touches_low = intervals
820                        .iter()
821                        .any(|window| window[0] <= domain[0] + 1e-6 * span);
822                    let touches_high = intervals
823                        .iter()
824                        .any(|window| window[1] >= domain[1] - 1e-6 * span);
825                    if touches_low && touches_high {
826                        // Shift everything on the low side of the void up one
827                        // period so the band is contiguous in the arrangement
828                        // frame.
829                        rebase[axis] = Some(((g0 + g1) / 2.0, span));
830                    }
831                }
832            }
833        }
834        rebase
835    };
836    let rebase_point = |mut point: Vec2| -> Vec2 {
837        if let Some((cutoff, period)) = band_rebase[0] {
838            if point.x < cutoff {
839                point.x += period;
840            }
841        }
842        if let Some((cutoff, period)) = band_rebase[1] {
843            if point.y < cutoff {
844                point.y += period;
845            }
846        }
847        point
848    };
849    // Region test points computed in a re-based or unwrapped frame must be
850    // folded back into the domain before trim classification / evaluation.
851    // Wrapping is gated on the axis being CLOSED (a coherent flat face never
852    // produces out-of-domain interior points, so this is a no-op there).
853    let wrap_back = {
854        let closed = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
855        move |mut point: Vec2| -> Vec2 {
856            if closed.0 {
857                let span = u_domain[1] - u_domain[0];
858                if point.x < u_domain[0] || point.x > u_domain[1] {
859                    point.x = u_domain[0] + (point.x - u_domain[0]).rem_euclid(span);
860                }
861            }
862            if closed.1 {
863                let span = v_domain[1] - v_domain[0];
864                if point.y < v_domain[0] || point.y > v_domain[1] {
865                    point.y = v_domain[0] + (point.y - v_domain[0]).rem_euclid(span);
866                }
867            }
868            point
869        }
870    };
871    profile.setup += profile.lap(lap_start);
872    let pieces_by_id = &index.pieces_by_id;
873    let mut cut_hints = Vec::new();
874    let mut active_pieces = Vec::new();
875    let mut cut_keys = HashMap::default();
876    for piece_id in piece_ids {
877        let piece = pieces_by_id
878            .get(piece_id)
879            .ok_or_else(|| KernelRefusal::internal(KernelStage::Fragment, "fragment.face_split", "fragment_face: missing imprint piece"))?;
880        let pcurve = pcurve_for(piece, face_key)?;
881        let [start, end] = pcurve.domain().or_refuse(KernelStage::Fragment, "domain")?;
882        let a = pcurve.evaluate(start).or_refuse(KernelStage::Fragment, "evaluate")?;
883        let b = pcurve.evaluate(end).or_refuse(KernelStage::Fragment, "evaluate")?;
884        let middle = pcurve.evaluate((start + end) / 2.0).or_refuse(KernelStage::Fragment, "evaluate")?;
885        for point in [a, b] {
886            cut_hints.push(Vec2 {
887                x: point.x,
888                y: point.y,
889            });
890        }
891        let quantize = |point: Vec3| {
892            (
893                (point.x / arrangement_tolerance).round() as i64,
894                (point.y / arrangement_tolerance).round() as i64,
895            )
896        };
897        let mut endpoints = [quantize(a), quantize(b)];
898        endpoints.sort();
899        let key = (endpoints[0], quantize(middle), endpoints[1]);
900        if cut_keys.insert(key, *piece_id).is_none() {
901            active_pieces.push(*piece_id);
902        }
903    }
904
905    let mut segments = Vec::new();
906    let mut chains = Vec::new();
907    let mut vertex_images: std::collections::HashMap<String, Vec<Vec2>> =
908        std::collections::HashMap::new();
909    let mut endpoint_images: Vec<(Vec3, Vec2)> = Vec::new();
910    // Each boundary image carries its 3D vertex point so the boundary-side
911    // canonicalization can refuse to unify two DISTINCT boundary vertices.
912    let mut boundary_images: Vec<(Vec2, Vec3)> = Vec::new();
913    // BOUNDARY-VERTEX ANCHOR (t427 box-corner graze). The boundary-side snap
914    // below (rule 3) merges a chain endpoint onto a same-side boundary image
915    // within arr_tol*100 with NO 3D check. For a CUT/section terminus that is
916    // the intended near-tangent graze bridge onto a trim edge. For a BOUNDARY
917    // endpoint it is a bug: two genuinely-distinct same-operand boundary
918    // vertices (t427: box corner v8 at v=68.4851 vs the edge-16 split A at
919    // v=68.4783, 6.8e-3 apart on the u=max domain edge, 6.8e-3 apart in 3D ≫
920    // the 1e-5 weld floor) get unified, which droops edge 21's pcurve and
921    // mints a phantom corner lens bounded by derived edges → a one-use cluster
922    // that only strands in UNION (where the unsectioned neighbour keeps the
923    // whole edge 21). Anchor a boundary endpoint to a same-side image only when
924    // they are the SAME physical vertex (3D-coincident within the weld floor).
925    // Hatch BREP_BOUNDARY_VERTEX_ANCHOR=0 restores the pre-fix behaviour.
926    let boundary_vertex_anchor_off =
927        std::env::var("BREP_BOUNDARY_VERTEX_ANCHOR").as_deref() == Ok("0");
928    let boundary_side = |point: Vec2| {
929        let distances = [
930            (point.x - u_domain[0]).abs(),
931            (point.x - u_domain[1]).abs(),
932            (point.y - v_domain[0]).abs(),
933            (point.y - v_domain[1]).abs(),
934        ];
935        let index = distances
936            .iter()
937            .enumerate()
938            .min_by(|a, b| a.1.total_cmp(b.1))
939            .map(|entry| entry.0)
940            .unwrap();
941        (distances[index] <= arrangement_tolerance * 50.0).then_some(index)
942    };
943
944    // RIM BRIDGE (`BREP_RIM_BRIDGE`, default on, `=0` off; the imprint mints the
945    // bridge): a straight section a few `tol / |tan·n|` long between two
946    // rim pierces, one end on a vertex of this face's rim (within the weld)
947    // and the other a distinct vertex just inside it. The far end lies far closer to the rim in the chart
948    // than in space, so rule 3 below (a cut end onto a same-side boundary
949    // image, no 3-D check) would fold the bridge onto the rim vertex and the
950    // sections meeting it would skip it. Its ends keep their own images.
951    let bridge_keys: HashSet<String> = if rim_bridge_enabled() {
952        let weld = crate::tolerance::assembler_weld(1e-7);
953        // The face's own boundary vertices: a bridge has one end on its rim.
954        let mut rim_points = Vec::new();
955        for coedge in face.loops.iter().flat_map(|loop_record| &loop_record.coedges) {
956            if let Some(edge) = solid.edges.iter().find(|edge| edge.id == coedge.edge_id) {
957                for vertex_id in [edge.start_vertex_id, edge.end_vertex_id] {
958                    rim_points.push(vertex_point(solid, vertex_id)?);
959                }
960            }
961        }
962        let on_rim = |point: &Vec3| rim_points.iter().any(|rim| rim.sub(*point).length() <= weld);
963        piece_ids
964            .iter()
965            .filter_map(|piece_id| pieces_by_id.get(piece_id))
966            .filter(|piece| {
967                piece.start_vertex_id != piece.end_vertex_id
968                    && piece.curve.degree == 1
969                    && piece.curve.control_points.len() == 2
970                    && match (index.vertex_points.get(&piece.start_vertex_id), index.vertex_points.get(&piece.end_vertex_id)) {
971                        (Some(a), Some(b)) => {
972                            let gap = a.sub(*b).length();
973                            gap > weld && gap <= 1e-3 && (on_rim(a) || on_rim(b))
974                        }
975                        _ => false,
976                    }
977            })
978            .flat_map(|piece| [format!("i:{}", piece.start_vertex_id), format!("i:{}", piece.end_vertex_id)])
979            .collect()
980    } else {
981        HashSet::default()
982    };
983    let mut add_chain = |mut points: Vec<Vec2>,
984                         source: ChainSource,
985                         vertex_start_key: String,
986                         vertex_start_point: Vec3,
987                         vertex_end_key: String,
988                         vertex_end_point: Vec3,
989                         is_boundary: bool|
990     -> Result<(), KernelRefusal> {
991        for (at, key, point3) in [
992            (0usize, vertex_start_key, vertex_start_point),
993            (points.len() - 1, vertex_end_key, vertex_end_point),
994        ] {
995            let point = points[at];
996            let bridged = bridge_keys.contains(&key);
997            let images = vertex_images.entry(key).or_default();
998            let canonical = images
999                .iter()
1000                .copied()
1001                .find(|image| image.sub(point).length() <= snap_threshold)
1002                .or_else(|| {
1003                    endpoint_images
1004                        .iter()
1005                        .find(|(candidate3, candidate)| {
1006                            candidate3.sub(point3).length() <= image_band_3d
1007                                && candidate.sub(point).length() <= image_band
1008                        })
1009                        .map(|candidate| candidate.1)
1010                })
1011                .or_else(|| {
1012                    if bridged {
1013                        return None;
1014                    }
1015                    boundary_side(point).and_then(|side| {
1016                        boundary_images
1017                            .iter()
1018                            .find(|(candidate, candidate3)| {
1019                                boundary_side(*candidate) == Some(side)
1020                                    && candidate.sub(point).length()
1021                                        <= image_band
1022                                    // Boundary endpoints only merge to a same-side
1023                                    // image when 3D-coincident (same physical
1024                                    // vertex); cut endpoints keep the graze bridge.
1025                                    && (boundary_vertex_anchor_off
1026                                        || !is_boundary
1027                                        || candidate3.sub(point3).length() <= 1e-5)
1028                            })
1029                            .map(|(candidate, _)| *candidate)
1030                    })
1031                });
1032            if let Some(canonical) = canonical {
1033                points[at] = canonical;
1034                images.push(canonical);
1035            } else {
1036                images.push(point);
1037                endpoint_images.push((point3, point));
1038                if is_boundary && boundary_side(point).is_some() {
1039                    boundary_images.push((point, point3));
1040                }
1041            }
1042        }
1043        if points.len() > 2 {
1044            let start = points[0];
1045            let end = points[points.len() - 1];
1046            let thin = |radius: f64| {
1047                let mut kept = vec![start];
1048                kept.extend(points[1..points.len() - 1].iter().copied().filter(|point| {
1049                    point.sub(start).length() > radius && point.sub(end).length() > radius
1050                }));
1051                kept.push(end);
1052                kept
1053            };
1054            let mut kept = thin(junction_radius);
1055            // SMALL RING (`BREP_FRAGMENT_SMALL_RING`): a chain that lies wholly
1056            // inside the junction radius of its own ends (the 0.028 pinhole on a
1057            // plane charted over [0, 30]², radius 0.064) loses every interior
1058            // point: a closed one bounds nothing and two half-circles fold onto
1059            // one chord, so the region they ring is never carved. When the
1060            // thinning would leave the chain flatter than it is (its bulge off
1061            // the chord, or off its vertex when closed, past ten tolerances),
1062            // thin it at a tenth of that bulge instead.
1063            let closed = start.sub(end).length() <= arrangement_tolerance;
1064            if kept.len() < if closed { 4 } else { 3 } && small_ring_enabled() {
1065                let chord = end.sub(start);
1066                let span = chord.length();
1067                let bulge = points
1068                    .iter()
1069                    .map(|point| {
1070                        let offset = point.sub(start);
1071                        if closed || span <= arrangement_tolerance {
1072                            offset.length()
1073                        } else {
1074                            (offset.x * chord.y - offset.y * chord.x).abs() / span
1075                        }
1076                    })
1077                    .fold(0.0, f64::max);
1078                if bulge > arrangement_tolerance * 10.0 {
1079                    kept = thin(bulge * 0.1);
1080                }
1081            }
1082            points = kept;
1083        }
1084        let chain_index = chains.len();
1085        let chain_segments = points
1086            .windows(2)
1087            .map(|pair| (pair[0], pair[1]))
1088            .collect::<Vec<_>>();
1089        for (segment_index, pair) in points.windows(2).enumerate() {
1090            segments.push(Segment2 {
1091                a: pair[0],
1092                b: pair[1],
1093                tag: segment_tag(chain_index, segment_index),
1094            });
1095        }
1096        chains.push(Chain {
1097            source,
1098            count: points.len() - 1,
1099            start: points[0],
1100            end: points[points.len() - 1],
1101            segments: chain_segments,
1102        });
1103        Ok(())
1104    };
1105
1106    profile.hints += profile.lap(lap_start);
1107    // SEAM-IMAGE LOOPS: per-loop u-control-hulls, computed once for the
1108    // straddling-hole gate below (does any OTHER loop span the full closed-u
1109    // domain — the rectangle-with-seam outer that closes the strip walls).
1110    let loop_hull_u: Vec<[f64; 2]> = face
1111        .loops
1112        .iter()
1113        .map(|loop_record| {
1114            let mut hull = [f64::INFINITY, f64::NEG_INFINITY];
1115            for coedge in &loop_record.coedges {
1116                for control in &coedge.pcurve.control_points {
1117                    if control.w.abs() <= 1e-300 {
1118                        continue;
1119                    }
1120                    hull[0] = hull[0].min(control.x / control.w);
1121                    hull[1] = hull[1].max(control.x / control.w);
1122                }
1123            }
1124            hull
1125        })
1126        .collect();
1127    // Signed area of the face's full-u-span loop (the rectangle-with-seam
1128    // outer), recorded as it is processed: it defines the chart's material
1129    // orientation, so a HOLE is any loop winding OPPOSITE to it (the absolute
1130    // sign is chart-dependent — mirrored charts flip both).
1131    let mut full_span_loop_area: Option<f64> = None;
1132    // Post-rebase boundary polyline segments (incl. seam-image copies), kept
1133    // for the sag-refinement boundary-crossing parity guard on cut chains.
1134    let mut boundary_segments: Vec<(Vec2, Vec2)> = Vec::new();
1135    // The u and v extent of the boundary chains as the arrangement places
1136    // them — after the seam-hop unwrap and the band re-base, before any
1137    // seam-image copy — which is the period window the face's material lives
1138    // in. See the cut-chain period lift below.
1139    let mut band_hull = [[f64::INFINITY, f64::NEG_INFINITY]; 2];
1140    // BOUNDARY-VERTEX HINTS (2026-11-02): a curved boundary chord is refined
1141    // where ANOTHER boundary vertex of the face lies within its length, exactly
1142    // as `refine_near_hints` already refines it near a cut's endpoints.
1143    // `sample_boundary_chain` only keeps a chord within a ten-thousandth of the
1144    // domain diagonal of its pcurve, and that bound cannot see a second loop:
1145    // the plate's top face (diagonal 222) drew its r 10.7 blend-corner arc at
1146    // 16 chords sagging 0.0128, while the corner vertex of the hole the earlier
1147    // subtracts cut sits 0.011 inside that arc. The hole loop poked through the
1148    // outer loop's polyline, the arrangement cut the rim at the chord crossings
1149    // (0.04 from the true arc, on the far side of the hole vertex), and the
1150    // subtract refused with four one-use rim edges and a 0.0125 vertex gap. The
1151    // face's own vertices are hints, so a chord that a neighbouring vertex
1152    // approaches within its length is drawn sixteen times finer there (sag 5e-5
1153    // against the 0.011 clearance). A chord's own endpoints are not hints for
1154    // it (every chord ends at a vertex), and a straight pcurve has no chords to
1155    // refine. Escape hatch `BREP_BOUNDARY_VERTEX_HINTS=0|off`.
1156    let boundary_vertex_hints: Vec<Vec2> = if boundary_vertex_hints_enabled() {
1157        let mut hints = Vec::new();
1158        for loop_record in &face.loops {
1159            for coedge in &loop_record.coedges {
1160                let [start, end] = coedge.pcurve.domain().or_refuse(KernelStage::Fragment, "domain")?;
1161                for parameter in [start, end] {
1162                    let point = coedge.pcurve.evaluate(parameter).or_refuse(KernelStage::Fragment, "evaluate")?;
1163                    hints.push(Vec2 { x: point.x, y: point.y });
1164                }
1165            }
1166        }
1167        hints
1168    } else {
1169        Vec::new()
1170    };
1171    for (loop_index, loop_record) in face.loops.iter().enumerate() {
1172        // SEAM-HOP UNWRAP: a loop whose pcurves are drawn IN-DOMAIN but hop
1173        // the seam between consecutive coedges (t91's face 675: the boundary
1174        // jumps u=1→0 at v≈0.967 and back at v≈0.453) is incoherent in the
1175        // flat chart — its cut chains mint fine and then dangle against the
1176        // hopping boundary, cycle extraction fails, and the face drops
1177        // wholesale (the trial-35 zero-fragment signature, third variant).
1178        // Unwrap onto the covering plane with the SAME per-coedge offsets the
1179        // mass integrator / tessellator / seam-band classifier use
1180        // (`loop_seam_offsets`); a continuous loop gets all-zero offsets and
1181        // is untouched (helmet-class unwrapped sources, native seam-edge
1182        // loops, plain faces). Composes with the band re-base above: offsets
1183        // normalize the boundary FIRST, after which the re-base's
1184        // below-cutoff boundary shifts are no-ops and only its cut-chain
1185        // shifts still apply. Escape hatch: BREP_LOOP_UNWRAP=0.
1186        let closed = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
1187        let offsets = if (closed.0 || closed.1)
1188            && std::env::var("BREP_LOOP_UNWRAP").as_deref() != Ok("0")
1189        {
1190            crate::topology::loop_seam_offsets(
1191                &loop_record.coedges,
1192                closed.0,
1193                closed.1,
1194                u_domain[1] - u_domain[0],
1195                v_domain[1] - v_domain[0],
1196            ).or_refuse(KernelStage::Fragment, "csg.fragment.face_split")?
1197        } else {
1198            vec![[0.0, 0.0]; loop_record.coedges.len()]
1199        };
1200        let mut sampled: Vec<(usize, Vec<Vec2>, NurbsCurve, u64, u64)> =
1201            Vec::with_capacity(loop_record.coedges.len());
1202        for (coedge_index, coedge) in loop_record.coedges.iter().enumerate() {
1203            let edge = edge_for(solid, coedge.edge_id)?;
1204            let (start_vertex, end_vertex) = if coedge.forward {
1205                (edge.start_vertex_id, edge.end_vertex_id)
1206            } else {
1207                (edge.end_vertex_id, edge.start_vertex_id)
1208            };
1209            let offset = offsets
1210                .get(coedge_index)
1211                .copied()
1212                .unwrap_or([0.0, 0.0]);
1213            // Adaptive samples are not uniformly spaced in parameter. Do not
1214            // feed them to refine_near_hints, which assumes uniform input.
1215            let boundary_points = if strip_resolution.is_some() {
1216                let initial = sample_chain(&coedge.pcurve)?;
1217                sag_refine_chain(&coedge.pcurve, initial, sampling_bound)?.0
1218            } else {
1219                // A curved boundary is drawn within a ten-thousandth of the
1220                // domain's diagonal of itself (`sample_boundary_chain`), so a
1221                // narrow face's two rims cannot cross each other.
1222                let initial = sample_boundary_chain(&coedge.pcurve, (diagonal * 1e-4).max(sampling_bound), 1024)?;
1223                if boundary_vertex_hints.is_empty() || initial.len() < 3 {
1224                    refine_near_hints(&initial, &coedge.pcurve, &cut_hints)?
1225                } else {
1226                    let own = [initial[0], initial[initial.len() - 1]];
1227                    let hints: Vec<Vec2> = cut_hints
1228                        .iter()
1229                        .copied()
1230                        .chain(boundary_vertex_hints.iter().copied().filter(|hint| {
1231                            own.iter().all(|end| hint.sub(*end).length() > arrangement_tolerance)
1232                        }))
1233                        .collect();
1234                    refine_near_hints(&initial, &coedge.pcurve, &hints)?
1235                }
1236            };
1237            let points: Vec<Vec2> = boundary_points
1238                    .into_iter()
1239                    .map(|point| {
1240                        rebase_point(Vec2 {
1241                            x: point.x + offset[0],
1242                            y: point.y + offset[1],
1243                        })
1244                    })
1245                    .collect();
1246            let curve = trimmed_curve(&edge.curve, edge.t0, edge.t1)?;
1247            let curve = if coedge.forward {
1248                curve
1249            } else {
1250                curve.reversed().or_refuse(KernelStage::Fragment, "reversed")?
1251            };
1252            sampled.push((coedge_index, points, curve, start_vertex, end_vertex));
1253        }
1254        // SEAM-IMAGE for a STRADDLING HOLE loop: an inner (hole) loop whose
1255        // unwrapped chains poke past a closed-u domain wall exists at only ONE
1256        // period image in the arrangement strip, so the strip's OTHER side
1257        // never sees it — the seam-adjacent region there keeps a boundary
1258        // running straight through the hole opening (t181/00000312#p7: face
1259        // 519's decagon hole unwraps to u∈[−0.039,0.039]; the region left of
1260        // the seam used the FULL seam edge 475 while the right region split at
1261        // the hole crossings; the hole's far-half rim edges stranded one-use
1262        // and an 18mm derived chord bridged the gap). Insert a duplicate of
1263        // the loop's chains shifted one period toward the strip's other side,
1264        // so BOTH walls are split by the hole and the half-hole phantom
1265        // regions (rejected by the cross-frame containment) carve it out.
1266        // STRUCTURAL GATE (all required): u-closed face; ≥2 loops; the loop's
1267        // unwrapped span crosses a u-wall by > junction slack; loop width ≤
1268        // half the period (a compact hole, never a rim/horizon); winding
1269        // OPPOSITE to the full-span outer loop (hole orientation relative to
1270        // the chart — an outer loop copy would double-cover material); and
1271        // some OTHER loop's control hull spans the full u-domain (the
1272        // seam-carrying rectangle exists). Escape hatch:
1273        // BREP_SEAM_IMAGE_LOOPS=0.
1274        let image_shift: Option<f64> = {
1275            let span = u_domain[1] - u_domain[0];
1276            let slack = arrangement_tolerance * 50.0;
1277            if !closed.0
1278                || face.loops.len() < 2
1279                || span <= 0.0
1280                || std::env::var("BREP_SEAM_IMAGE_LOOPS").as_deref() == Ok("0")
1281            {
1282                None
1283            } else {
1284                let mut min_u = f64::INFINITY;
1285                let mut max_u = f64::NEG_INFINITY;
1286                let mut area = 0.0;
1287                let mut flat: Vec<Vec2> = sampled
1288                    .iter()
1289                    .flat_map(|(_, points, ..)| points.iter().copied())
1290                    .collect();
1291                if flat.len() < 3 {
1292                    flat.clear();
1293                }
1294                for (index, point) in flat.iter().enumerate() {
1295                    min_u = min_u.min(point.x);
1296                    max_u = max_u.max(point.x);
1297                    let next = flat[(index + 1) % flat.len()];
1298                    area += crate::polygon::area_term([point.x, point.y], [next.x, next.y]);
1299                }
1300                // Full-span discrimination must use the UNWRAPPED width: a
1301                // straddling hole's raw control hull spans the whole domain
1302                // (its halves are stored at both seam sides), but its
1303                // unwrapped chains are compact; only the true outer rectangle
1304                // stays period-wide after unwrapping.
1305                let spans_full_domain =
1306                    !flat.is_empty() && max_u - min_u >= span * (1.0 - 1e-6);
1307                if spans_full_domain && full_span_loop_area.is_none() {
1308                    full_span_loop_area = Some(area);
1309                }
1310                let straddles_low = min_u < u_domain[0] - slack;
1311                let straddles_high = max_u > u_domain[1] + slack;
1312                let compact = max_u - min_u <= 0.5 * span;
1313                let is_hole = full_span_loop_area
1314                    .map(|outer| !spans_full_domain && area * outer < 0.0)
1315                    .unwrap_or(false);
1316                let has_full_span_other = loop_hull_u.iter().enumerate().any(|(other, hull)| {
1317                    other != loop_index
1318                        && hull[0] <= u_domain[0] + 1e-6 * span
1319                        && hull[1] >= u_domain[1] - 1e-6 * span
1320                });
1321                if compact && is_hole && has_full_span_other && (straddles_low ^ straddles_high) {
1322                    if env_read!("BREP_DEBUG_FRAG")
1323                        .map(|value| value == face.id.to_string())
1324                        .unwrap_or(false)
1325                    {
1326                        eprintln!(
1327                            "  seam-image: face {} loop {loop_index} straddles u-wall (u=[{min_u:.6},{max_u:.6}]) — inserting {} period image",
1328                            face.id,
1329                            if straddles_low { "+1" } else { "-1" }
1330                        );
1331                    }
1332                    Some(if straddles_low { span } else { -span })
1333                } else {
1334                    None
1335                }
1336            }
1337        };
1338        for (coedge_index, points, curve, start_vertex, end_vertex) in sampled {
1339            let coedge = &loop_record.coedges[coedge_index];
1340            for point in &points {
1341                band_hull[0] = [band_hull[0][0].min(point.x), band_hull[0][1].max(point.x)];
1342                band_hull[1] = [band_hull[1][0].min(point.y), band_hull[1][1].max(point.y)];
1343            }
1344            boundary_segments.extend(points.windows(2).map(|pair| (pair[0], pair[1])));
1345            add_chain(
1346                points.clone(),
1347                ChainSource::Boundary {
1348                    coedge: coedge.clone(),
1349                    curve: curve.clone(),
1350                },
1351                format!("b:{operand}:{start_vertex}"),
1352                vertex_point(solid, start_vertex)?,
1353                format!("b:{operand}:{end_vertex}"),
1354                vertex_point(solid, end_vertex)?,
1355                true,
1356            )?;
1357            if let Some(shift) = image_shift {
1358                let shifted: Vec<Vec2> = points
1359                    .iter()
1360                    .map(|point| Vec2 {
1361                        x: point.x + shift,
1362                        y: point.y,
1363                    })
1364                    .collect();
1365                boundary_segments
1366                    .extend(shifted.windows(2).map(|pair| (pair[0], pair[1])));
1367                add_chain(
1368                    shifted,
1369                    ChainSource::Boundary {
1370                        coedge: coedge.clone(),
1371                        curve,
1372                    },
1373                    format!("b:{operand}:{start_vertex}"),
1374                    vertex_point(solid, start_vertex)?,
1375                    format!("b:{operand}:{end_vertex}"),
1376                    vertex_point(solid, end_vertex)?,
1377                    true,
1378                )?;
1379            }
1380        }
1381    }
1382    // CUT-CHAIN CLAMP BOUND: on a CLOSED direction the face's boundary band may
1383    // legitimately extend past the surface domain — a seam-straddling face is
1384    // arranged in UNWRAPPED coordinates (helmet Face_15's boundary chains run
1385    // u∈[-0.178, 0.295] on a [0,1]-domain surface). Clamping a cut pcurve to
1386    // the surface domain there tears it off the band: the branch-preserved
1387    // truncation bridge collapsed to a count=1 sliver hugging u=0 and the face
1388    // never split. Widen the clamp bound to domain ∪ trim-pcurve control hulls
1389    // (the hulls bound the boundary chains); a cut already inside the domain is
1390    // untouched, and OPEN directions keep the plain domain clamp. Escape hatch
1391    // BREP_FRAG_ENV_CLAMP=0.
1392    let (clamp_u, clamp_v) = {
1393        let mut clamp_u = u_domain;
1394        let mut clamp_v = v_domain;
1395        let (closed_u, closed_v) = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
1396        // NOTE (2026-09-16): this hatch's ONLY tamper guard,
1397        // `csg/fragment/tests.rs::seam_band_cut_chain_splits_periodic_face`, went
1398        // INERT when the march-window lift landed — that test now passes with this
1399        // hatch off, so nothing tamper-verifies this mechanism any more. Not that it
1400        // is untested: no other test NAMES this hatch.
1401        if (closed_u || closed_v) && std::env::var("BREP_FRAG_ENV_CLAMP").as_deref() != Ok("0") {
1402            for loop_record in &face.loops {
1403                for coedge in &loop_record.coedges {
1404                    for point in &coedge.pcurve.control_points {
1405                        if point.w.abs() <= 1e-300 {
1406                            continue;
1407                        }
1408                        if closed_u {
1409                            clamp_u[0] = clamp_u[0].min(point.x / point.w);
1410                            clamp_u[1] = clamp_u[1].max(point.x / point.w);
1411                        }
1412                        if closed_v {
1413                            clamp_v[0] = clamp_v[0].min(point.y / point.w);
1414                            clamp_v[1] = clamp_v[1].max(point.y / point.w);
1415                        }
1416                    }
1417                }
1418            }
1419        }
1420        (clamp_u, clamp_v)
1421    };
1422    profile.boundary += profile.lap(lap_start);
1423    for piece_id in active_pieces {
1424        let piece = pieces_by_id[&piece_id];
1425        let pcurve = pcurve_for(piece, face_key)?.clone();
1426        // SAG-BOUNDED CUT-CHAIN FIDELITY (t70 split-set asynchrony class).
1427        // `sample_chain`'s uniform sampling is knot-driven, so the SAME
1428        // imprint piece gets a 16-segment polyline on one support face and a
1429        // 92-segment one on the other (the counts follow each face's pcurve
1430        // knot structure, not the curvature). A coarse chord can then sag
1431        // MILLIMETRE-scale away from the true pcurve and invade a trim
1432        // region the true curve clears — t70: piece 55's chord on face
1433        // 1:405 sagged ~3e-3 into a hole corner the true pcurve misses by
1434        // 1.3e-3, so the arrangement carved phantom micro-junctions on that
1435        // face only, the two owners' split sets diverged (whole Imprint
1436        // edge vs a 4-piece Derived chain of the same locus), and every op
1437        // failed with one-use edges. Refining every cut chain until its
1438        // chords hug the true pcurve within the arrangement's own tolerance
1439        // makes both owners' polylines faithful to the ONE shared curve, so
1440        // their junction sets agree structurally (no proximity decisions
1441        // anywhere). Escape hatch: BREP_CHAIN_SAG_REFINE=0.
1442        let (mut points, original_flags) = sag_refine_chain(
1443            &pcurve,
1444            sample_chain(&pcurve)?,
1445            sampling_bound,
1446        )?;
1447        for point in &mut points {
1448            if point.x < clamp_u[0] {
1449                point.x = clamp_u[0] + arrangement_tolerance * 10.0;
1450            } else if point.x > clamp_u[1] {
1451                point.x = clamp_u[1] - arrangement_tolerance * 10.0;
1452            }
1453            if point.y < clamp_v[0] {
1454                point.y = clamp_v[0] + arrangement_tolerance * 10.0;
1455            } else if point.y > clamp_v[1] {
1456                point.y = clamp_v[1] - arrangement_tolerance * 10.0;
1457            }
1458        }
1459        let snap_endpoint_to_domain = |point: Vec2| {
1460            let candidates = [
1461                (
1462                    (point.x - u_domain[0]).abs(),
1463                    Vec2 {
1464                        x: u_domain[0],
1465                        y: point.y,
1466                    },
1467                ),
1468                (
1469                    (point.x - u_domain[1]).abs(),
1470                    Vec2 {
1471                        x: u_domain[1],
1472                        y: point.y,
1473                    },
1474                ),
1475                (
1476                    (point.y - v_domain[0]).abs(),
1477                    Vec2 {
1478                        x: point.x,
1479                        y: v_domain[0],
1480                    },
1481                ),
1482                (
1483                    (point.y - v_domain[1]).abs(),
1484                    Vec2 {
1485                        x: point.x,
1486                        y: v_domain[1],
1487                    },
1488                ),
1489            ];
1490            let nearest = candidates
1491                .into_iter()
1492                .min_by(|first, second| first.0.total_cmp(&second.0))
1493                .unwrap();
1494            if nearest.0 <= arrangement_tolerance * 50.0 {
1495                nearest.1
1496            } else {
1497                point
1498            }
1499        };
1500        let last = points.len() - 1;
1501        // A rim bridge's ends keep their own place (see RIM BRIDGE above): the
1502        // end inside the face is a distinct vertex a chart hair from the edge.
1503        if !bridge_keys.contains(&format!("i:{}", piece.start_vertex_id)) {
1504            points[0] = snap_endpoint_to_domain(points[0]);
1505        }
1506        if !bridge_keys.contains(&format!("i:{}", piece.end_vertex_id)) {
1507            points[last] = snap_endpoint_to_domain(points[last]);
1508        }
1509        for point in &mut points {
1510            *point = rebase_point(*point);
1511        }
1512        // CUT-CHAIN PERIOD LIFT. A section's pcurve on a closed carrier is
1513        // drawn on the carrier's own period, but the boundary chains above can
1514        // sit a period away from it: an imported face whose seam edge is not
1515        // at the carrier's parameter origin carries rims split AT that origin,
1516        // so its loop hops the branch cut between coedges and the seam-hop
1517        // unwrap lays it out over [seam, seam + period] (the 2026-09-14 gear:
1518        // cone and fillet torus with seam edges at u = 1/2, boundary chains on
1519        // u ∈ [0.5, 1.5], section arcs on [0, 0.5] and [0.5, 1]). The arc a
1520        // period off never meets the band, the section never closes across the
1521        // face, the face stays one fragment and is kept or dropped whole — one
1522        // rim and the section ring then strand one-use. Every piece the imprint
1523        // attaches to a face has its midpoint inside that face's trim, so the
1524        // chain's image whose middle lies inside the band is the one the
1525        // arrangement must see; a whole-period shift is exact on a periodic
1526        // carrier. The chain is lifted only when its middle is OUTSIDE the band
1527        // and exactly one period image puts it inside, so a chain already
1528        // coherent with its band — every native face, every re-based band —
1529        // is untouched. The pcurve keeps the carrier's own period (the lift is
1530        // an arrangement coordinate, recorded on the chain for the partial-run
1531        // rescue). A lifted chain that still crosses a wall of the band crosses
1532        // the face's seam edge unsplit, and the face refuses by name. Escape
1533        // hatch `BREP_CUT_PERIOD_LIFT=0`.
1534        let mut lift = Vec2 { x: 0.0, y: 0.0 };
1535        if std::env::var("BREP_CUT_PERIOD_LIFT").as_deref() != Ok("0") {
1536            let closed = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
1537            for axis in 0..2 {
1538                let (is_closed, domain) = if axis == 0 { (closed.0, u_domain) } else { (closed.1, v_domain) };
1539                let [low, high] = band_hull[axis];
1540                let span = domain[1] - domain[0];
1541                if !is_closed || !(span > 0.0) || !(low < domain[0] || high > domain[1]) {
1542                    continue;
1543                }
1544                let coordinate = |point: &Vec2| if axis == 0 { point.x } else { point.y };
1545                let middle = coordinate(&points[points.len() / 2]);
1546                let inside = |value: f64| value > low && value < high;
1547                if inside(middle) {
1548                    continue;
1549                }
1550                let images: Vec<f64> = [-2.0, -1.0, 1.0, 2.0]
1551                    .into_iter()
1552                    .map(|periods| periods * span)
1553                    .filter(|shift| inside(middle + shift))
1554                    .collect();
1555                let [shift] = images[..] else {
1556                    continue;
1557                };
1558                let slack = arrangement_tolerance * 50.0;
1559                let (reach_low, reach_high) = points.iter().map(coordinate).fold(
1560                    (f64::INFINITY, f64::NEG_INFINITY),
1561                    |(lo, hi), value| (lo.min(value), hi.max(value)),
1562                );
1563                if reach_low + shift < low - slack || reach_high + shift > high + slack {
1564                    let [first, second] = piece.support_faces;
1565                    return Err(KernelRefusal::internal(
1566                        KernelStage::Fragment,
1567                        "fragment.face_split",
1568                        format!(
1569                            "fragment_face: section piece {piece_id} of faces {}:{} x {}:{} crosses \
1570                             the seam wall of face {}:{}'s unwrapped band ({} in [{low:.9}, {high:.9}], \
1571                             the section's period image [{:.9}, {:.9}]) without a vertex there",
1572                            first.operand,
1573                            first.face_id,
1574                            second.operand,
1575                            second.face_id,
1576                            operand,
1577                            face.id,
1578                            if axis == 0 { "u" } else { "v" },
1579                            reach_low + shift,
1580                            reach_high + shift,
1581                        ),
1582                    ));
1583                }
1584                for point in &mut points {
1585                    if axis == 0 {
1586                        point.x += shift;
1587                    } else {
1588                        point.y += shift;
1589                    }
1590                }
1591                if axis == 0 {
1592                    lift.x = shift;
1593                } else {
1594                    lift.y = shift;
1595                }
1596                if env_read!("BREP_DEBUG_FRAG")
1597                    .map(|value| value == face.id.to_string())
1598                    .unwrap_or(false)
1599                {
1600                    eprintln!(
1601                        "  period lift: face {} cut p{piece_id} {} {shift:+} into the band [{low:.6},{high:.6}]",
1602                        face.id,
1603                        if axis == 0 { "u" } else { "v" }
1604                    );
1605                }
1606            }
1607        }
1608        // Guard runs on the FINAL (clamped/snapped/rebased) coordinates — the
1609        // same frame the boundary chains were sampled into — so crossing
1610        // parity is measured exactly where the arrangement would see it.
1611        points = enforce_boundary_crossing_parity(
1612            points,
1613            &original_flags,
1614            &boundary_segments,
1615            arrangement_tolerance,
1616            env_read!("BREP_DEBUG_FRAG")
1617                .map(|value| value == face.id.to_string())
1618                .unwrap_or(false),
1619            face.id,
1620        );
1621        let start_point = index
1622            .vertex_points
1623            .get(&piece.start_vertex_id)
1624            .copied()
1625            .ok_or_else(|| KernelRefusal::internal(KernelStage::Fragment, "fragment.face_split", "fragment_face: missing imprint start vertex"))?;
1626        let end_point = index
1627            .vertex_points
1628            .get(&piece.end_vertex_id)
1629            .copied()
1630            .ok_or_else(|| KernelRefusal::internal(KernelStage::Fragment, "fragment.face_split", "fragment_face: missing imprint end vertex"))?;
1631        add_chain(
1632            points,
1633            ChainSource::Cut {
1634                piece_id,
1635                pcurve,
1636                lift,
1637                curve: trimmed_curve(&piece.curve, piece.t0, piece.t1)?,
1638                shared_ring: piece.shared_edge.filter(|_| {
1639                    piece
1640                        .curve
1641                        .evaluate(piece.t0)
1642                        .ok()
1643                        .zip(piece.curve.evaluate(piece.t1).ok())
1644                        .is_some_and(|(a, b)| a.sub(b).length() <= 1e-6)
1645                }),
1646            },
1647            format!("i:{}", piece.start_vertex_id),
1648            start_point,
1649            format!("i:{}", piece.end_vertex_id),
1650            end_point,
1651            false,
1652        )?;
1653    }
1654    drop(add_chain);
1655
1656    // POLE-GAP BRIDGE (single-loop periodic cap; corpus fixture 25 cube-pierce).
1657    // A face closed in one parameter direction can carry a DEGENERATE pole
1658    // iso-line — a whole domain-edge row of one parameter collapsing to a
1659    // single 3D point (the dome apex v2 at v=0 here). The imported trim
1660    // represents the pole with a partial arc plus a degenerate self-edge that
1661    // stop SHORT of the opposite seam foot, so the outer boundary is OPEN in
1662    // the flat UV chart at the pole: the two seam feet sit a FRACTION of a
1663    // period apart (never a whole period), so no covering-strip period-image
1664    // can ever join them — this case is structurally outside the seam-image
1665    // loops machinery above. When a section cut then splits the cap,
1666    // `arrange_segments` closes only the lens region that avoids the pole and
1667    // DROPS the seam-and-pole-spanning outside region: the cap fragments to 1
1668    // not 2, its section + outer-rim edges strand one-use (union/subtract fail
1669    // `S=1 genus=1`), while intersect keeps the lens and succeeds. Close the
1670    // boundary in-chart: pair the two dangling (odd-incidence) boundary
1671    // endpoints that are UV images of the SAME solid vertex (structural
1672    // identity via `vertex_images`, never proximity clustering), VERIFY the
1673    // straight UV connector between them is degenerate (every sample collapses
1674    // to one 3D point — a genuine pole, not a real seam that would cross
1675    // material), and add a bridge chain that REUSES an existing degenerate
1676    // pole edge as its source. The reused edge is 3D-zero-length, so the
1677    // minted coedge is s==e and exempt from the two-use rule — no new edge is
1678    // introduced (the zero-regression reuse pattern), and the outside fragment
1679    // reproduces the original cap loop's own pole/seam structure. A
1680    // cleanly-closing boundary has no odd node and is byte-identical untouched;
1681    // a genuine (non-degenerate) seam gap fails the degeneracy check and is
1682    // left alone (the safe direction). Escape hatch: BREP_POLE_GAP_BRIDGE=0.
1683    let pole_gap_closed = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
1684    if (pole_gap_closed.0 || pole_gap_closed.1)
1685        && std::env::var("BREP_POLE_GAP_BRIDGE").as_deref() != Ok("0")
1686    {
1687        // A degenerate pole iso-line only exists on a surface closed in the
1688        // seam direction; a non-periodic face never reaches this block and is
1689        // byte-identical untouched.
1690        //
1691        // `add_chain` canonicalizes shared endpoints to a bit-identical `Vec2`,
1692        // so a TIGHT epsilon groups exactly the coincident nodes and never
1693        // over-merges two distinct nearby vertices (which would corrupt the
1694        // incidence parity on a small face).
1695        let node_tol = arrangement_tolerance * 10.0;
1696        // Incidence over BOUNDARY chain endpoints only — the pole gap is a
1697        // property of the trim boundary, and cut chains anchor to it elsewhere.
1698        let mut nodes: Vec<(Vec2, usize)> = Vec::new();
1699        for chain in &chains {
1700            if !matches!(chain.source, ChainSource::Boundary { .. }) {
1701                continue;
1702            }
1703            for point in [chain.start, chain.end] {
1704                if let Some(entry) = nodes.iter_mut().find(|(q, _)| q.sub(point).length() <= node_tol)
1705                {
1706                    entry.1 += 1;
1707                } else {
1708                    nodes.push((point, 1));
1709                }
1710            }
1711        }
1712        // Odd incidence => the boundary is UV-open at that endpoint.
1713        let odd: Vec<Vec2> = nodes
1714            .iter()
1715            .filter(|(_, count)| count % 2 == 1)
1716            .map(|(point, _)| *point)
1717            .collect();
1718        // Each odd node's owning solid vertex: the canonical image lists in
1719        // `vertex_images` are keyed "b:{operand}:{vertex_id}".
1720        let vertex_of = |point: Vec2| -> Option<u64> {
1721            vertex_images.iter().find_map(|(key, images)| {
1722                images
1723                    .iter()
1724                    .any(|image| image.sub(point).length() <= node_tol)
1725                    .then(|| key.rsplit(':').next().and_then(|id| id.parse::<u64>().ok()))
1726                    .flatten()
1727            })
1728        };
1729        let mut by_vertex: HashMap<u64, Vec<Vec2>> = HashMap::default();
1730        for point in &odd {
1731            if let Some(vertex_id) = vertex_of(*point) {
1732                by_vertex.entry(vertex_id).or_default().push(*point);
1733            }
1734        }
1735        // Forward-evaluate only (the projector is a poisoned oracle; evaluate
1736        // is exact). A degenerate 3D curve collapses to a point over its span.
1737        let curve_degenerate = |curve: &NurbsCurve| -> bool {
1738            let Ok(domain) = curve.domain() else {
1739                return false;
1740            };
1741            let Ok(base) = curve.evaluate(domain[0]) else {
1742                return false;
1743            };
1744            [0.5, 1.0].iter().all(|fraction| {
1745                curve
1746                    .evaluate(domain[0] + (domain[1] - domain[0]) * fraction)
1747                    .map(|point| point.sub(base).length() <= 1e-5)
1748                    .unwrap_or(false)
1749            })
1750        };
1751        for (vertex_id, images) in by_vertex {
1752            // A pole slit unclosed in exactly one place has EXACTLY two dangling
1753            // images of its apex vertex; anything else is not this class.
1754            if images.len() != 2 {
1755                continue;
1756            }
1757            let (a, b) = (images[0], images[1]);
1758            // The straight UV connector must be a degenerate iso-line: every
1759            // sample collapses to one 3D point. This is the gate that keeps a
1760            // real seam (whose connector crosses material) from being bridged.
1761            let mut base: Option<Vec3> = None;
1762            let mut degenerate = true;
1763            for step in 0..=8 {
1764                let fraction = step as f64 / 8.0;
1765                let uv = a.add(b.sub(a).scale(fraction));
1766                match face.surface.evaluate(uv.x, uv.y) {
1767                    Ok(point) => match base {
1768                        None => base = Some(point),
1769                        Some(reference) => {
1770                            if point.sub(reference).length() > 1e-5 {
1771                                degenerate = false;
1772                                break;
1773                            }
1774                        }
1775                    },
1776                    Err(_) => {
1777                        degenerate = false;
1778                        break;
1779                    }
1780                }
1781            }
1782            if !degenerate {
1783                continue;
1784            }
1785            // Reuse an existing degenerate pole edge's source (its 3D curve is a
1786            // point, so the minted coedge is s==e and validation-exempt).
1787            let Some(source) = chains.iter().find_map(|chain| match &chain.source {
1788                ChainSource::Boundary { coedge, curve }
1789                    if chain.start.sub(chain.end).length() <= node_tol
1790                        && chain.count <= 1
1791                        && (chain.start.sub(a).length() <= node_tol
1792                            || chain.start.sub(b).length() <= node_tol)
1793                        && curve_degenerate(curve) =>
1794                {
1795                    Some(ChainSource::Boundary {
1796                        coedge: coedge.clone(),
1797                        curve: curve.clone(),
1798                    })
1799                }
1800                _ => None,
1801            }) else {
1802                continue;
1803            };
1804            let chain_index = chains.len();
1805            segments.push(Segment2 {
1806                a,
1807                b,
1808                tag: segment_tag(chain_index, 0),
1809            });
1810            chains.push(Chain {
1811                source,
1812                count: 1,
1813                start: a,
1814                end: b,
1815                segments: vec![(a, b)],
1816            });
1817            boundary_segments.push((a, b));
1818            if env_read!("BREP_DEBUG_FRAG")
1819                .map(|value| value == face.id.to_string())
1820                .unwrap_or(false)
1821            {
1822                eprintln!(
1823                    "  pole-gap bridge: face {} vertex {vertex_id} joins \
1824                     ({:.6},{:.6})->({:.6},{:.6}) along a degenerate iso-line",
1825                    face.id, a.x, a.y, b.x, b.y
1826                );
1827            }
1828        }
1829    }
1830
1831    // A cut chain that coincides with a boundary chain cannot split the
1832    // interior — the face's own edge already bounds it there. Worse, the
1833    // duplicate overlapping segments corrupt arrangement cycle extraction
1834    // and silently swallow every region that touches the overlap (seen when
1835    // endpoint polishing snaps a face/face intersection line onto a
1836    // near-coincident boundary edge of the same face). Drop such cuts
1837    // before arranging.
1838    let coincidence_tolerance = arrangement_tolerance * 50.0;
1839    let point_segment_distance = |point: Vec2, a: Vec2, b: Vec2| -> f64 {
1840        let segment = b.sub(a);
1841        let length_squared = segment.dot(segment);
1842        if length_squared <= 1e-30 {
1843            return point.sub(a).length();
1844        }
1845        let parameter = (point.sub(a).dot(segment) / length_squared).clamp(0.0, 1.0);
1846        point.sub(a.add(segment.scale(parameter))).length()
1847    };
1848    let near_chain = |point: Vec2, chain: &Chain, tolerance: f64| {
1849        chain
1850            .segments
1851            .iter()
1852            .any(|(a, b)| point_segment_distance(point, *a, *b) <= tolerance)
1853    };
1854    // A cut coincides with a boundary only if its INTERIOR follows the same
1855    // path, not merely its endpoints. A single-segment straight cut whose two
1856    // endpoints happen to land on a CURVED boundary edge's endpoints (a
1857    // cap∩cap intersection chord spanning a curved rim arc — the missing
1858    // cone-cap split behind the curved-primitive open-edge booleans) shares
1859    // only its endpoints and cuts clean across the interior; dropping it as a
1860    // duplicate loses the region it carves. So also require the segment
1861    // MIDPOINTS to lie near the boundary. The midpoint test runs at a looser
1862    // threshold than the endpoint match: a genuinely-coincident curved cut's
1863    // polyline midpoints carry their own chord sag (order 1× tolerance), which
1864    // must still count as near, while a chord spanning a curved arc departs by
1865    // the arc's sagitta (orders of magnitude larger) and is correctly kept. On
1866    // the box path both edges are straight, so two shared endpoints force the
1867    // midpoint onto the boundary and the drop is unchanged.
1868    let midpoint_tolerance = coincidence_tolerance * 8.0;
1869    // CORNER-CHORD VETO. The endpoint match above admits a cut whose endpoint
1870    // sits up to `coincidence_tolerance` (50x arr_tol) from the boundary
1871    // chain's endpoint. When a section threads through a MODEL CORNER where two
1872    // boundary edges (legs) meet at a shared vertex, the section arm on the
1873    // sliver face between them is the HYPOTENUSE of a tiny right triangle whose
1874    // short leg can be well under that slack (t634: the torus crosses a 3-face
1875    // corner of 00000327; on the sliver face the arm's start is 31x arr_tol
1876    // from the matched leg's start, and the hypotenuse midpoint is ~15x arr_tol
1877    // off the leg — both under the 50x/400x tolerances). The old test dropped
1878    // that arm as a "duplicate" of one leg, so the tiny corner region was never
1879    // carved on the sliver face while the mate operand kept the arm -> the
1880    // shared section edge went one-use (invalid genus). A genuine corner chord
1881    // is distinguishable STRUCTURALLY, not by tolerance: its endpoint coincides
1882    // (at NODE scale = the arrangement's own find_node snap) with a real,
1883    // DISTINCT boundary vertex — proof it terminates at a 0-cell of its own,
1884    // spanning two legs, rather than retracing this one edge. A true retrace
1885    // (box duplicate, cap-rim chord) has endpoints AT the matched chain's ends
1886    // (gap ~0), so the veto never triggers for it. Hatch restores the old drop.
1887    profile.cuts += profile.lap(lap_start);
1888    let corner_veto_enabled = std::env::var("BREP_DUP_DROP_CORNER_VETO")
1889        .map(|value| value != "0")
1890        .unwrap_or(true);
1891    let node_tolerance = arrangement_tolerance;
1892    let terminates_at_distinct_boundary_vertex = |cut_end: Vec2, matched_end: Vec2| -> bool {
1893        cut_end.sub(matched_end).length() > node_tolerance
1894            && chains.iter().any(|other| {
1895                matches!(other.source, ChainSource::Boundary { .. })
1896                    && (other.start.sub(cut_end).length() <= node_tolerance
1897                        || other.end.sub(cut_end).length() <= node_tolerance)
1898            })
1899    };
1900    let debug_drop = env_read!("BREP_DEBUG_FRAG")
1901        .map(|value| value == face.id.to_string())
1902        .unwrap_or(false);
1903    // MICRO-BOUNDARY SKIP (t316). A boundary chain SHORTER than the coincidence
1904    // tolerance cannot serve as a duplication reference: its two endpoints are
1905    // indistinguishable at this tolerance, so the orientation-agnostic endpoint
1906    // match collapses and a cut that merely touches ONE of its ends spuriously
1907    // "matches" BOTH. That wrongly drops a junction-bridge micro-cut spanning a
1908    // weld-scale vertex cluster (t316: cut p38, the ~0.004mm bridge from a
1909    // section terminus C to the plate-rim node C', gets dropped against e99 — a
1910    // ~0.008mm rim segment shorter than the 0.07mm coincidence tolerance —
1911    // stranding the whole b-poke bottom-cap sliver one-use). The genuine target
1912    // (the tangent-wall cap section, coincident with a full-length box edge ≫
1913    // tolerance) is untouched. Independent of the corner-chord veto below: this
1914    // guard disqualifies a sub-tolerance BOUNDARY as a reference, that one spares
1915    // a corner-spanning CUT. Hatch `BREP_DUP_CUT_MICRO_BOUNDARY=0` restores the
1916    // old, length-agnostic reference test.
1917    let micro_boundary_skip = std::env::var("BREP_DUP_CUT_MICRO_BOUNDARY").as_deref() != Ok("0");
1918    let boundary_extent = |boundary: &Chain| -> f64 {
1919        boundary
1920            .segments
1921            .iter()
1922            .map(|(a, b)| b.sub(*a).length())
1923            .sum()
1924    };
1925    // POLE-COLLAPSED ENDS. A pole is a whole row of the chart at one 3D
1926    // point, so two chain ends on its degenerate boundary chain are one point
1927    // however far apart their u reads, and near the pole a chart distance says
1928    // nothing about the 3D one. The revolve_pole union's G2 end cap (a 93 deg
1929    // sector with its apex on the axis) is cut by the wedge's two side planes
1930    // along its own radius edges: the sections are those edges' sub-arcs to
1931    // 1e-15, ending 3.2e-6 short of the apex. Their pcurves follow u = 1 and
1932    // u = 0 and then swing to one u = 0.443 at the pole; evaluated on the cap
1933    // a chain stands 7.4e-4 off its radius at v = 0.99976. The endpoint test
1934    // read a gap of 0.557 and kept both cuts, and the arrangement carved two
1935    // slivers, each bounded by a radius edge and its own weld copy. One was
1936    // kept in the union and the coplanar merge refused its genus; the
1937    // intersection refused with one-use edges. So when a chain end and a
1938    // boundary end both lie on one degenerate boundary chain they coincide,
1939    // and the cut is held to the boundary in 3D, section curve against edge
1940    // curve, within the coincidence tolerance carried along the boundary by
1941    // its own stretch (3D length over chart length). Escape hatch
1942    // `BREP_DUP_DROP_POLE=0`.
1943    let pole_ends = std::env::var("BREP_DUP_DROP_POLE").as_deref() != Ok("0");
1944    let degenerate_chains: Vec<usize> = chains
1945        .iter()
1946        .enumerate()
1947        .filter(|(_, chain)| match &chain.source {
1948            ChainSource::Boundary { coedge, .. } => solid
1949                .edges
1950                .iter()
1951                .any(|edge| edge.id == coedge.edge_id && edge.degenerate),
1952            ChainSource::Cut { .. } => false,
1953        })
1954        .map(|(index, _)| index)
1955        .collect();
1956    let on_one_pole = |a: Vec2, b: Vec2| -> bool {
1957        degenerate_chains.iter().any(|&pole| {
1958            near_chain(a, &chains[pole], coincidence_tolerance)
1959                && near_chain(b, &chains[pole], coincidence_tolerance)
1960        })
1961    };
1962    let coincident_in_space = |cut: &Chain, boundary: &Chain, weld_scale: bool| -> bool {
1963        let ChainSource::Boundary { curve, coedge } = &boundary.source else {
1964            return false;
1965        };
1966        let ChainSource::Cut { curve: section, piece_id, .. } = &cut.source else {
1967            return false;
1968        };
1969        let Ok([start, end]) = section.domain() else {
1970            return false;
1971        };
1972        let mut chart_length = 0.0;
1973        let mut space_length = 0.0;
1974        for (a, b) in &boundary.segments {
1975            let (Ok(pa), Ok(pb)) = (face.surface.evaluate(a.x, a.y), face.surface.evaluate(b.x, b.y)) else {
1976                return false;
1977            };
1978            chart_length += b.sub(*a).length();
1979            space_length += pb.sub(pa).length();
1980        }
1981        if chart_length <= 0.0 || space_length <= 0.0 {
1982            return false;
1983        }
1984        let copy_boundary = index.pieces_by_id.get(piece_id).is_some_and(|piece| {
1985            boundary_has_cosurface_partner(solid, face_key, coedge.edge_id, piece, index)
1986        });
1987        let bound = if weld_scale {
1988            if copy_boundary { crate::tolerance::WELD_FLOOR } else { 1e-7 }
1989        } else {
1990            coincidence_tolerance * space_length / chart_length
1991        };
1992        let stations = cut.segments.len().max(16);
1993        (0..=stations).all(|index| {
1994            section
1995                .evaluate(start + (end - start) * index as f64 / stations as f64)
1996                .ok()
1997                .and_then(|at| crate::project_point_to_curve(curve, at).ok())
1998                .is_some_and(|projection| projection.distance <= bound)
1999        })
2000    };
2001    let mut dropped_chains = HashSet::default();
2002    for (index, chain) in chains.iter().enumerate() {
2003        if !matches!(chain.source, ChainSource::Cut { .. }) {
2004            continue;
2005        }
2006        let mut matched_boundary: Option<usize> = None;
2007        for (boundary_index, boundary) in chains.iter().enumerate() {
2008            if !matches!(boundary.source, ChainSource::Boundary { .. }) {
2009                continue;
2010            }
2011            // t316: a sub-tolerance micro-boundary cannot be a duplication
2012            // reference — its endpoints collapse at this tolerance, so a cut that
2013            // merely touches one end spuriously matches both. Skip it as a
2014            // candidate (never selectable as `matched_boundary`).
2015            if micro_boundary_skip && boundary_extent(boundary) <= coincidence_tolerance {
2016                continue;
2017            }
2018            let forward = boundary.start.sub(chain.start).length() <= coincidence_tolerance
2019                && boundary.end.sub(chain.end).length() <= coincidence_tolerance;
2020            let reversed = boundary.start.sub(chain.end).length() <= coincidence_tolerance
2021                && boundary.end.sub(chain.start).length() <= coincidence_tolerance;
2022            let same_end = |a: Vec2, b: Vec2| a.sub(b).length() <= coincidence_tolerance || on_one_pole(a, b);
2023            let (forward, reversed, at_pole) = if forward || reversed || !pole_ends || degenerate_chains.is_empty() {
2024                (forward, reversed, false)
2025            } else {
2026                let forward = same_end(boundary.start, chain.start) && same_end(boundary.end, chain.end);
2027                let reversed = same_end(boundary.start, chain.end) && same_end(boundary.end, chain.start);
2028                (forward, reversed, true)
2029            };
2030            if !(forward || reversed) {
2031                continue;
2032            }
2033            let interior_coincident = if at_pole {
2034                coincident_in_space(chain, boundary, false)
2035            } else {
2036                chain.segments.iter().all(|(a, b)| {
2037                    near_chain(*a, boundary, coincidence_tolerance)
2038                        && near_chain(*b, boundary, coincidence_tolerance)
2039                        && near_chain(a.add(*b).scale(0.5), boundary, midpoint_tolerance)
2040                })
2041            };
2042            if !interior_coincident {
2043                continue;
2044            }
2045            // UV proximity only nominates a duplicate. A real cap lens can
2046            // be narrower than that chart-space search band while its two
2047            // arcs are distinct in 3D. Discard the cut only when the section
2048            // follows the boundary within model identity, not the repair band.
2049            // Endpoint anchoring can turn a very short cut and boundary into
2050            // the SAME chart segment even when their pre-anchor 3D curves
2051            // differ. Such identical constraints cannot enclose a region;
2052            // keeping both would corrupt arrangement cycle extraction.
2053            let identical_segment = chain.segments.len() == 1
2054                && boundary.segments.len() == 1
2055                && ((chain.start.sub(boundary.start).length() <= node_tolerance
2056                    && chain.end.sub(boundary.end).length() <= node_tolerance)
2057                    || (chain.start.sub(boundary.end).length() <= node_tolerance
2058                        && chain.end.sub(boundary.start).length() <= node_tolerance));
2059            if !identical_segment
2060                && std::env::var("BREP_DUP_CUT_GEOMETRY").as_deref() != Ok("0")
2061                && !coincident_in_space(chain, boundary, true)
2062            {
2063                continue;
2064            }
2065            if corner_veto_enabled {
2066                // Pair each cut endpoint with the boundary endpoint it matched.
2067                let (bnd_for_start, bnd_for_end) = if forward {
2068                    (boundary.start, boundary.end)
2069                } else {
2070                    (boundary.end, boundary.start)
2071                };
2072                if terminates_at_distinct_boundary_vertex(chain.start, bnd_for_start)
2073                    || terminates_at_distinct_boundary_vertex(chain.end, bnd_for_end)
2074                {
2075                    continue;
2076                }
2077            }
2078            matched_boundary = Some(boundary_index);
2079            break;
2080        }
2081        if let Some(boundary_index) = matched_boundary {
2082            if debug_drop {
2083                let boundary = &chains[boundary_index];
2084                eprintln!(
2085                    "  drop cut chain {} as duplicate of boundary chain {} (endpoint gaps {:.3e}/{:.3e})",
2086                    index,
2087                    boundary_index,
2088                    boundary.start.sub(chain.start).length(),
2089                    boundary.end.sub(chain.end).length(),
2090                );
2091            }
2092            dropped_chains.insert(index);
2093        }
2094    }
2095    if !dropped_chains.is_empty() {
2096        segments.retain(|segment| {
2097            tag_chain(&segment.tag)
2098                .map(|chain| !dropped_chains.contains(&(chain as usize)))
2099                .unwrap_or(true)
2100        });
2101    }
2102
2103    let debug = env_read!("BREP_DEBUG_FRAG")
2104        .map(|value| value == face.id.to_string())
2105        .unwrap_or(false);
2106    if debug {
2107        eprintln!(
2108            "fragment_face {}: domain u={:?} v={:?} arr_tol={:.3e} chains={} segments={}",
2109            face.id,
2110            u_domain,
2111            v_domain,
2112            arrangement_tolerance,
2113            chains.len(),
2114            segments.len()
2115        );
2116        for (index, chain) in chains.iter().enumerate() {
2117            let kind = match &chain.source {
2118                ChainSource::Boundary { coedge, .. } => format!("boundary e{}", coedge.edge_id),
2119                ChainSource::Cut { piece_id, .. } => format!("cut p{piece_id}"),
2120            };
2121            eprintln!(
2122                "  chain {index} {kind} count={} start=({:.9},{:.9}) end=({:.9},{:.9})",
2123                chain.count, chain.start.x, chain.start.y, chain.end.x, chain.end.y
2124            );
2125        }
2126    }
2127    // TRIPLE-POINT CUT-CHAIN BRIDGE (imported-geometry corner rescue).
2128    // Two SSI cut curves that meet at a triple point (two faces of one operand
2129    // both crossing a single face of the other) can be marched to endpoints that
2130    // DISAGREE by more than arr_tol when the operand's shared edge carries an
2131    // imported edge<->vertex gap — the imprint then mints TWO vertices for the
2132    // one corner, so the cut chain is BROKEN there. A broken cut cannot separate
2133    // the domain: the arrangement returns one region covering the whole face, the
2134    // face is classified by a single test point and wrongly dropped, and its
2135    // neighbours' shared SSI edges are stranded one-use (non-integral genus).
2136    // Reconnect a pair of cut-chain endpoints that BOTH dangle in the interior
2137    // (each neither on a domain boundary nor meeting any other chain endpoint)
2138    // and whose chains are EACH anchored at their other end, by snapping them to
2139    // their midpoint. Gated on the dangling-anchored-pair precondition, which
2140    // only a broken cut produces, so a face whose cuts already connect (every
2141    // currently-succeeding fragmentation) is never touched.
2142    if chains.iter().any(|c| matches!(c.source, ChainSource::Cut { .. })) {
2143        let endpoints: Vec<(usize, bool, Vec2)> = chains
2144            .iter()
2145            .enumerate()
2146            .flat_map(|(i, c)| [(i, false, c.start), (i, true, c.end)])
2147            .collect();
2148        let anchored = |chain: usize, point: Vec2| -> bool {
2149            boundary_side(point).is_some()
2150                || endpoints.iter().any(|(other_chain, _, other_point)| {
2151                    *other_chain != chain
2152                        && other_point.sub(point).length() <= junction_radius
2153                })
2154        };
2155        // Interior cut endpoints with an anchored far end are the loose ends of
2156        // an almost-through cut. Only these are bridge candidates.
2157        //
2158        // OVERSHOOT-STUB EXCLUSION (offset-shell triple junctions). A pairwise
2159        // section between offset carriers is clipped to the offset IMAGE of the
2160        // source trims, which can extend PAST the true junction with a third
2161        // carrier's section instead of stopping at it (O.S14: the plane×cylinder
2162        // line runs to the source cone∩cylinder circle image at y=100/13 while
2163        // the plane×cone section crosses it ~0.3 earlier at the real offset
2164        // junction). Such a chain is already CONNECTED into the cut graph:
2165        // arrange_segments splits both chains at the crossing node and its
2166        // degree-1 pruning drops the overshoot stub beyond it, and build_loop
2167        // emits the surviving partial runs as Derived sub-curves. The stub's
2168        // dangling endpoint is therefore NOT a broken-junction loose end —
2169        // bridging it would drag a chain end to a midpoint off the section
2170        // (a wrong patch) and the 3D gate would refuse a face the arrangement
2171        // handles exactly. Exclude an endpoint from bridge candidacy when its
2172        // chain intersects another CUT chain at a point interior to the chain
2173        // (beyond the junction radius of both its endpoints — an intersection
2174        // AT the far anchor is the ordinary shared-junction meeting and must
2175        // keep the endpoint eligible, which preserves the imported-geometry
2176        // broken-cut rescue: those chains stop SHORT of each other and have no
2177        // crossing at all).
2178        let cut_chain_has_interior_crossing = |i: usize| -> bool {
2179            let chain = &chains[i];
2180            chains.iter().enumerate().any(|(j, other)| {
2181                // A boundary-duplicate chain was dropped from the arrangement's
2182                // segment set; a crossing with it would not materialize a node.
2183                if j == i
2184                    || dropped_chains.contains(&j)
2185                    || !matches!(other.source, ChainSource::Cut { .. })
2186                {
2187                    return false;
2188                }
2189                chain.segments.iter().any(|&(a0, a1)| {
2190                    other.segments.iter().any(|&(b0, b1)| {
2191                        let Some([t, _]) = crate::arrangement::segment_intersection(
2192                            a0,
2193                            a1,
2194                            b0,
2195                            b1,
2196                            arrangement_tolerance,
2197                        ) else {
2198                            return false;
2199                        };
2200                        let crossing = a0.add(a1.sub(a0).scale(t));
2201                        crossing.sub(chain.start).length() > junction_radius
2202                            && crossing.sub(chain.end).length() > junction_radius
2203                    })
2204                })
2205            })
2206        };
2207        let boundary_anchor = bridge_boundary_t_junction_anchor_enabled();
2208        let lies_on_another_cut_run = |i: usize, point: Vec2| -> bool {
2209            bridge_t_junction_anchor_enabled()
2210                && chains.iter().enumerate().any(|(j, other)| {
2211                    j != i
2212                        && !dropped_chains.contains(&j)
2213                        && (boundary_anchor || matches!(other.source, ChainSource::Cut { .. }))
2214                        && other.segments.iter().any(|&(a, b)| {
2215                            let along = b.sub(a);
2216                            let span = along.dot(along);
2217                            let t = if span > 0.0 {
2218                                (point.sub(a).dot(along) / span).clamp(0.0, 1.0)
2219                            } else {
2220                                0.0
2221                            };
2222                            a.add(along.scale(t)).sub(point).length() <= arrangement_tolerance
2223                        })
2224                })
2225        };
2226        let mut loose: Vec<(usize, bool, Vec2)> = Vec::new();
2227        for (i, c) in chains.iter().enumerate() {
2228            if !matches!(c.source, ChainSource::Cut { .. }) {
2229                continue;
2230            }
2231            for (is_end, this, other) in [(false, c.start, c.end), (true, c.end, c.start)] {
2232                if !anchored(i, this) && anchored(i, other) {
2233                    if lies_on_another_cut_run(i, this) {
2234                        if debug {
2235                            eprintln!("  cut chain {i} end {} is a T-junction on another chain's run", if is_end { "end" } else { "start" });
2236                        }
2237                        continue;
2238                    }
2239                    if cut_chain_has_interior_crossing(i) {
2240                        if debug {
2241                            eprintln!(
2242                                "  cut chain {i} loose {} excluded from junction bridging — \
2243                                 the chain crosses another cut chain mid-run; arrangement \
2244                                 pruning owns the overshoot stub",
2245                                if is_end { "end" } else { "start" },
2246                            );
2247                        }
2248                        continue;
2249                    }
2250                    loose.push((i, is_end, this));
2251                }
2252            }
2253        }
2254        // A triple-point disagreement on imported geometry runs a few 1e-4 (the
2255        // edge<->vertex gap), well under a percent of the face diagonal; bound the
2256        // bridge span so two unrelated loose cuts across a face are never joined.
2257        let bridge_band = (diagonal * 0.02).max(junction_radius * 4.0);
2258        if debug && !loose.is_empty() {
2259            eprintln!("  junction_radius={junction_radius:.6} bridge_band={bridge_band:.6} loose={loose:?}");
2260        }
2261        let skip_crossing_pairs = bridge_skip_crossing_pairs_enabled();
2262        let chains_cross = |i: usize, j: usize| -> bool {
2263            chains[i].segments.iter().any(|&(a0, a1)| {
2264                chains[j].segments.iter().any(|&(b0, b1)| {
2265                    crate::arrangement::segment_intersection(a0, a1, b0, b1, arrangement_tolerance).is_some()
2266                })
2267            })
2268        };
2269        let mut pairs: Vec<(f64, usize, usize)> = Vec::new();
2270        for a in 0..loose.len() {
2271            for b in (a + 1)..loose.len() {
2272                if loose[a].0 == loose[b].0 {
2273                    continue; // never bridge a chain to itself
2274                }
2275                if skip_crossing_pairs && chains_cross(loose[a].0, loose[b].0) {
2276                    if debug {
2277                        eprintln!("  loose ends of chains {} and {} are not bridged: the chains cross", loose[a].0, loose[b].0);
2278                    }
2279                    continue;
2280                }
2281                let gap = loose[a].2.sub(loose[b].2).length();
2282                if gap <= bridge_band {
2283                    pairs.push((gap, a, b));
2284                }
2285            }
2286        }
2287        pairs.sort_by(|x, y| x.0.total_cmp(&y.0));
2288        // 3D-DISTANCE CORROBORATION (case 08:10-genus, secondary defect): the
2289        // 2% UV band is metric-blind — on a big-carrier face (full-sphere
2290        // domain [0,1]² spanning hundreds of mm) it silently bridged loose
2291        // ends 8-14 mm apart in 3D, manufacturing UV-closed but 3D-OPEN loops
2292        // ("loop open between coedges" at assembly). A legitimate bridge heals
2293        // a triple-point disagreement (imported edge<->vertex gap, a small
2294        // fraction of the face scale); an impossible one spans a real missing
2295        // piece. Corroborate every candidate pair in 3D: evaluate the surface
2296        // at both (wrapped-back) loose ends and REFUSE — loudly, as a
2297        // missing-piece error — when their 3D gap exceeds 1% of the face's 3D
2298        // diameter (estimated over a coarse full-domain grid; on a small trim
2299        // of a big carrier this overstates the diameter, i.e. errs LENIENT —
2300        // the safe direction). Any probe evaluation failure falls back to the
2301        // old silent bridging (a diagnostic must not fail the operation).
2302        // Escape hatch: BREP_BRIDGE_3D_GATE=0 restores unconditional bridging.
2303        let bridge_3d_gate = std::env::var("BREP_BRIDGE_3D_GATE").as_deref() != Ok("0");
2304        let face_diameter3 = if bridge_3d_gate && !pairs.is_empty() {
2305            let mut samples: Vec<Vec3> = Vec::new();
2306            'grid: for i in 0..3 {
2307                for j in 0..3 {
2308                    let u = u_domain[0] + (u_domain[1] - u_domain[0]) * i as f64 / 2.0;
2309                    let v = v_domain[0] + (v_domain[1] - v_domain[0]) * j as f64 / 2.0;
2310                    match face.surface.evaluate(u, v) {
2311                        Ok(point) => samples.push(point),
2312                        Err(_) => {
2313                            samples.clear();
2314                            break 'grid;
2315                        }
2316                    }
2317                }
2318            }
2319            let mut diameter = 0.0f64;
2320            for (index, a) in samples.iter().enumerate() {
2321                for b in samples.iter().skip(index + 1) {
2322                    diameter = diameter.max(a.sub(*b).length());
2323                }
2324            }
2325            (diameter > 0.0).then_some(diameter)
2326        } else {
2327            None
2328        };
2329        let mut consumed = vec![false; loose.len()];
2330        let mut bridged = 0usize;
2331        for (gap, a, b) in pairs {
2332            if consumed[a] || consumed[b] {
2333                continue;
2334            }
2335            consumed[a] = true;
2336            consumed[b] = true;
2337            let (chain_a, _, point_a) = loose[a];
2338            let (chain_b, _, point_b) = loose[b];
2339            if let Some(diameter) = face_diameter3 {
2340                let wrapped_a = wrap_back(point_a);
2341                let wrapped_b = wrap_back(point_b);
2342                if let (Ok(position_a), Ok(position_b)) = (
2343                    face.surface.evaluate(wrapped_a.x, wrapped_a.y),
2344                    face.surface.evaluate(wrapped_b.x, wrapped_b.y),
2345                ) {
2346                    let gap3 = position_a.sub(position_b).length();
2347                    let limit = 0.01 * diameter;
2348                    if gap3 > limit {
2349                        return Err(KernelRefusal::internal(KernelStage::Fragment, "fragment.face_split", format!(
2350                            "fragment_face {}: cut junction bridge refused — 3D gap \
2351                             {gap3:.4} exceeds {limit:.4} (1% of face diameter \
2352                             {diameter:.4}) across uv gap {gap:.6}; a section piece \
2353                             is missing between chains {chain_a} and {chain_b}",
2354                            face.id
2355                        )));
2356                    }
2357                }
2358            }
2359            let midpoint = point_a.add(point_b).scale(0.5);
2360            if debug {
2361                eprintln!(
2362                    "  bridging chain {chain_a} at ({:.9},{:.9}) and chain {chain_b} at ({:.9},{:.9}), uv gap {gap:.6}",
2363                    point_a.x, point_a.y, point_b.x, point_b.y
2364                );
2365            }
2366            for (chain, old) in [(chain_a, point_a), (chain_b, point_b)] {
2367                if chains[chain].start.sub(old).length() <= 1e-15 {
2368                    chains[chain].start = midpoint;
2369                }
2370                if chains[chain].end.sub(old).length() <= 1e-15 {
2371                    chains[chain].end = midpoint;
2372                }
2373                for seg in &mut chains[chain].segments {
2374                    if seg.0.sub(old).length() <= 1e-15 {
2375                        seg.0 = midpoint;
2376                    }
2377                    if seg.1.sub(old).length() <= 1e-15 {
2378                        seg.1 = midpoint;
2379                    }
2380                }
2381                for segment in segments.iter_mut() {
2382                    if tag_chain(&segment.tag) != Some(chain as u64) {
2383                        continue;
2384                    }
2385                    if segment.a.sub(old).length() <= 1e-15 {
2386                        segment.a = midpoint;
2387                    }
2388                    if segment.b.sub(old).length() <= 1e-15 {
2389                        segment.b = midpoint;
2390                    }
2391                }
2392            }
2393            bridged += 1;
2394        }
2395        if debug && bridged > 0 {
2396            eprintln!("  bridged {bridged} broken cut junction(s)");
2397        }
2398    }
2399    // GRAZE-BAND COMMON-BLOCK — near-tangent hook absorption (member t548,
2400    // fixture 28). fragment_face runs on the ALREADY edge-split solid, so
2401    // every imprint-minted section×boundary crossing is already a chain endpoint
2402    // by construction. A near-tangent SSI section ending at a shared junction
2403    // vertex can HOOK in its last span: its sampled pcurve overshoots and crosses
2404    // the boundary edge a graze before reaching the shared endpoint (the crossing
2405    // add_edge_split already REFUSED as sub-weld, so the imprint minted only the
2406    // shared vertex). arrange_segments, seeing the linearised hook, re-invents that
2407    // crossing as a distinct INTERIOR point Q, splits the boundary there, and
2408    // spawns a sub-band micro-stub that strands one-use at assembly (t548's
2409    // A→B→C needle). Absorb it deterministically: when a Cut chain and a Boundary
2410    // chain SHARE an endpoint node P, and the cut's polyline crosses that boundary
2411    // at an interior point Q within a MEASURED band of P (3D via surface.evaluate;
2412    // ceiling = the residual-merge sep_cap family, never grown, never the poisoned
2413    // projector), CLIP the cut's hooked tail — drop the samples between the
2414    // crossing and P and run straight into P. The cut's SOURCE curve is untouched,
2415    // so the complete-run path in build_loop still emits the full section edge to
2416    // P; only the spurious arrangement self-crossing is removed. This is the
2417    // arrangement sibling of BREP_SAG_CROSSING_GUARD ("sag refinement may REMOVE a
2418    // crossing but never ADD one"): the arrangement may not invent a boundary
2419    // crossing the imprint did not mint. The structural gate (shared endpoint +
2420    // strictly-interior sub-band crossing) keeps it off legitimate transversals —
2421    // an interior crossing BEYOND the band bails the chain untouched. Generalising
2422    // that gate to Cut×Cut pairs over a measured junction blob was tried and
2423    // falsified as a structural no-op: a crossing in the junction-ADJACENT first
2424    // segment leaves keep_lo=1 reconnecting an identical polyline, and two arms
2425    // that DIVERGE from a shared junction cannot be uncrossed by clipping — this
2426    // absorbs a multi-segment overshoot-and-return hook only. Escape hatch
2427    // BREP_GRAZE_HOOK_CLIP=0.
2428    if std::env::var("BREP_GRAZE_HOOK_CLIP").as_deref() != Ok("0") {
2429        let raw_extent = if solid.vertices.is_empty() {
2430            0.0
2431        } else {
2432            let mut lo = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
2433            let mut hi = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
2434            for v in &solid.vertices {
2435                lo.x = lo.x.min(v.point.x);
2436                lo.y = lo.y.min(v.point.y);
2437                lo.z = lo.z.min(v.point.z);
2438                hi.x = hi.x.max(v.point.x);
2439                hi.y = hi.y.max(v.point.y);
2440                hi.z = hi.z.max(v.point.z);
2441            }
2442            hi.sub(lo).length()
2443        };
2444        // Mirror imprint::residual_merge_bands' sep_cap (the measured junction-radius
2445        // family, 1.5e-3·extent, floored by the achieved weld band) — the largest
2446        // near-tangent hook a shared-junction section can plausibly span.
2447        let band = 2.0 * 1e-5 * raw_extent.max(1.0);
2448        let sep_cap = (1.5e-3 * raw_extent).max(band);
2449        let graze_debug = debug || std::env::var("BREP_DEBUG_GRAZE").as_deref() == Ok("1");
2450
2451        let mut plans: Vec<(usize, Vec<(Vec2, Vec2)>, f64)> = Vec::new();
2452        for ci in 0..chains.len() {
2453            if !matches!(chains[ci].source, ChainSource::Cut { .. }) {
2454                continue;
2455            }
2456            // A chain already dropped as a boundary duplicate has had its flat
2457            // `segments` removed; never re-materialise it below.
2458            if dropped_chains.contains(&ci) {
2459                continue;
2460            }
2461            if chains[ci].segments.is_empty() {
2462                continue;
2463            }
2464            let mut lo_cut: Option<usize> = None; // clip a start hook after this seg
2465            let mut hi_cut: Option<usize> = None; // clip an end hook from this seg on
2466            let mut measured = 0.0f64;
2467            let mut bail = false;
2468            for &at_end in &[false, true] {
2469                let p = if at_end {
2470                    chains[ci].end
2471                } else {
2472                    chains[ci].start
2473                };
2474                let Ok(p3) = face.surface.evaluate(p.x, p.y) else {
2475                    continue;
2476                };
2477                for bi in 0..chains.len() {
2478                    if bi == ci || !matches!(chains[bi].source, ChainSource::Boundary { .. }) {
2479                        continue;
2480                    }
2481                    let shares = chains[bi].start.sub(p).length() <= node_tolerance
2482                        || chains[bi].end.sub(p).length() <= node_tolerance;
2483                    if !shares {
2484                        continue;
2485                    }
2486                    for si in 0..chains[ci].segments.len() {
2487                        let (sa, sb) = chains[ci].segments[si];
2488                        for &(ba, bb) in &chains[bi].segments {
2489                            let Some([t1, t2]) = crate::arrangement::segment_intersection(
2490                                sa,
2491                                sb,
2492                                ba,
2493                                bb,
2494                                arrangement_tolerance,
2495                            ) else {
2496                                continue;
2497                            };
2498                            // Strictly interior on BOTH segments: not the shared-node
2499                            // touch (t≈0/1) and not a boundary-endpoint (imprint-minted)
2500                            // crossing (t2≈0/1) — an arrangement-invented self-crossing.
2501                            if !(t1 > 1e-3 && t1 < 1.0 - 1e-3 && t2 > 1e-3 && t2 < 1.0 - 1e-3) {
2502                                continue;
2503                            }
2504                            let q = Vec2 {
2505                                x: sa.x + (sb.x - sa.x) * t1,
2506                                y: sa.y + (sb.y - sa.y) * t1,
2507                            };
2508                            let Ok(q3) = face.surface.evaluate(q.x, q.y) else {
2509                                continue;
2510                            };
2511                            let dist = q3.sub(p3).length();
2512                            if dist > sep_cap {
2513                                // A genuine transversal far from the shared vertex —
2514                                // preserve current behaviour, touch nothing.
2515                                bail = true;
2516                                break;
2517                            }
2518                            measured = measured.max(dist);
2519                            if at_end {
2520                                hi_cut = Some(hi_cut.map_or(si, |m| m.min(si)));
2521                            } else {
2522                                lo_cut = Some(lo_cut.map_or(si, |m| m.max(si)));
2523                            }
2524                        }
2525                        if bail {
2526                            break;
2527                        }
2528                    }
2529                    if bail {
2530                        break;
2531                    }
2532                }
2533                if bail {
2534                    break;
2535                }
2536            }
2537            if bail || (lo_cut.is_none() && hi_cut.is_none()) {
2538                continue;
2539            }
2540            let segs = &chains[ci].segments;
2541            let keep_lo = lo_cut.map(|m| m + 1).unwrap_or(0);
2542            let keep_hi = hi_cut.unwrap_or(segs.len());
2543            if keep_lo >= keep_hi {
2544                continue;
2545            }
2546            let mut points: Vec<Vec2> = Vec::with_capacity(keep_hi - keep_lo + 3);
2547            if lo_cut.is_some() {
2548                points.push(chains[ci].start);
2549            }
2550            points.push(segs[keep_lo].0);
2551            for si in keep_lo..keep_hi {
2552                points.push(segs[si].1);
2553            }
2554            if hi_cut.is_some() {
2555                points.push(chains[ci].end);
2556            }
2557            let new_segments: Vec<(Vec2, Vec2)> = points
2558                .windows(2)
2559                .filter(|w| w[0].sub(w[1]).length() > 0.0)
2560                .map(|w| (w[0], w[1]))
2561                .collect();
2562            if new_segments.is_empty() {
2563                continue;
2564            }
2565            if graze_debug {
2566                eprintln!(
2567                    "graze-hook-clip: face {} cut chain {} measured_band={:.3e} sep_cap={:.3e} \
2568                     segs {} -> {} (lo_cut={:?} hi_cut={:?})",
2569                    face.id,
2570                    ci,
2571                    measured,
2572                    sep_cap,
2573                    chains[ci].segments.len(),
2574                    new_segments.len(),
2575                    lo_cut,
2576                    hi_cut
2577                );
2578            }
2579            plans.push((ci, new_segments, measured));
2580        }
2581        for (ci, new_segments, _) in plans {
2582            chains[ci].count = new_segments.len();
2583            chains[ci].segments = new_segments.clone();
2584            segments.retain(|s| tag_chain(&s.tag) != Some(ci as u64));
2585            for (segment_index, (a, b)) in new_segments.into_iter().enumerate() {
2586                segments.push(Segment2 {
2587                    a,
2588                    b,
2589                    tag: segment_tag(ci, segment_index),
2590                });
2591            }
2592        }
2593    }
2594    profile.dedup_bridge += profile.lap(lap_start);
2595    profile.arranged_faces += 1;
2596    let regions = arrange_segments(&segments, arrangement_tolerance).or_refuse(KernelStage::Fragment, "arrange_segments")?;
2597    profile.arrange += profile.lap(lap_start);
2598    profile.region_faces += 1;
2599    // Orient every rebuilt loop the way the SOURCE face's own loops are
2600    // oriented, rather than assuming `same_sense` predicts it. The two agree on
2601    // every face whose chart is right-handed against its normal, so this is a
2602    // no-op there; it differs only on a face the old assumption got wrong, and
2603    // getting it wrong reverses that face's whole fragment. Falls back to the
2604    // old rule whenever the source winding cannot be read (see the helper).
2605    // Escape hatch for A/B against the old behaviour.
2606    let source_winding = if std::env::var("BREP_FRAGMENT_SOURCE_WINDING").as_deref() == Ok("0") {
2607        None
2608    } else {
2609        source_loop_winding_negative(face)?
2610    };
2611    let reverse_loops = source_winding.unwrap_or(!face.same_sense);
2612    if debug && reverse_loops != !face.same_sense {
2613        eprintln!(
2614            "  face {}: source loop winds {} against same_sense={} — orienting fragments to the source",
2615            face.id,
2616            if reverse_loops { "CW" } else { "CCW" },
2617            face.same_sense
2618        );
2619    }
2620    let mut fragments = Vec::new();
2621    for mut region in regions {
2622        super::loops::detach_touched_closed_boundaries(&mut region, &chains, arrangement_tolerance)?;
2623        let order = FragmentOrder {
2624            area: region.outer_area,
2625            key: super::loops::region_topology_key(&region.outer, &region.holes),
2626        };
2627        let mut region_lap = profile.enabled.then(Instant::now);
2628        let arranged: Vec<Vec2> = interior_points(&region.outer, &region.holes, region.area, 3);
2629        let interior_ms = profile.lap(&mut region_lap).as_secs_f64() * 1000.0;
2630        let candidates: Vec<Vec2> = arranged.iter().copied().map(wrap_back).collect();
2631        let Some(&test_uv) = candidates.first() else {
2632            if debug {
2633                eprintln!("  region area={:.6e}: no interior point", region.area);
2634            }
2635            continue;
2636        };
2637        let mut containment = parameter_point_in_face(face, test_uv, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")?;
2638        // TRIM-FRAME CONTAINMENT. A face whose own pcurves run past its
2639        // carrier's domain (helmet Face_15: its trim spans u in
2640        // [-0.178, 0.298] on a carrier closed in u, drawn without a hop) is
2641        // arranged in that frame, and the region's point is on the face there.
2642        // Its wrapped image (u = 0.970 for -0.030) lies on no trim polygon,
2643        // and containment has no period image to try for a trim that never
2644        // hops the seam, so it reads Outside and the region was dropped: the
2645        // face's y >= 0 piece in `20_helmet_merge_stage` b - a, whose section
2646        // edges then stood one-use. With no band re-base the arrangement's
2647        // frame is the trim's own, so the region's point is asked there as
2648        // well. Escape hatch `BREP_REGION_TRIM_FRAME=0`.
2649        if containment != PolygonClass::Inside
2650            && band_rebase == [None, None]
2651            && (arranged[0].x != test_uv.x || arranged[0].y != test_uv.y)
2652            && std::env::var("BREP_REGION_TRIM_FRAME").as_deref() != Ok("0")
2653        {
2654            containment = parameter_point_in_face(face, arranged[0], 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")?;
2655            if debug {
2656                eprintln!(
2657                    "  region area={:.6e} test=({:.9},{:.9}) asked in the trim's frame at ({:.9},{:.9}): {:?}",
2658                    region.area, test_uv.x, test_uv.y, arranged[0].x, arranged[0].y,
2659                    containment == PolygonClass::Inside
2660                );
2661            }
2662        }
2663        if containment != PolygonClass::Inside {
2664            if debug {
2665                eprintln!(
2666                    "  region area={:.6e} test=({:.9},{:.9}): outside trimmed face",
2667                    region.area, test_uv.x, test_uv.y
2668                );
2669            }
2670            continue;
2671        }
2672        if debug {
2673            eprintln!(
2674                "  region area={:.6e} test=({:.9},{:.9}): KEPT",
2675                region.area, test_uv.x, test_uv.y
2676            );
2677        }
2678        let containment_ms = profile.lap(&mut region_lap).as_secs_f64() * 1000.0;
2679        let Some(outer_loop) = build_loop(
2680            &region.outer,
2681            reverse_loops,
2682            &chains,
2683            arrangement_tolerance,
2684            &face.surface,
2685        )?
2686        else {
2687            // Spurious zero-area antenna region (e.g. a seam-grazing fold on a
2688            // periodic band); it bounds no material, so drop it.
2689            if debug {
2690                eprintln!("  region area={:.6e}: dropped (degenerate antenna)", region.area);
2691            }
2692            continue;
2693        };
2694        let mut loops = vec![outer_loop];
2695        for hole in &region.holes {
2696            // Holes take the SAME flag as the outer loop: the arrangement
2697            // hands them back already wound against it, so reversing both
2698            // together keeps them opposite.
2699            if let Some(hole_loop) = build_loop(
2700                hole,
2701                reverse_loops,
2702                &chains,
2703                arrangement_tolerance,
2704                &face.surface,
2705            )? {
2706                loops.push(hole_loop);
2707            }
2708        }
2709        for loop_record in &mut loops {
2710            for coedge in &mut loop_record.coedges {
2711                if let FragmentEdgeSource::Boundary {
2712                    operand: source_operand,
2713                    ..
2714                } = &mut coedge.source
2715                {
2716                    *source_operand = operand;
2717                }
2718            }
2719        }
2720        let mut extra_test_points = Vec::new();
2721        for &candidate in candidates.iter().skip(1) {
2722            if parameter_point_in_face(face, candidate, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? == PolygonClass::Inside {
2723                extra_test_points.push(face.surface.evaluate(candidate.x, candidate.y).or_refuse(KernelStage::Fragment, "evaluate")?);
2724            }
2725        }
2726        if profile.enabled {
2727            crate::imprint::profile_emit(format_args!("fragment.region face={} edges={} holes={} interior={interior_ms:.3} containment={containment_ms:.3} loops={:.3}",
2728                face.id, region.outer.len(), region.holes.len(), profile.lap(&mut region_lap).as_secs_f64() * 1000.0));
2729        }
2730        fragments.push(FaceFragmentRecord {
2731            operand,
2732            source_face_id: face.id,
2733            surface: face.surface.clone(),
2734            same_sense: face.same_sense,
2735            loops,
2736            test_point: face.surface.evaluate(test_uv.x, test_uv.y).or_refuse(KernelStage::Fragment, "evaluate")?,
2737            test_uv,
2738            extra_test_points,
2739            order: Some(order),
2740        });
2741    }
2742
2743    // Rescue an UNCUT face the flat 2D arrangement failed to reconstruct.
2744    // A seam-closed multi-loop face (a band on a surface of revolution, whose
2745    // param-space loops only close across the u/v seam, or a holed face whose
2746    // domain centre lands in the hole so the fast path above declined it)
2747    // collapses to zero-area lines in the [0,1]^2 arrangement and yields NO
2748    // region -> the face is dropped entirely, stranding its neighbours' shared
2749    // edges as one-use and knocking the assembly's genus off an integer. Since
2750    // the face is uncut, its loops already describe the exact trimmed boundary,
2751    // so pass the unchanged face through — we only need a valid interior test
2752    // point, found by gridding the ACTUAL knot domain. Gated on
2753    // `fragments.is_empty()`, this never touches a face the arrangement handled.
2754    profile.regions += profile.lap(lap_start);
2755    if fragments.is_empty() && piece_ids.is_empty() {
2756        const GRID: usize = 12;
2757        let mut test_uv: Option<Vec2> = None;
2758        let mut extra_test_points: Vec<Vec3> = Vec::new();
2759        'search: for iu in 1..GRID {
2760            for iv in 1..GRID {
2761                let candidate = Vec2 {
2762                    x: u_domain[0] + (u_domain[1] - u_domain[0]) * iu as f64 / GRID as f64,
2763                    y: v_domain[0] + (v_domain[1] - v_domain[0]) * iv as f64 / GRID as f64,
2764                };
2765                if parameter_point_in_face(face, candidate, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? == PolygonClass::Inside {
2766                    if test_uv.is_none() {
2767                        test_uv = Some(candidate);
2768                    } else {
2769                        extra_test_points.push(face.surface.evaluate(candidate.x, candidate.y).or_refuse(KernelStage::Fragment, "evaluate")?);
2770                        if extra_test_points.len() >= 2 {
2771                            break 'search;
2772                        }
2773                    }
2774                }
2775            }
2776        }
2777        // SEAM-STRADDLING BAND fallback (a SINGLY-periodic surface of
2778        // revolution whose trim loop closes only ACROSS the u/v seam — e.g. a
2779        // rounded imported cap grazed near-tangent, so it stays uncut). The
2780        // even-odd `parameter_point_in_face` reads Outside EVERYWHERE on such a
2781        // loop (the seam hop makes the [0,1]^2 polygon non-simple), so the knot
2782        // grid above finds nothing and the face would still drop. `seam_band_
2783        // point_in_face` only covers the DOUBLY-periodic (torus) case, so the
2784        // singly-periodic band has no seam-aware classifier. Unwrap the loop
2785        // onto the surface's covering plane (`loop_seam_offsets`, the same fold
2786        // the mass integrator and tessellator use), where it IS a simple
2787        // polygon, grid that unwrapped bbox with the plain even-odd test, and
2788        // map an interior hit back into the domain. Runs ONLY when the grid
2789        // already failed AND the offsets show the loop truly straddled a seam
2790        // (a full-wrap rim yields all-zero offsets and is left untouched), so it
2791        // never changes a face any earlier path handled.
2792        if test_uv.is_none() {
2793            let (closed_u, closed_v) = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
2794            if closed_u || closed_v {
2795                let u_period = u_domain[1] - u_domain[0];
2796                let v_period = v_domain[1] - v_domain[0];
2797                let mut loop_polygons: Vec<Vec<Vec2>> = Vec::new();
2798                let mut straddled = false;
2799                let (mut umin, mut umax, mut vmin, mut vmax) = (
2800                    f64::INFINITY,
2801                    f64::NEG_INFINITY,
2802                    f64::INFINITY,
2803                    f64::NEG_INFINITY,
2804                );
2805                for loop_record in &face.loops {
2806                    let offsets = crate::topology::loop_seam_offsets(
2807                        &loop_record.coedges,
2808                        closed_u,
2809                        closed_v,
2810                        u_period,
2811                        v_period,
2812                    ).or_refuse(KernelStage::Fragment, "csg.fragment.face_split")?;
2813                    if offsets.iter().any(|o| o[0] != 0.0 || o[1] != 0.0) {
2814                        straddled = true;
2815                    }
2816                    let mut polygon = Vec::new();
2817                    for (coedge_index, coedge) in loop_record.coedges.iter().enumerate() {
2818                        let [d0, d1] = coedge.pcurve.domain().or_refuse(KernelStage::Fragment, "domain")?;
2819                        let samples = 32usize;
2820                        for k in 0..samples {
2821                            let t = d0 + (d1 - d0) * k as f64 / samples as f64;
2822                            let p = coedge.pcurve.evaluate(t).or_refuse(KernelStage::Fragment, "evaluate")?;
2823                            let uv = Vec2 {
2824                                x: p.x + offsets[coedge_index][0],
2825                                y: p.y + offsets[coedge_index][1],
2826                            };
2827                            umin = umin.min(uv.x);
2828                            umax = umax.max(uv.x);
2829                            vmin = vmin.min(uv.y);
2830                            vmax = vmax.max(uv.y);
2831                            polygon.push(uv);
2832                        }
2833                    }
2834                    loop_polygons.push(polygon);
2835                }
2836                if straddled && umax > umin && vmax > vmin {
2837                    let wrap = |value: f64, lo: f64, span: f64| {
2838                        if span > 0.0 {
2839                            lo + (value - lo).rem_euclid(span)
2840                        } else {
2841                            value
2842                        }
2843                    };
2844                    'seam: for iu in 1..GRID {
2845                        for iv in 1..GRID {
2846                            let cu = umin + (umax - umin) * iu as f64 / GRID as f64;
2847                            let cv = vmin + (vmax - vmin) * iv as f64 / GRID as f64;
2848                            let candidate = Vec2 { x: cu, y: cv };
2849                            // Even-odd fill across ALL unwrapped loops: interior
2850                            // of the trimmed region is where the winding is odd
2851                            // (outer loop minus any hole loops), independent of
2852                            // which loop is the outer one.
2853                            let winding = loop_polygons
2854                                .iter()
2855                                .filter(|polygon| point_in_polygon(candidate, polygon))
2856                                .count();
2857                            if winding % 2 == 0 {
2858                                continue;
2859                            }
2860                            let mapped = Vec2 {
2861                                x: if closed_u {
2862                                    wrap(cu, u_domain[0], u_period)
2863                                } else {
2864                                    cu
2865                                },
2866                                y: if closed_v {
2867                                    wrap(cv, v_domain[0], v_period)
2868                                } else {
2869                                    cv
2870                                },
2871                            };
2872                            if test_uv.is_none() {
2873                                test_uv = Some(mapped);
2874                            } else {
2875                                extra_test_points
2876                                    .push(face.surface.evaluate(mapped.x, mapped.y).or_refuse(KernelStage::Fragment, "evaluate")?);
2877                                if extra_test_points.len() >= 2 {
2878                                    break 'seam;
2879                                }
2880                            }
2881                        }
2882                    }
2883                }
2884            }
2885        }
2886        if let Some(uv) = test_uv {
2887            fragments.push(FaceFragmentRecord {
2888                operand,
2889                source_face_id: face.id,
2890                surface: face.surface.clone(),
2891                same_sense: face.same_sense,
2892                loops: face
2893                    .loops
2894                    .iter()
2895                    .map(|loop_record| FragmentLoop {
2896                        coedges: loop_record
2897                            .coedges
2898                            .iter()
2899                            .map(|coedge| FragmentCoedge {
2900                                source: FragmentEdgeSource::Boundary {
2901                                    operand,
2902                                    edge_id: coedge.edge_id,
2903                                },
2904                                forward: coedge.forward,
2905                                pcurve: coedge.pcurve.clone(),
2906                            })
2907                            .collect(),
2908                    })
2909                    .collect(),
2910                test_point: face.surface.evaluate(uv.x, uv.y).or_refuse(KernelStage::Fragment, "evaluate")?,
2911                test_uv: uv,
2912                extra_test_points,
2913                order: None,
2914            });
2915        }
2916    }
2917    profile.rescue += profile.lap(lap_start);
2918    Ok(fragments)
2919}
2920
2921pub fn fragment_solid(
2922    solid: &BrepSolid,
2923    operand: u8,
2924    imprint: &ImprintResultRecord,
2925) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
2926    fragment_solid_against(solid, operand, imprint, None)
2927}
2928
2929/// A positive rational carrier is enclosed by its control hull. Unknown or
2930/// non-finite weights/points cannot certify separation and keep the full path.
2931pub(crate) fn bounded_carriers(solid: &BrepSolid) -> bool {
2932    solid.shells.iter().flat_map(|s| &s.faces).all(|face| bounded_carrier(face))
2933}
2934
2935fn bounded_carrier(face: &FaceRecord) -> bool {
2936    let (Ok(u), Ok(v)) = (face.surface.domain_u(), face.surface.domain_v()) else { return false };
2937    let positive_finite = |p: &crate::Vec4| {
2938        p.w > 0.0 && p.w.is_finite() && p.point().is_ok_and(|v|
2939            v.x.is_finite() && v.y.is_finite() && v.z.is_finite())
2940    };
2941    // Extended trims may live outside the carrier hull. Positive pcurve
2942    // controls within both knot domains certify the whole trim stays inside;
2943    // otherwise retain the existing extension/seam handling, without epsilon.
2944    face.surface.control_points.iter().flatten().all(positive_finite)
2945        && face.loops.iter().flat_map(|lp| &lp.coedges).all(|coedge| {
2946            coedge.pcurve.control_points.iter().all(|p| {
2947                positive_finite(p) && p.point().is_ok_and(|p|
2948                    p.x >= u[0] && p.x <= u[1] && p.y >= v[0] && p.y <= v[1])
2949            })
2950        })
2951}
2952
2953/// Exact control-hull separation, including the extended-trim guard.
2954pub(crate) fn face_separated(face: &FaceRecord, bounds: crate::Aabb, band: f64) -> bool {
2955    bounded_carrier(face) && crate::Aabb::from_surface_controls(&face.surface)
2956        .is_ok_and(|own| !own.intersects(bounds, band))
2957}
2958
2959/// A boundary witness suffices when the ENTIRE carrier is separated from the
2960/// other operand: every point of the face has the same Outside classification.
2961/// Keep the authored loops exactly; no interior-point search or arrangement
2962/// is needed to decide material against an operand outside this carrier box.
2963/// TRIM-SIDE PROBE for a face no section touches whose arrangement came back
2964/// EMPTY (hosted report 2026-11-02 "boolean fails", the shelf pin): the
2965/// bracket union's cylinder carried its cone on a 0.01-wide cap ring (the
2966/// cone's base rim 0.01 inside the cylinder's cap), no section on it. Its
2967/// domain centre is its hole, so it went to the arrangement for a test
2968/// point, which sampled a circle at 32 chords: the inner rim's chords sagged
2969/// 0.0115 into the hole, the only region's test point sat there, "outside
2970/// trimmed face", and the face was dropped without a word (open edges at
2971/// assembly, or a body that validated at the wrong volume). A face without
2972/// a cut that the arrangement does not split is one fragment, itself; all
2973/// it needs is a point of its own material, and that lies beside its trim: a
2974/// station on each loop's pcurve, stepped off along the pcurve's normal on
2975/// both sides over a ladder of distances, the first one the trim owns. The
2976/// arrangement's own answers stand where it gives any (a pinched loop it
2977/// splits in two). Escape hatch `BREP_FRAGMENT_TRIM_PROBE=0|off` leaves the
2978/// empty answer as it was.
2979fn trim_probe_enabled() -> bool {
2980    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2981    *ON.get_or_init(|| !matches!(std::env::var("BREP_FRAGMENT_TRIM_PROBE").as_deref(), Ok("0") | Ok("off")))
2982}
2983
2984/// The face itself as its one fragment, with the test point `uv`.
2985fn untouched_fragment(
2986    face: &FaceRecord,
2987    operand: u8,
2988    uv: Vec2,
2989    extra_test_points: Vec<Vec3>,
2990) -> Result<FaceFragmentRecord, KernelRefusal> {
2991    Ok(FaceFragmentRecord {
2992        operand,
2993        source_face_id: face.id,
2994        surface: face.surface.clone(),
2995        same_sense: face.same_sense,
2996        loops: face
2997            .loops
2998            .iter()
2999            .map(|loop_record| FragmentLoop {
3000                coedges: loop_record
3001                    .coedges
3002                    .iter()
3003                    .map(|coedge| FragmentCoedge {
3004                        source: FragmentEdgeSource::Boundary {
3005                            operand,
3006                            edge_id: coedge.edge_id,
3007                        },
3008                        forward: coedge.forward,
3009                        pcurve: coedge.pcurve.clone(),
3010                    })
3011                    .collect(),
3012            })
3013            .collect(),
3014        test_point: face.surface.evaluate(uv.x, uv.y).or_refuse(KernelStage::Fragment, "evaluate")?,
3015        test_uv: uv,
3016        extra_test_points,
3017        order: None,
3018    })
3019}
3020
3021/// A point of `face`'s material beside its trim, with up to two more as
3022/// extra test points, or `None` when no station of the ladder is owned.
3023fn trim_interior_probe(
3024    face: &FaceRecord,
3025    u: [f64; 2],
3026    v: [f64; 2],
3027) -> Result<Option<(Vec2, Vec<Vec3>)>, KernelRefusal> {
3028    let diagonal = ((u[1] - u[0]).powi(2) + (v[1] - v[0]).powi(2)).sqrt();
3029    if !(diagonal > 0.0) {
3030        return Ok(None);
3031    }
3032    let mut found: Vec<Vec2> = Vec::new();
3033    'loops: for loop_record in &face.loops {
3034        for coedge in &loop_record.coedges {
3035            let [a, b] = coedge.pcurve.domain().or_refuse(KernelStage::Fragment, "domain")?;
3036            if !(b > a) {
3037                continue;
3038            }
3039            for fraction in [0.5, 0.25, 0.75] {
3040                let t = a + (b - a) * fraction;
3041                let derivatives = coedge.pcurve.derivatives(t, 1).or_refuse(KernelStage::Fragment, "derivatives")?;
3042                let (at, tangent) = (derivatives[0], derivatives[1]);
3043                let speed = (tangent.x * tangent.x + tangent.y * tangent.y).sqrt();
3044                if !(speed > 0.0) || !speed.is_finite() {
3045                    continue;
3046                }
3047                let normal = Vec2 { x: -tangent.y / speed, y: tangent.x / speed };
3048                for scale in [1e-2, 1e-3, 1e-4, 1e-5, 1e-6] {
3049                    let step = diagonal * scale;
3050                    for sign in [1.0, -1.0] {
3051                        let candidate = Vec2 { x: at.x + normal.x * step * sign, y: at.y + normal.y * step * sign };
3052                        if candidate.x < u[0] || candidate.x > u[1] || candidate.y < v[0] || candidate.y > v[1] {
3053                            continue;
3054                        }
3055                        if parameter_point_in_face(face, candidate, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? == PolygonClass::Inside {
3056                            found.push(candidate);
3057                            if found.len() >= 3 {
3058                                break 'loops;
3059                            }
3060                            continue 'loops;
3061                        }
3062                    }
3063                }
3064            }
3065        }
3066    }
3067    let Some(uv) = found.first().copied() else {
3068        return Ok(None);
3069    };
3070    let mut extra_test_points = Vec::new();
3071    for candidate in found.iter().skip(1) {
3072        extra_test_points.push(face.surface.evaluate(candidate.x, candidate.y).or_refuse(KernelStage::Fragment, "evaluate")?);
3073    }
3074    Ok(Some((uv, extra_test_points)))
3075}
3076
3077fn separated_fragment(face: &FaceRecord, operand: u8) -> Option<FaceFragmentRecord> {
3078    let coedge = face.loops.first()?.coedges.first()?;
3079    let [a, b] = coedge.pcurve.domain().ok()?;
3080    let point = coedge.pcurve.evaluate(a + (b - a) * 0.5).ok()?;
3081    let uv = Vec2 { x: point.x, y: point.y };
3082    let test_point = face.surface.evaluate(uv.x, uv.y).ok()?;
3083    Some(FaceFragmentRecord {
3084        operand, source_face_id: face.id, surface: face.surface.clone(),
3085        same_sense: face.same_sense,
3086        loops: face.loops.iter().map(|lp| FragmentLoop {
3087            coedges: lp.coedges.iter().map(|coedge| FragmentCoedge {
3088                source: FragmentEdgeSource::Boundary { operand, edge_id: coedge.edge_id },
3089                forward: coedge.forward, pcurve: coedge.pcurve.clone(),
3090            }).collect(),
3091        }).collect(),
3092        test_point, test_uv: uv, extra_test_points: Vec::new(), order: None,
3093    })
3094}
3095
3096pub(crate) fn fragment_solid_against(
3097    solid: &BrepSolid,
3098    operand: u8,
3099    imprint: &ImprintResultRecord,
3100    other: Option<(crate::Aabb, f64)>,
3101) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
3102    let mut profile = FragmentProfile::new();
3103    if profile.enabled {
3104        crate::classification::containment_profile_begin();
3105    }
3106    let result = fragment_solid_profiled(solid, operand, imprint, other, &mut profile);
3107    profile.report(operand);
3108    if profile.enabled {
3109        crate::classification::containment_profile_report("fragment");
3110    }
3111    result
3112}
3113
3114pub(super) fn fragment_solid_profiled(
3115    solid: &BrepSolid,
3116    operand: u8,
3117    imprint: &ImprintResultRecord,
3118    other: Option<(crate::Aabb, f64)>,
3119    profile: &mut FragmentProfile,
3120) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
3121    let index = FragmentIndex::build(imprint);
3122    let mut fragments = Vec::new();
3123    for face in solid.shells.iter().flat_map(|shell| shell.faces.iter()) {
3124        let far = other.is_some_and(|(bounds, band)| face_separated(face, bounds, band));
3125        if !far { profile.box_candidates += 1; }
3126        let has_pieces = index.by_face.get(&(operand, face.id)).is_some_and(|ids| !ids.is_empty());
3127        let cosurface = index.cosurface_pairs.iter().any(|(key, _)| key.operand == operand && key.face_id == face.id);
3128        if far && !has_pieces && !cosurface && std::env::var("BREP_FRAGMENT_SCOPE").as_deref() != Ok("0") {
3129            if let Some(fragment) = separated_fragment(face, operand) {
3130                profile.separated_faces += 1;
3131                profile.face_done(face.id, None);
3132                fragments.push(fragment);
3133                continue;
3134            }
3135        }
3136        let before = (profile.arranged_faces, profile.region_faces);
3137        fragments.extend(fragment_face_indexed(solid, operand, face, &index, profile)?);
3138        if far {
3139            profile.far_arranged += profile.arranged_faces - before.0;
3140            profile.far_regions += profile.region_faces - before.1;
3141        }
3142    }
3143    Ok(fragments)
3144}
3145
3146
3147/// `BREP_RIM_BRIDGE` (default on, `=0` off): the fragment side of the imprint's rim
3148/// bridge (see RIM BRIDGE in `fragment_face_arranged`).
3149fn rim_bridge_enabled() -> bool {
3150    crate::switch!("BREP_RIM_BRIDGE", true)
3151}