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