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