Skip to main content

brep_kernel/blending/blend/chain/
closed.rs

1use crate::{KernelRefusal, KernelStage, OrRefuse};
2use super::*;
3
4/// Piece of a support rim assigned to one mate face.
5pub(super) struct RimPiece {
6    pub(super) face_id: u64,
7    pub(super) loop_index: usize,
8    /// Global-parameter window of this piece.
9    pub(super) window: [f64; 2],
10    pub(super) edge_id: u64,
11}
12
13/// Blend a chain of conjugated edges with one rolling-ball blend face
14/// (Golovanov §6.9.5: all conjugated edges processed together).  A CLOSED
15/// chain (stadium rim, T-pipe saddle) welds into a periodic blend; an OPEN
16/// chain (line->arc->line capped by end faces) rolls a clamped blend that
17/// terminates in a transverse edge on each end face.
18pub fn blend_smooth_chain(
19    solid: &BrepSolid,
20    seed_edge_id: u64,
21    radius: f64,
22    chamfer: bool,
23    name: Option<&str>,
24) -> Result<BrepSolid, KernelRefusal> {
25    // One blend operation: the reports of an earlier one, never consumed, are
26    // dropped as it starts (nested calls keep this operation's).
27    let _operation = crate::blend::BlendOperation::enter();
28    if !(radius > 0.0) || !radius.is_finite() {
29        return Err(KernelRefusal::input(KernelStage::Collect, "radius", "blend: radius must be positive"));
30    }
31    let chain = collect_smooth_chain(solid, seed_edge_id)?;
32    if chain.segments.len() < 2 {
33        return Err(KernelRefusal::internal(KernelStage::Refine, "chain_single_segment", "blend: chain collapsed to a single segment"));
34    }
35    if chain.closed {
36        blend_closed_smooth_chain(solid, &chain.segments, radius, chamfer, name)
37    } else {
38        blend_open_smooth_chain(solid, &chain, radius, chamfer, name)
39    }
40}
41
42/// `blend_smooth_chain` restricted to CLOSED chains: an edge that is one arc
43/// of a seam-split rim (a cyl×cyl saddle) is blended as the whole rim, but an
44/// OPEN tangent chain is not walked — filleting one edge of it must not
45/// fillet the unselected edges it runs into.  Tangent propagation is a
46/// selection policy for the feature layer; the kernel caps an unselected
47/// continuation instead (`network.rs`).
48pub fn blend_smooth_chain_if_closed(
49    solid: &BrepSolid,
50    seed_edge_id: u64,
51    radius: f64,
52    chamfer: bool,
53    name: Option<&str>,
54) -> Result<BrepSolid, KernelRefusal> {
55    // One blend operation: the reports of an earlier one, never consumed, are
56    // dropped as it starts (nested calls keep this operation's).
57    let _operation = crate::blend::BlendOperation::enter();
58    if !(radius > 0.0) || !radius.is_finite() {
59        return Err(KernelRefusal::input(KernelStage::Collect, "radius", "blend: radius must be positive"));
60    }
61    let chain = collect_smooth_chain(solid, seed_edge_id)?;
62    if chain.segments.len() < 2 {
63        return Err(KernelRefusal::internal(KernelStage::Refine, "chain_single_segment", "blend: chain collapsed to a single segment"));
64    }
65    if !chain.closed {
66        return Err(
67            KernelRefusal::unsupported(KernelStage::Classify, "open_tangent_chain", "blend: the edge is one arc of an OPEN tangent chain; the unselected \
68             continuation is capped, not blended"),
69        );
70    }
71    blend_closed_smooth_chain(solid, &chain.segments, radius, chamfer, name)
72}
73
74/// Blend a CLOSED chain of conjugated edges with one rolling-ball blend
75/// face (Golovanov §6.9.5: all conjugated edges processed together).
76///
77/// A wall whose ball-centre curve turns tighter than the ball FOLDS, and the
78/// envelope a plain march sweeps is then not the blend (`blend/fold.rs`).  It
79/// is not refused here any more where the fold is a lens the wall closes on
80/// itself: the chain is re-marched up a measured ladder of budgets and the lens
81/// is carved out along the crease (`blend/carve.rs`).  The re-march is the
82/// whole of the extra cost and only a folding wall pays it — the first march is
83/// the ordinary one, and its refusal is what asks for the second.
84fn blend_closed_smooth_chain(
85    solid: &BrepSolid,
86    segments: &[ChainSegment<'_>],
87    radius: f64,
88    chamfer: bool,
89    name: Option<&str>,
90) -> Result<BrepSolid, KernelRefusal> {
91    let bar = crate::KernelTolerances::for_solid(solid, 1e-7).intersection_fit;
92    // The plain collar climbs the same measured ladder the carve does, from the
93    // same first rung: a budget picked once is a resolution request in disguise.
94    let mut budget = ChainBudget::for_chain(segments, CHAIN_PER_SEGMENT)?;
95    let first = loop {
96        let per_segment = budget.rung;
97        let rung = build_closed_smooth_chain(
98            solid,
99            segments,
100            radius,
101            chamfer,
102            name,
103            &budget,
104            FoldPolicy::Refuse,
105            bar,
106        );
107        match rung {
108            Ok(Rung::TooCoarse { deviation }) => {
109                crate::blend::carve::carve_trace(format_args!(
110                    "blend chain: {} leaves the rails \
111                     {deviation:.6e} from the rolling ball, against {bar:.3e}",
112                    budget.describe()
113                ));
114                if budget.largest() * 2 > CHAIN_CARVE_MAX_PER_SEGMENT {
115                    return Err(KernelRefusal::non_convergence(KernelStage::Refine, "chain_ladder_top", format!(
116                        "blend: at {per_segment} stations per segment — the top of the chain's \
117                         ladder — this collar's fitted rails are still {deviation:.6e} from the \
118                         rolling ball's own contacts against a bar of {bar:.3e}, so there is no \
119                         wall accurate enough to build"
120                    )));
121                }
122                budget = budget.doubled();
123            }
124            // The continuation Newton of one station did not converge from its
125            // neighbour at this spacing. Only that typed station failure climbs:
126            // the next rung seeds every station from a neighbour half as far
127            // away. At the top the march's own refusal is returned verbatim.
128            Ok(Rung::StationFailed(failure)) => {
129                crate::blend::carve::carve_trace(format_args!(
130                    "blend chain: {per_segment} stations per segment: segment {} position {} \
131                     (t {:.9}) continued from position {} (t {:.9}, seed {:?}) did not converge: {}",
132                    failure.segment, failure.position, failure.t, failure.from_position,
133                    failure.from_t, failure.seed_uv, failure.error
134                ));
135                if budget.largest() * 2 > CHAIN_CARVE_MAX_PER_SEGMENT {
136                    break Err(failure.error);
137                }
138                budget = budget.doubled();
139            }
140            other => break other,
141        }
142    };
143    if let Err(error) = &first {
144        crate::blend::carve::carve_trace(format_args!(
145            "blend chain: the march at {} refused: {error}", budget.describe()
146        ));
147    }
148    match first {
149        Ok(Rung::Built(built)) => Ok(built),
150        Ok(Rung::TooCoarse { .. }) | Ok(Rung::StationFailed(_)) => {
151            Err(KernelRefusal::internal(KernelStage::Refine, "chain_ladder_rung", "blend: the chain ladder left an accuracy rung unhandled"))
152        }
153        // A fold that reaches a RAIL is not a lens: every section of that wall
154        // is singular somewhere between its contacts, so there is nothing to cut
155        // away and keep, and the ladder below would only re-march it to its top
156        // before the carve refused the same band. Its refusal names the face it
157        // cannot cross, and it is terminal as it stands (`blend/fold.rs`).
158        Err(error) if crate::blend::fold::is_rail_fold(&error) => Err(error),
159        // A CHAMFER section is a straight line and has no ball centre to read
160        // the fold locus against, so its fold stays the refusal it was.
161        Err(error) if crate::blend::is_wall_fold(&error) && !chamfer => {
162            carve_ladder(solid, segments, radius, name, bar)
163                // A CARVE THAT FAILS IS STILL A FOLD, and the fold is terminal.
164                // If the carve's own refusal did not read as one, the ladder
165                // below would answer a proven-folding centre curve with the
166                // cutter's unrelated complaint — which is the exact failure
167                // `fold.rs` was made terminal to stop. So every exit from this
168                // branch carries the fold's own prefix, with the carve's reason
169                // inside it.
170                .map_err(|carve_error| {
171                    if crate::blend::is_wall_fold(&carve_error) {
172                        carve_error
173                    } else {
174                        crate::blend::fold::wall_fold_refusal(
175                            Vec::new(),
176                            format!(
177                                "{} {} fits this edge: the wall folds, and the fold band could not \
178                                 be carved — {carve_error}",
179                                crate::blend::WALL_FOLDS,
180                                radius.abs()
181                            ),
182                        )
183                    }
184                })
185        }
186        Err(error) => Err(error),
187    }
188}
189
190/// Re-march a folding chain up a ladder of station budgets and carve at the
191/// first rung whose wall IS the rolling ball's to `bar`.
192///
193/// The budget is not chosen: a larger or a tighter fillet needs a different
194/// rung, and a number picked once from one fixture would be a resolution
195/// request in disguise. Each rung is measured by the rolling ball itself — its
196/// contacts re-solved halfway between stations, where an interpolant is worst —
197/// against the fitted rails, and the ladder stops at the first rung inside
198/// `intersection_fit`. A wall that still misses at the top is refused by name.
199fn carve_ladder(
200    solid: &BrepSolid,
201    segments: &[ChainSegment<'_>],
202    radius: f64,
203    name: Option<&str>,
204    bar: f64,
205) -> Result<BrepSolid, KernelRefusal> {
206    let mut budget = ChainBudget::for_chain(segments, CHAIN_PER_SEGMENT)?;
207    loop {
208        let per_segment = budget.rung;
209        match build_closed_smooth_chain(
210            solid,
211            segments,
212            radius,
213            false,
214            name,
215            &budget,
216            FoldPolicy::Carve,
217            bar,
218        )? {
219            Rung::Built(built) => {
220                crate::blend::carve::carve_trace(format_args!(
221                    "blend carve: {} is the first rung inside \
222                     {bar:.3e}",
223                    budget.describe()
224                ));
225                return Ok(built);
226            }
227            // The carve keeps its own contract: a station failure refuses as
228            // it did before the plain ladder learned to climb on it.
229            Rung::StationFailed(failure) => return Err(failure.error),
230            Rung::TooCoarse { deviation } => {
231                crate::blend::carve::carve_trace(format_args!(
232                    "blend carve: {} leaves the rails \
233                     {deviation:.6e} from the rolling ball, against {bar:.3e}",
234                    budget.describe()
235                ));
236                if budget.largest() * 2 > CHAIN_CARVE_MAX_PER_SEGMENT {
237                    return Err(crate::blend::fold::wall_fold_refusal(
238                        Vec::new(),
239                        format!(
240                            "{} {} fits this edge: the wall folds, and at {per_segment} stations \
241                             per segment — the top of the carve's ladder — its fitted rails are \
242                             still {deviation:.6e} from the rolling ball's own contacts against a \
243                             bar of {bar:.3e}, so there is no wall accurate enough to carve",
244                            crate::blend::WALL_FOLDS,
245                            radius.abs()
246                        ),
247                    ));
248                }
249                budget = budget.doubled();
250            }
251        }
252    }
253}
254
255/// One rung of a chain build: the finished solid, or — for a carve — the
256/// measured reason this budget is not enough.
257enum Rung {
258    Built(BrepSolid),
259    TooCoarse { deviation: f64 },
260    /// A station's continuation Newton found no root at this budget
261    /// (`march::ChainStationFailure`); a finer rung is a nearer seed.
262    StationFailed(ChainStationFailure),
263}
264
265/// The worst distance from the rolling ball's halfway contacts to the fitted
266/// rails `cr` and `cs`, each found by a closest-point Newton from the
267/// parameter the chord map assigns the midpoint.
268///
269/// THE NEWTON STAYS WHERE IT WAS SEEDED. The foot of a halfway contact is
270/// inside the station interval it was solved in, so every step is bounded by
271/// that interval's own length in the fit's parameter, clamped to the rail's
272/// domain, and taken only when it brings the rail closer. A plain Newton stops
273/// wherever the distance is stationary along the curve, which a rail that
274/// turns sharply at a chain junction offers well away from the foot. Measured
275/// on a 20 cube rounded at r = 3 with a bottom edge at r = 4 (the wall folds at
276/// the arcs' rails, so this is the ladder's reading with that refusal set
277/// aside): unguarded, every rung read the distance from the halfway contact
278/// beside a junction to the junction itself — 2.927709e-1 at 24 stations per
279/// segment against a half interval of 0.291667, halving per rung to
280/// 9.128591e-3 at 768 — and on two of the eight mirror-image edges a foot
281/// across the chain, 1.299089e1. Guarded, all eight read 6.961233e-3 at 24
282/// and 6.920006e-6 to 6.920008e-6 at 768, falling fourfold per rung as a
283/// cubic's does. A far foot can read short as well as long, which would pass
284/// a rung that misses.
285///
286/// Also returns every interval over `bar`, named by its left sample's index:
287/// what local refinement inserts a station into.
288fn rail_deviation(
289    samples: &[ChainSample],
290    cr: &NurbsCurve,
291    cs: &NurbsCurve,
292    fit_low: f64,
293    fit_range: f64,
294    budget: &ChainBudget,
295    bar: f64,
296) -> Result<(f64, Vec<usize>), KernelRefusal> {
297    // Each interval runs from a station to the NEXT one along its segment — the
298    // next grid position, a station local refinement inserted, or the
299    // segment's own end station — in the order the stations take.
300    let mut order: Vec<usize> = (0..samples.len())
301        .filter(|&index| (0..=budget.count(samples[index].segment) as isize).contains(&samples[index].position))
302        .collect();
303    order.sort_by(|&a, &b| {
304        let (a, b) = (&samples[a], &samples[b]);
305        a.segment.cmp(&b.segment).then(a.position.cmp(&b.position)).then(a.offset.total_cmp(&b.offset))
306    });
307    let next_of: std::collections::HashMap<usize, f64> = order
308        .windows(2)
309        .filter(|pair| samples[pair[0]].segment == samples[pair[1]].segment)
310        .map(|pair| (pair[0], samples[pair[1]].parameter))
311        .collect();
312    let mut failing = Vec::new();
313    let closest = closest_on_rail;
314    let mut worst: f64 = 0.0;
315    // Each segment's worst halfway contact, for `BREP_BLEND_CARVE_TRACE=1`: where
316    // along the chain a rung misses is what tells a junction from a bend.
317    let mut per_segment_worst: Vec<(f64, isize, f64, f64, Vec3, Vec3)> = Vec::new();
318    for &index in &order {
319        let sample = &samples[index];
320        let Some([p1, p2]) = sample.midpoint else {
321            continue;
322        };
323        let Some(&next) = next_of.get(&index) else {
324            continue;
325        };
326        let global = (0.5 * (sample.parameter + next)).rem_euclid(1.0);
327        let seed = (global - fit_low) / fit_range;
328        let reach = (next - sample.parameter).abs() / fit_range;
329        let (miss1, miss2) = (closest(cr, p1, seed, reach)?, closest(cs, p2, seed, reach)?);
330        worst = worst.max(miss1).max(miss2);
331        if miss1.max(miss2) > bar {
332            failing.push(index);
333        }
334        if per_segment_worst.len() <= sample.segment {
335            per_segment_worst.resize(
336                sample.segment + 1,
337                (0.0, 0, 0.0, 0.0, Vec3::default(), Vec3::default()),
338            );
339        }
340        if miss1.max(miss2) >= per_segment_worst[sample.segment].0 {
341            per_segment_worst[sample.segment] = (miss1.max(miss2), sample.position, miss1, miss2, p1, p2);
342        }
343    }
344    for (segment, (miss, position, miss1, miss2, p1, p2)) in per_segment_worst.iter().enumerate() {
345        crate::blend::carve::carve_trace(format_args!(
346            "  rail miss segment {segment}: worst {miss:.6e} after station {position} \
347             (cr {miss1:.6e} at ({:.6}, {:.6}, {:.6}), cs {miss2:.6e} at ({:.6}, {:.6}, {:.6}))",
348            p1.x, p1.y, p1.z, p2.x, p2.y, p2.z
349        ));
350    }
351    Ok((worst, failing))
352}
353
354/// Most dense INTERVALS (4097 samples) a closed chain's support-piece track may
355/// hold: the existing ceiling its uniform track is built under, and the one
356/// local refinement of that track stays within — not a sample count.
357const PIECE_TRACK_CEILING: usize = 4096;
358
359/// Distance from `target` to a fitted rail, by a Newton that stays where it is
360/// seeded (`rail_deviation`'s reading, shared with the dense diagnostic).
361fn closest_on_rail(curve: &NurbsCurve, target: Vec3, seed: f64, reach: f64) -> Result<f64, KernelRefusal> {
362    let [low, high] = curve.domain().or_refuse(KernelStage::Refine, "domain")?;
363    let distance = |u: f64| -> Result<f64, KernelRefusal> {
364        Ok(curve.derivatives_extended(u, 0).or_refuse(KernelStage::Refine, "derivatives_extended")?[0].sub(target).length())
365    };
366    let mut u = seed.clamp(low, high);
367    let mut best = distance(u)?;
368    for _ in 0..40 {
369        let derivatives = curve.derivatives_extended(u, 2).or_refuse(KernelStage::Refine, "derivatives_extended")?;
370        let offset = derivatives[0].sub(target);
371        let slope = offset.dot(derivatives[1]);
372        let curvature = derivatives[1].dot(derivatives[1]) + offset.dot(derivatives[2]);
373        // Off a minimum's basin the Newton step points the wrong way; walk
374        // downhill by the bound instead and let the test below shorten it.
375        let mut step = if curvature > 0.0 {
376            (slope / curvature).clamp(-reach, reach)
377        } else {
378            reach.copysign(slope)
379        };
380        let mut trial = (u - step).clamp(low, high);
381        let mut trial_distance = distance(trial)?;
382        for _ in 0..30 {
383            if trial_distance < best {
384                break;
385            }
386            step *= 0.5;
387            trial = (u - step).clamp(low, high);
388            trial_distance = distance(trial)?;
389        }
390        if !(trial_distance < best) {
391            break;
392        }
393        let moved = (trial - u).abs();
394        u = trial;
395        best = trial_distance;
396        if moved < 1e-15 {
397            break;
398        }
399    }
400    Ok(best)
401}
402
403/// Points of every interval the LOCAL-INSERTION path judges a chain rung's
404/// rails on: j/16, j = 1..15 (the halfway point among them).
405pub(super) const LOCAL_RAIL_STENCIL: usize = 16;
406
407/// One dense contact pair's miss: each rail's raw reading is checked BEFORE
408/// they are joined (`f64::max` alone drops a NaN on either side); `None` when
409/// either is unreadable.
410fn rail_pair_miss(cr_miss: f64, cs_miss: f64) -> Option<f64> {
411    (cr_miss.is_finite() && cs_miss.is_finite()).then(|| cr_miss.max(cs_miss))
412}
413
414/// Each solved dense contact's distance to the fitted rails, read with the same
415/// closest-point Newton and seeds `rail_deviation` uses, keyed by the
416/// interval's left sample: the readable misses, and separately every interval
417/// with an unreadable reading on either rail (a non-finite distance, or a foot
418/// that could not be evaluated).
419#[allow(clippy::too_many_arguments)]
420pub(super) fn dense_rail_misses(
421    contacts: &crate::blend::chain::DenseContacts,
422    samples: &[ChainSample],
423    cr: &NurbsCurve,
424    cs: &NurbsCurve,
425    low: f64,
426    range: f64,
427    wrap: bool,
428) -> Result<(Vec<(usize, f64)>, Vec<usize>), KernelRefusal> {
429    let mut readable = Vec::with_capacity(contacts.solved.len());
430    let mut unreadable = Vec::new();
431    for (left, next, fraction, p1, p2) in &contacts.solved {
432        let (from, to) = (samples[*left].parameter, samples[*next].parameter);
433        // A closed chain's chord parameter wraps with period 1; an OPEN
434        // chain's runs straight through its ends (its last station's may
435        // overshoot 1, and wrapping it would read the far end).
436        let global = from + fraction * (to - from);
437        let global = if wrap { global.rem_euclid(1.0) } else { global };
438        let seed = (global - low) / range;
439        let reach = (to - from).abs() / range;
440        // A closest foot that cannot be evaluated is an unreadable reading of
441        // this interval, not a refusal of the rung.
442        let cr_miss = closest_on_rail(cr, *p1, seed, reach).unwrap_or(f64::NAN);
443        let cs_miss = closest_on_rail(cs, *p2, seed, reach).unwrap_or(f64::NAN);
444        match rail_pair_miss(cr_miss, cs_miss) {
445            Some(miss) => readable.push((*left, miss)),
446            None => unreadable.push(*left),
447        }
448    }
449    Ok((readable, unreadable))
450}
451
452/// The local-insertion path's rail verdict: the halfway reading joined with the
453/// dense stencil's, FAIL-CLOSED — an interval with a dense contact over `bar`,
454/// an unreadable distance, or a contact that was not read (unsolved, or
455/// unreadable on either rail), fails; the worst is the largest readable
456/// distance. Intervals are named by their left sample.
457pub(super) fn combine_rail_verdict(
458    halfway: (f64, Vec<usize>),
459    dense: &[(usize, f64)],
460    unsolved: &[usize],
461    bar: f64,
462) -> (f64, Vec<usize>) {
463    let (mut worst, mut failing) = halfway;
464    for &(left, miss) in dense {
465        if !miss.is_finite() {
466            failing.push(left);
467            continue;
468        }
469        if miss > bar {
470            failing.push(left);
471        }
472        worst = worst.max(miss);
473    }
474    failing.extend_from_slice(unsolved);
475    failing.sort_unstable();
476    failing.dedup();
477    (worst, failing)
478}
479
480/// Whether the construction request runs on an accepted plain fillet rung:
481/// always, outside the controls that turn it off on their own thread
482/// (`MODEL_REQUEST_OFF`) or that turn the wall verdict off.
483/// The construction request's round limit: the existing local-refinement
484/// budget, outside the control that sets it to zero on its own thread to
485/// force the typed unmet report.
486pub(super) fn model_rounds_limit() -> usize {
487    REFINE_ROUNDS
488}
489
490pub(super) fn model_request_on() -> bool {
491    wall_verdict_on()
492}
493
494
495/// Whether the fillet wall verdict runs: always, outside the mutation control
496/// that turns it off on its own thread (`WALL_VERDICT_OFF`).
497pub(super) fn wall_verdict_on() -> bool {
498    true
499}
500
501
502
503
504
505/// Section-angle probes across a fillet wall, as fractions of its `v` span:
506/// k/8 for k = 1..7, between (never on) the rails the rail verdict reads.
507pub(super) const WALL_SECTION_PROBES: usize = 8;
508
509/// How far `p` stands from the rolling ball's sweep, read at p's OWN
510/// characteristic: the stationary point of |p − C(s)|, C the cubic through
511/// four consecutive exact ball centres of `nodes` around node `j` (`nodes`
512/// uniform in fraction across one interval, its ends the interval's own
513/// stations), searched within one node of `j`, minus `radius`.
514///
515/// The cubic is an APPROXIMATION of the centre curve, not the exact sweep:
516/// its error grows with the node spacing and the spine's higher derivatives,
517/// and [`wall_probe_reading`] estimates it by a second, overlapping stencil.
518///
519/// A STATIONARY point, not the nearest centre: past a fold's focal point an
520/// envelope point is a MAXIMUM of its distance to the spine, and a global
521/// nearest centre would read a carved wall's lens as inside another ball. A
522/// centre at the probe's own chain parameter is not used either: the wall's
523/// `u` and the stencil's edge parameter name the same section only to first
524/// order, and a spine offset ℓ adds √(ρ² + ℓ²) − ρ to the reading.
525///
526/// `None` — unreadable, which fails its interval — for a non-finite input,
527/// fewer than four nodes, a probe at an interval end, or a solve that leaves
528/// its window or does not converge.
529pub(in crate::blend) fn sweep_residual(p: Vec3, nodes: &[Vec3], j: usize, radius: f64) -> Option<f64> {
530    let j = locate_foot(p, nodes, j, radius)?;
531    sweep_residual_on(p, nodes, (j - 1).min(nodes.len() - 4), j, radius)
532}
533
534/// How many nodes the stencil may follow a probe's foot away from the probe's
535/// own node.
536const FOOT_TRAVEL: usize = 4;
537
538/// The node whose stencil holds `p`'s stationary point, starting from node
539/// `j`: the probe's wall `u` and the centres' edge parameter name the same
540/// section only to first order, so the foot can sit more than the one-node
541/// window from `j` (the 15- and 20-degree rows' feet stood up to 1.5 nodes,
542/// one 3.3, from it on f4ed17782's build, every decline there a window exit).
543/// A solve that leaves its window moves the stencil to the node nearest
544/// where it left, at most [`FOOT_TRAVEL`] nodes from `j` and never back to a
545/// node already tried; `None` when the foot is not found that way (or a
546/// solve is unreadable), which fails its interval as before.
547fn locate_foot(p: Vec3, nodes: &[Vec3], j: usize, radius: f64) -> Option<usize> {
548    let n = nodes.len();
549    if n < 4 || j == 0 || j + 1 >= n {
550        return None;
551    }
552    let mut tried: Vec<usize> = Vec::new();
553    let mut at = j;
554    loop {
555        let base = (at - 1).min(n - 4);
556        match stencil_solve(p, nodes, base, at, radius) {
557            Solve::Read(_) => return Some(at),
558            Solve::Unread => return None,
559            Solve::Window(s) => {
560                tried.push(at);
561                let next = (base as f64 + s).round();
562                let next = if next.is_finite() { next.clamp(1.0, (n - 2) as f64) as usize } else { at };
563                if tried.contains(&next) || next.abs_diff(j) > FOOT_TRAVEL {
564                    wall_decline(|| format!(
565                        "foot search from node {j} of {n}: tried {tried:?}, next {next} ({})",
566                        if tried.contains(&next) { "already tried" } else { "beyond FOOT_TRAVEL" }
567                    ));
568                    return None;
569                }
570                at = next;
571            }
572        }
573    }
574}
575
576/// [`sweep_residual`] on the cubic through `nodes[base..base + 4]`, the
577/// stationary point searched from node `j` within one node of it.
578fn sweep_residual_on(p: Vec3, nodes: &[Vec3], base: usize, j: usize, radius: f64) -> Option<f64> {
579    match stencil_solve(p, nodes, base, j, radius) {
580        Solve::Read(distance) => Some(distance),
581        Solve::Window(_) | Solve::Unread => None,
582    }
583}
584
585/// One stencil's solve: the distance read, or where (in node units from
586/// `base`) the search left its one-node window, or unreadable.
587enum Solve {
588    Read(f64),
589    Window(f64),
590    Unread,
591}
592
593thread_local! {
594    /// When Some, every decline inside the declared wall reader appends WHY
595    /// (innermost first): set and taken by `declared_wall_probe_why` only, so
596    /// every other caller pays one thread-local read per decline and the
597    /// reader's arithmetic and outcomes are the same either way.
598    static WALL_DECLINES: std::cell::RefCell<Option<Vec<String>>> = const { std::cell::RefCell::new(None) };
599}
600
601fn wall_decline(reason: impl FnOnce() -> String) {
602    WALL_DECLINES.with(|log| {
603        if let Some(lines) = log.borrow_mut().as_mut() {
604            lines.push(reason());
605        }
606    });
607}
608
609/// [`declared_wall_probe`] with, when it declines, the chain of declines
610/// that made it (innermost first): which tier (fine / coarse), which
611/// stencil, and inside the solver the exit (unreadable input, a vanishing
612/// slope, a window exit with the overshot `s`, its offset from the window and
613/// whether the solver's OWN stationarity predicate already held at the
614/// overshot point, a stall, a non-finite distance) or the foot search's
615/// tried nodes. Exactly the same arithmetic as `declared_wall_probe`.
616pub(in crate::blend) fn declared_wall_probe_why(p: Vec3, fine: &[Vec3], jf: usize, radius: f64) -> Result<(f64, f64), String> {
617    let previous = WALL_DECLINES.with(|log| log.replace(Some(Vec::new())));
618    let read = declared_wall_probe(p, fine, jf, radius);
619    let lines = WALL_DECLINES.with(|log| log.replace(previous)).unwrap_or_default();
620    read.ok_or_else(|| if lines.is_empty() { "declined (no inner cause recorded)".to_string() } else { lines.join(" <- ") })
621}
622
623fn stencil_solve(p: Vec3, nodes: &[Vec3], base: usize, j: usize, radius: f64) -> Solve {
624    let finite = |v: Vec3| v.x.is_finite() && v.y.is_finite() && v.z.is_finite();
625    let n = nodes.len();
626    if n < 4 || j == 0 || j + 1 >= n || base + 4 > n || !radius.is_finite() || !finite(p) || !nodes.iter().all(|&c| finite(c)) {
627        wall_decline(|| format!("stencil {n} nodes base {base} j {j}: unreadable input"));
628        return Solve::Unread;
629    }
630    // Newton's divided differences on 0, 1, 2, 3, s in node units from `base`.
631    let [p0, p1, p2, p3] = [nodes[base], nodes[base + 1], nodes[base + 2], nodes[base + 3]];
632    let d1 = p1.sub(p0);
633    let d2 = p2.sub(p1.scale(2.0)).add(p0).scale(0.5);
634    let d3 = p3.sub(p2.scale(3.0)).add(p1.scale(3.0)).sub(p0).scale(1.0 / 6.0);
635    let curve = |s: f64| -> (Vec3, Vec3, Vec3) {
636        let c = p0.add(d1.scale(s)).add(d2.scale(s * (s - 1.0))).add(d3.scale(s * (s - 1.0) * (s - 2.0)));
637        let c1 = d1.add(d2.scale(2.0 * s - 1.0)).add(d3.scale(3.0 * s * s - 6.0 * s + 2.0));
638        let c2 = d2.scale(2.0).add(d3.scale(6.0 * s - 6.0));
639        (c, c1, c2)
640    };
641    let start = j as f64 - base as f64;
642    let mut s = start;
643    let mut converged = false;
644    for _ in 0..50 {
645        let (c, c1, c2) = curve(s);
646        let w = c.sub(p);
647        let (g, slope) = (w.dot(c1), c1.dot(c1) + w.dot(c2));
648        if !(g.is_finite() && slope.is_finite()) || slope == 0.0 {
649            wall_decline(|| format!("stencil {n} nodes base {base} j {j}: slope {slope:e} g {g:e} at s {s}"));
650            return Solve::Unread;
651        }
652        // Converged at THIS s once the stationarity condition holds to its own
653        // rounding: g = (C − p)·C′ carries an absolute error of a few
654        // ε·(|p| + |C|)·|C′|, so at a node spacing of 1e-4 the Newton step
655        // cannot fall below ~1e-11 node units and the relative step test
656        // alone never fires — a line-for-line model of this reader read 87 %
657        // to 97 % of the NX and skew walls' fine-stencil probes unreadable
658        // that way (its Newton run past the stop read |g| / (ε·(|p| + |C|)·|C′|)
659        // at most 0.91 there; 16 keeps a margin). Tested before the step, so
660        // the point read is the point whose stationarity was checked; its
661        // distance is off the true stationary one by about
662        // ½·|g|·δs/|C − p| ≤ 8ε·(|p| + |C|)·|C′|/ρ for δs within the
663        // one-node window, far below any bar.
664        let stationary = g.abs() <= 16.0 * f64::EPSILON * (p.length() + c.length()) * c1.length();
665        if stationary {
666            converged = true;
667            break;
668        }
669        let step = g / slope;
670        let before = s;
671        s -= step;
672        if !((s - start).abs() <= 1.0) {
673            wall_decline(|| {
674                // The solver's own stationarity predicate, read at the
675                // overshot point (diagnostic only: nothing decides on it).
676                let (c, c1, _) = curve(s);
677                let g_after = c.sub(p).dot(c1);
678                let stationary_after = g_after.abs() <= 16.0 * f64::EPSILON * (p.length() + c.length()) * c1.length();
679                format!(
680                    "stencil {n} nodes base {base} j {j}: window exit s {before} -> {s} (start {start}, |s - start| - 1 = {:e}), \
681                     g {g:e} -> {g_after:e}, stationary at the overshot s: {stationary_after}",
682                    (s - start).abs() - 1.0
683                )
684            });
685            return if s.is_finite() { Solve::Window(s) } else { Solve::Unread };
686        }
687        if step.abs() <= 1e-14 * (1.0 + s.abs()) {
688            converged = true;
689            break;
690        }
691    }
692    if !converged {
693        wall_decline(|| format!("stencil {n} nodes base {base} j {j}: no convergence in 50 steps, s {s}"));
694        return Solve::Unread;
695    }
696    let distance = curve(s).0.sub(p).length() - radius;
697    if distance.is_finite() {
698        Solve::Read(distance)
699    } else {
700        wall_decline(|| format!("stencil {n} nodes base {base} j {j}: non-finite distance at s {s}"));
701        Solve::Unread
702    }
703}
704
705/// One wall probe read on TWO overlapping four-centre stencils around node
706/// `j` (the one [`sweep_residual`] uses and its neighbour): `(worst, spread)`
707/// — the larger magnitude, which keeps ONE stencil's under-read from passing,
708/// and how far apart the two readings are. Both are PROXY readings: when both
709/// stencils under-read alike (a symmetric node set about a foot near the
710/// focal distance) the larger still under-reads and the spread reads zero,
711/// so neither number is an error bound. `None` when either stencil is
712/// unreadable.
713pub(in crate::blend) fn wall_probe_reading(p: Vec3, nodes: &[Vec3], j: usize, radius: f64) -> Option<(f64, f64)> {
714    let n = nodes.len();
715    if n < 5 || j == 0 || j + 1 >= n {
716        return None;
717    }
718    let j = locate_foot(p, nodes, j, radius)?;
719    let first = (j - 1).min(n - 4);
720    let second = if j >= 2 { (j - 2).min(n - 4) } else { j.min(n - 4) };
721    let second = if second == first { if first >= 1 { first - 1 } else { first + 1 } } else { second };
722    let a = sweep_residual_on(p, nodes, first, j, radius)?;
723    let b = sweep_residual_on(p, nodes, second, j, radius)?;
724    Some((a.abs().max(b.abs()), (a - b).abs()))
725}
726
727/// The DECLARED wall reading at one probe, the one rule both wall tiers
728/// decide on: `(reading, uncertainty)`, declared = reading + uncertainty.
729/// `fine` are the interval's centres at twice the probe density, `jf` the
730/// probe's (even) node among them; the coarse set is every other node. The
731/// reading is the larger fine two-stencil magnitude; the uncertainty is the
732/// larger of the fine stencils' spread and how far the fine reading moved
733/// from the coarse one — the proxy's error estimated from its own change with
734/// node density, which two stencils agreeing at one density cannot show (two
735/// symmetric stencils that both under-read read a spread of zero). An
736/// ESTIMATE, not a bound: a proxy whose error does not shrink with density
737/// would still hide. `None` when any reading is unreadable.
738pub(in crate::blend) fn declared_wall_probe(p: Vec3, fine: &[Vec3], jf: usize, radius: f64) -> Option<(f64, f64)> {
739    if jf % 2 != 0 || fine.len() < 9 || fine.len() % 2 == 0 {
740        wall_decline(|| format!("declared reader: node {jf} of {} not an even node of an odd fine set", fine.len()));
741        return None;
742    }
743    let Some((reading, spread)) = wall_probe_reading(p, fine, jf, radius) else {
744        wall_decline(|| format!("FINE two-stencil reading at node {jf} of {}", fine.len()));
745        return None;
746    };
747    let Some(at_fine) = sweep_residual(p, fine, jf, radius) else {
748        wall_decline(|| format!("FINE sweep at node {jf} of {}", fine.len()));
749        return None;
750    };
751    let coarse: Vec<Vec3> = fine.iter().step_by(2).copied().collect();
752    let Some(at_coarse) = sweep_residual(p, &coarse, jf / 2, radius) else {
753        wall_decline(|| format!("COARSE sweep at node {} of {}", jf / 2, coarse.len()));
754        return None;
755    };
756    let uncertainty = spread.max((at_fine - at_coarse).abs());
757    uncertainty.is_finite().then_some((reading, uncertainty))
758}
759
760/// The even fine node whose position along the CENTRE PATH (cumulative
761/// chord of `fine`) is nearest `fraction` of it, inside 2..=n-3; `None` when
762/// the path has no finite positive length.
763///
764/// A wall probe sits at a fraction of its interval in the WALL's parameter,
765/// which is chord-like; the exact centres sit at uniform fractions of the
766/// edge's NATIVE parameter. On a regular edge the two agree to first order and
767/// the probe's foot lies within a node or two of its own node. At a
768/// STATIONARY section (the edge's derivative vanishes there) the centre moves
769/// like t^2 in the native parameter, so a probe at chord fraction f has its foot
770/// near native fraction sqrt(f): the probe at node 2 of 32 (f = 1/16) has its
771/// foot near node 8, past `FOOT_TRAVEL`, and the reader declines though the
772/// centre locus is perfectly regular there (zero_tangent_sites' stationary
773/// stadium and tombstone rims refused that way with every reading under the
774/// bar).
775pub(in crate::blend) fn centre_path_node(fine: &[Vec3], fraction: f64) -> Option<usize> {
776    let n = fine.len();
777    if n < 7 || !(fraction.is_finite()) {
778        return None;
779    }
780    let mut cumulative = Vec::with_capacity(n);
781    cumulative.push(0.0_f64);
782    for pair in fine.windows(2) {
783        let step = pair[1].sub(pair[0]).length();
784        cumulative.push(cumulative.last().copied().unwrap_or(0.0) + step);
785    }
786    let total = *cumulative.last()?;
787    if !(total.is_finite() && total > 0.0) {
788        return None;
789    }
790    let target = fraction.clamp(0.0, 1.0) * total;
791    let nearest = (0..n).min_by(|&a, &b| (cumulative[a] - target).abs().total_cmp(&(cumulative[b] - target).abs()))?;
792    let even = 2 * ((nearest as f64 / 2.0).round() as usize);
793    Some(even.clamp(2, (n - 3) & !1))
794}
795
796/// [`declared_wall_probe_why`] from the probe's own node `jf`; when that
797/// declines, once more from the node the probe's `fraction` reaches along the
798/// centre path ([`centre_path_node`]), when that is another node. The same
799/// reader, bars and fail-closed rule decide; a probe it still cannot read is
800/// unread (both declines named). A probe the first read reads is read exactly
801/// as before.
802pub(in crate::blend) fn declared_wall_probe_located_why(p: Vec3, fine: &[Vec3], jf: usize, fraction: f64, radius: f64) -> Result<(f64, f64), String> {
803    let first = match declared_wall_probe_why(p, fine, jf, radius) {
804        Ok(read) => return Ok(read),
805        Err(why) => why,
806    };
807    match centre_path_node(fine, fraction) {
808        Some(located) if located != jf => declared_wall_probe_why(p, fine, located, radius)
809            .map_err(|again| format!("{first} -- and from centre-path node {located}: {again}")),
810        _ => Err(first),
811    }
812}
813
814/// [`declared_wall_probe_located_why`] as an `Option`.
815pub(in crate::blend) fn declared_wall_probe_located(p: Vec3, fine: &[Vec3], jf: usize, fraction: f64, radius: f64) -> Option<(f64, f64)> {
816    match declared_wall_probe(p, fine, jf, radius) {
817        Some(read) => Some(read),
818        None => match centre_path_node(fine, fraction) {
819            Some(located) if located != jf => declared_wall_probe(p, fine, located, radius),
820            _ => None,
821        },
822    }
823}
824
825/// [`declared_wall_probe_why`] on centres at NON-UNIFORM native abscissae
826/// `ts` (one per node, strictly increasing): the same two-tier rule, the
827/// same two overlapping four-centre stencils, the same foot search
828/// (`FOOT_TRAVEL`), the same 16·ε stationarity predicate and the same
829/// window (one node either side of the start node), with each cubic the
830/// Newton interpolant on the nodes' OWN abscissae instead of the implicit
831/// 0, 1, 2, 3. For a junction of two intervals of unequal native width (local
832/// insertions make them so): the uniform reader would place the two halves'
833/// nodes at one spacing and distort the centre locus. The coarse tier is
834/// every other node at every other abscissa. A caller whose abscissae are
835/// uniform uses the uniform reader, bit for bit.
836pub(in crate::blend) fn declared_wall_probe_at_why(p: Vec3, fine: &[Vec3], ts: &[f64], jf: usize, radius: f64) -> Result<(f64, f64), String> {
837    if jf % 2 != 0
838        || fine.len() < 9
839        || fine.len() % 2 == 0
840        || ts.len() != fine.len()
841        || !ts.iter().all(|t| t.is_finite())
842        || !ts.windows(2).all(|pair| pair[1] > pair[0])
843    {
844        return Err(format!("native-abscissa reader: node {jf} of {} with {} finite, strictly increasing abscissae required", fine.len(), ts.len()));
845    }
846    let previous = WALL_DECLINES.with(|log| log.replace(Some(Vec::new())));
847    let read = (|| -> Option<(f64, f64)> {
848        let (reading, spread) = wall_probe_reading_at(p, fine, ts, jf, radius).or_else(|| {
849            wall_decline(|| format!("FINE two-stencil reading (native abscissae) at node {jf} of {}", fine.len()));
850            None
851        })?;
852        let at_fine = sweep_residual_at(p, fine, ts, jf, radius).or_else(|| {
853            wall_decline(|| format!("FINE sweep (native abscissae) at node {jf} of {}", fine.len()));
854            None
855        })?;
856        let coarse: Vec<Vec3> = fine.iter().step_by(2).copied().collect();
857        let coarse_ts: Vec<f64> = ts.iter().step_by(2).copied().collect();
858        let at_coarse = sweep_residual_at(p, &coarse, &coarse_ts, jf / 2, radius).or_else(|| {
859            wall_decline(|| format!("COARSE sweep (native abscissae) at node {} of {}", jf / 2, coarse.len()));
860            None
861        })?;
862        let uncertainty = spread.max((at_fine - at_coarse).abs());
863        uncertainty.is_finite().then_some((reading, uncertainty))
864    })();
865    let lines = WALL_DECLINES.with(|log| log.replace(previous)).unwrap_or_default();
866    read.ok_or_else(|| if lines.is_empty() { "declined (no inner cause recorded)".to_string() } else { lines.join(" <- ") })
867}
868
869fn wall_probe_reading_at(p: Vec3, nodes: &[Vec3], ts: &[f64], j: usize, radius: f64) -> Option<(f64, f64)> {
870    let n = nodes.len();
871    if n < 5 || j == 0 || j + 1 >= n {
872        return None;
873    }
874    let j = locate_foot_at(p, nodes, ts, j, radius)?;
875    let first = (j - 1).min(n - 4);
876    let second = if j >= 2 { (j - 2).min(n - 4) } else { j.min(n - 4) };
877    let second = if second == first { if first >= 1 { first - 1 } else { first + 1 } } else { second };
878    let a = match stencil_solve_at(p, nodes, ts, first, j, radius) { Solve::Read(distance) => distance, _ => return None };
879    let b = match stencil_solve_at(p, nodes, ts, second, j, radius) { Solve::Read(distance) => distance, _ => return None };
880    Some((a.abs().max(b.abs()), (a - b).abs()))
881}
882
883fn sweep_residual_at(p: Vec3, nodes: &[Vec3], ts: &[f64], j: usize, radius: f64) -> Option<f64> {
884    let j = locate_foot_at(p, nodes, ts, j, radius)?;
885    match stencil_solve_at(p, nodes, ts, (j - 1).min(nodes.len() - 4), j, radius) {
886        Solve::Read(distance) => Some(distance),
887        Solve::Window(_) | Solve::Unread => None,
888    }
889}
890
891/// [`locate_foot`] on native abscissae: a window exit moves the stencil to
892/// the node whose abscissa is nearest the exit, within `FOOT_TRAVEL` nodes
893/// and never back to a node already tried.
894fn locate_foot_at(p: Vec3, nodes: &[Vec3], ts: &[f64], j: usize, radius: f64) -> Option<usize> {
895    let n = nodes.len();
896    if n < 4 || j == 0 || j + 1 >= n {
897        return None;
898    }
899    let mut tried: Vec<usize> = Vec::new();
900    let mut at = j;
901    loop {
902        let base = (at - 1).min(n - 4);
903        match stencil_solve_at(p, nodes, ts, base, at, radius) {
904            Solve::Read(_) => return Some(at),
905            Solve::Unread => return None,
906            Solve::Window(t) => {
907                tried.push(at);
908                let nearest = (0..n).min_by(|&a, &b| (ts[a] - t).abs().total_cmp(&(ts[b] - t).abs())).unwrap_or(at);
909                let next = if t.is_finite() { nearest.clamp(1, n - 2) } else { at };
910                if tried.contains(&next) || next.abs_diff(j) > FOOT_TRAVEL {
911                    wall_decline(|| format!(
912                        "foot search (native abscissae) from node {j} of {n}: tried {tried:?}, next {next} ({})",
913                        if tried.contains(&next) { "already tried" } else { "beyond FOOT_TRAVEL" }
914                    ));
915                    return None;
916                }
917                at = next;
918            }
919        }
920    }
921}
922
923/// [`stencil_solve`] on the four nodes `base..base + 4` at their native
924/// abscissae: the Newton (divided-difference) cubic in `t`, searched from
925/// node `j`'s abscissa within one node either side; `Window` carries the
926/// abscissa it left at.
927fn stencil_solve_at(p: Vec3, nodes: &[Vec3], ts: &[f64], base: usize, j: usize, radius: f64) -> Solve {
928    let finite = |v: Vec3| v.x.is_finite() && v.y.is_finite() && v.z.is_finite();
929    let n = nodes.len();
930    if n < 4 || j == 0 || j + 1 >= n || base + 4 > n || ts.len() != n || !radius.is_finite() || !finite(p) || !nodes.iter().all(|&c| finite(c)) {
931        wall_decline(|| format!("native stencil {n} nodes base {base} j {j}: unreadable input"));
932        return Solve::Unread;
933    }
934    let [p0, p1, p2, p3] = [nodes[base], nodes[base + 1], nodes[base + 2], nodes[base + 3]];
935    let [t0, t1, t2, t3] = [ts[base], ts[base + 1], ts[base + 2], ts[base + 3]];
936    let d1 = p1.sub(p0).scale(1.0 / (t1 - t0));
937    let d12 = p2.sub(p1).scale(1.0 / (t2 - t1));
938    let d23 = p3.sub(p2).scale(1.0 / (t3 - t2));
939    let d2 = d12.sub(d1).scale(1.0 / (t2 - t0));
940    let d123 = d23.sub(d12).scale(1.0 / (t3 - t1));
941    let d3 = d123.sub(d2).scale(1.0 / (t3 - t0));
942    let curve = |t: f64| -> (Vec3, Vec3, Vec3) {
943        let (a, b, c) = (t - t0, t - t1, t - t2);
944        let point = p0.add(d1.scale(a)).add(d2.scale(a * b)).add(d3.scale(a * b * c));
945        let first = d1.add(d2.scale(a + b)).add(d3.scale(b * c + a * c + a * b));
946        let second = d2.scale(2.0).add(d3.scale(2.0 * (a + b + c)));
947        (point, first, second)
948    };
949    let (low, high) = (ts[j - 1], ts[j + 1]);
950    let start = ts[j];
951    let mut t = start;
952    let mut converged = false;
953    for _ in 0..50 {
954        let (c, c1, c2) = curve(t);
955        let w = c.sub(p);
956        let (g, slope) = (w.dot(c1), c1.dot(c1) + w.dot(c2));
957        if !(g.is_finite() && slope.is_finite()) || slope == 0.0 {
958            wall_decline(|| format!("native stencil {n} nodes base {base} j {j}: slope {slope:e} g {g:e} at t {t}"));
959            return Solve::Unread;
960        }
961        if g.abs() <= 16.0 * f64::EPSILON * (p.length() + c.length()) * c1.length() {
962            converged = true;
963            break;
964        }
965        let step = g / slope;
966        let before = t;
967        t -= step;
968        if !(t >= low && t <= high) {
969            wall_decline(|| format!("native stencil {n} nodes base {base} j {j}: window exit t {before} -> {t} (window [{low}, {high}])"));
970            return if t.is_finite() { Solve::Window(t) } else { Solve::Unread };
971        }
972        // The uniform reader's stop, 1e-14 · (1 + |s|) in node units from
973        // the stencil's base, read in LOCAL node units here (one node = half
974        // the window), so it does not depend on where the native t's origin
975        // is or on its scale.
976        let node = 0.5 * (high - low);
977        if (step / node).abs() <= 1e-14 * (1.0 + ((t - t0) / node).abs()) {
978            converged = true;
979            break;
980        }
981    }
982    if !converged {
983        wall_decline(|| format!("native stencil {n} nodes base {base} j {j}: no convergence in 50 steps, t {t}"));
984        return Solve::Unread;
985    }
986    let distance = curve(t).0.sub(p).length() - radius;
987    if distance.is_finite() {
988        Solve::Read(distance)
989    } else {
990        wall_decline(|| format!("native stencil {n} nodes base {base} j {j}: non-finite distance at t {t}"));
991        Solve::Unread
992    }
993}
994
995/// The intervals the construction request refines (and, when it cannot,
996/// reports): every interval whose DECLARED wall reading — as
997/// `wall_interior_misses` returns it, `(left, declared, reading,
998/// uncertainty)` — exceeds `model`. The one decision the request makes.
999pub(in crate::blend) fn wall_model_missing(readings: &[(usize, f64, f64, f64)], model: f64) -> Vec<usize> {
1000    readings.iter().filter(|&&(_, declared, _, _)| !(declared <= model)).map(|&(left, _, _, _)| left).collect()
1001}
1002
1003/// Every fillet-wall interval's worst distance from the rolling ball's sweep,
1004/// read on the BUILT wall at the even fractions of the centre stencil
1005/// j/`subdivisions` (the centres at twice the probe density) and section
1006/// angles k/`probes` ([`declared_wall_probe`]), keyed by the interval's left
1007/// sample: each readable interval's largest DECLARED reading, largest reading
1008/// and largest uncertainty, and separately
1009/// every interval with an unreadable reading or a contact the stencil did not
1010/// solve. The wall shares the rails' `u` (one interpolation parameter for its
1011/// three rows).
1012#[allow(clippy::too_many_arguments)]
1013pub(super) fn wall_interior_misses(
1014    contacts: &crate::blend::chain::DenseContacts,
1015    samples: &[ChainSample],
1016    surface: &crate::NurbsSurface,
1017    low: f64,
1018    range: f64,
1019    radius: f64,
1020    subdivisions: usize,
1021    probes: usize,
1022    wrap: bool,
1023) -> (Vec<(usize, f64, f64, f64)>, Vec<usize>) {
1024    use std::collections::BTreeMap;
1025    // Each interval's centres at fractions 0, 1/k, …, 1: its stations at the
1026    // ends, the stencil's solved balls between.
1027    let mut nodes: BTreeMap<usize, (usize, Vec<Option<Vec3>>)> = BTreeMap::new();
1028    for (index, (left, next, fraction, _, _)) in contacts.solved.iter().enumerate() {
1029        let entry = nodes.entry(*left).or_insert_with(|| {
1030            let mut row = vec![None; subdivisions + 1];
1031            row[0] = Some(samples[*left].station.center);
1032            row[subdivisions] = Some(samples[*next].station.center);
1033            (*next, row)
1034        });
1035        let step = (fraction * subdivisions as f64).round() as usize;
1036        if (1..subdivisions).contains(&step) {
1037            entry.1[step] = contacts.centers.get(index).copied().flatten();
1038        }
1039    }
1040    let mut readable = Vec::new();
1041    let mut unreadable: Vec<usize> = contacts.unsolved_at.clone();
1042    for (&left, (next, row)) in &nodes {
1043        let Some(centres) = row.iter().copied().collect::<Option<Vec<Vec3>>>() else {
1044            unreadable.push(left);
1045            continue;
1046        };
1047        let (from, to) = (samples[left].parameter, samples[*next].parameter);
1048        let (mut declared, mut worst, mut spread) = (0.0_f64, 0.0_f64, 0.0_f64);
1049        let mut read = true;
1050        'probes: for step in (2..subdivisions).step_by(2) {
1051            let global = from + (step as f64 / subdivisions as f64) * (to - from);
1052            let global = if wrap { global.rem_euclid(1.0) } else { global };
1053            let u = (global - low) / range;
1054            for k in 1..probes {
1055                let v = k as f64 / probes as f64;
1056                let reading = surface.evaluate(u, v).ok().and_then(|p| {
1057                    declared_wall_probe_located(p, &centres, step, step as f64 / subdivisions as f64, radius)
1058                });
1059                match reading {
1060                    Some((distance, gap)) => {
1061                        declared = declared.max(distance + gap);
1062                        worst = worst.max(distance);
1063                        spread = spread.max(gap);
1064                    }
1065                    None => {
1066                        read = false;
1067                        break 'probes;
1068                    }
1069                }
1070            }
1071        }
1072        if read {
1073            readable.push((left, declared, worst, spread));
1074        } else {
1075            unreadable.push(left);
1076        }
1077    }
1078    unreadable.sort_unstable();
1079    unreadable.dedup();
1080    (readable, unreadable)
1081}
1082
1083
1084fn build_closed_smooth_chain(
1085    solid: &BrepSolid,
1086    segments: &[ChainSegment<'_>],
1087    radius: f64,
1088    chamfer: bool,
1089    name: Option<&str>,
1090    budget: &ChainBudget,
1091    fold: FoldPolicy,
1092    bar: f64,
1093) -> Result<Rung, KernelRefusal> {
1094    let samples = match march_chain_typed(segments, radius, false, budget, fold) {
1095        Ok(samples) => samples,
1096        Err(ChainMarchError::Station(failure)) => return Ok(Rung::StationFailed(failure)),
1097        // Only local refinement reports incoherence; the march never does, so
1098        // this is its refusal exactly as `march_chain` would return it.
1099        Err(ChainMarchError::Incoherent(failure)) => return Err(failure.error),
1100        Err(ChainMarchError::Other(error)) => return Err(error),
1101    };
1102    CHAIN_DEEPEST_ROUND.with(|deepest| deepest.set(0));
1103    build_closed_chain_from_samples(solid, segments, radius, chamfer, name, budget, fold, bar, samples, 0)
1104}
1105
1106
1107thread_local! {
1108    /// The deepest local round any rung of the current ladder step attempted
1109    /// (reset as each rung's march is built from), so a rejected refinement
1110    /// reports the rounds it actually spent.
1111    static CHAIN_DEEPEST_ROUND: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
1112}
1113
1114/// Fit, measure and build one rung from its stations. A plain (non-carve)
1115/// rung whose fitted rails miss the halfway contacts by more than `bar`
1116/// first REFINES LOCALLY: one continuation station into each failing interval
1117/// (`march::insert_chain_stations`), up to [`REFINE_ROUNDS`] rounds and never
1118/// past [`CHAIN_CARVE_MAX_PER_SEGMENT`] stations in a segment, then measures
1119/// again. Only a rung whose local rounds are spent is too coarse, and the
1120/// ladder climbs as it always has. A rung that passes at once is built from
1121/// exactly the stations it was built from before.
1122#[allow(clippy::too_many_arguments)]
1123fn build_closed_chain_from_samples(
1124    solid: &BrepSolid,
1125    segments: &[ChainSegment<'_>],
1126    radius: f64,
1127    chamfer: bool,
1128    name: Option<&str>,
1129    budget: &ChainBudget,
1130    fold: FoldPolicy,
1131    bar: f64,
1132    mut samples: Vec<ChainSample>,
1133    round: usize,
1134) -> Result<Rung, KernelRefusal> {
1135    // The rung, for the traces and the typed reports; each segment's own
1136    // count is the budget's.
1137    let per_segment = budget.rung;
1138    CHAIN_DEEPEST_ROUND.with(|deepest| deepest.set(deepest.get().max(round)));
1139
1140    // Global rows from the in-segment samples (wrapped-overlap closed fit).
1141    let mut global: Vec<(f64, &ChainSample)> = samples
1142        .iter()
1143        .filter(|sample| (0..budget.count(sample.segment) as isize).contains(&sample.position))
1144        .map(|sample| (sample.parameter.rem_euclid(1.0), sample))
1145        .collect();
1146    global.sort_by(|a, b| a.0.total_cmp(&b.0));
1147    let count = global.len();
1148    let degree = FIT_DEGREE;
1149    let mut extended_params = Vec::new();
1150    let mut samples_cr = Vec::new();
1151    let mut samples_mid = Vec::new();
1152    let mut samples_cs = Vec::new();
1153    let mut push = |station: &Station, parameter: f64| {
1154        extended_params.push(parameter);
1155        samples_cr.push(Vec4::from_point(station.p1, 1.0));
1156        samples_cs.push(Vec4::from_point(station.p2, 1.0));
1157        samples_mid.push(Vec4 {
1158            x: station.apex.x * station.weight,
1159            y: station.apex.y * station.weight,
1160            z: station.apex.z * station.weight,
1161            w: station.weight,
1162        });
1163    };
1164    for offset in (1..=degree).rev() {
1165        let (parameter, sample) = &global[count - offset];
1166        push(&sample.station, parameter - 1.0);
1167    }
1168    for (parameter, sample) in &global {
1169        push(&sample.station, *parameter);
1170    }
1171    // Close the loop: repeat the first station at parameter 1, then the
1172    // wrap continuation.
1173    push(&global[0].1.station, global[0].0 + 1.0);
1174    for offset in 1..=degree {
1175        let (parameter, sample) = &global[offset];
1176        push(&sample.station, parameter + 1.0);
1177    }
1178    let low = extended_params[0];
1179    let high = *extended_params.last().unwrap();
1180    let range = high - low;
1181    let normalized: Vec<f64> = extended_params
1182        .iter()
1183        .map(|parameter| (parameter - low) / range)
1184        .collect();
1185    let seam_low = (0.0 - low) / range;
1186    let seam_high = (1.0 - low) / range;
1187    let fit_row = |row: &[Vec4]| -> Result<NurbsCurve, KernelRefusal> {
1188        let curve = fit::interpolate_homogeneous(row, degree, &normalized).or_refuse(KernelStage::Refine, "interpolate_homogeneous")?;
1189        let (_, tail) = curve.split(seam_low).or_refuse(KernelStage::Refine, "split")?;
1190        let (middle, _) = tail.split(seam_high).or_refuse(KernelStage::Refine, "split")?;
1191        Ok(middle)
1192    };
1193    let cr = fit_row(&samples_cr)?;
1194    let cs = fit_row(&samples_cs)?;
1195    let mid = if chamfer {
1196        None
1197    } else {
1198        Some(fit_row(&samples_mid)?)
1199    };
1200    let u_domain = cr.domain().or_refuse(KernelStage::Refine, "domain")?;
1201    let surface = crate::blend::rows::surface_from_rows(degree, &cr, &cs, mid.as_ref(), true)?;
1202
1203    // The CARVE.  A wall that folds carries a lens of parameter inside the
1204    // swept ball volume; the blend is that wall with the lens cut away along
1205    // the crease where the two sheets meet, and the crease becomes an edge of
1206    // the blend with two coedges on this one face.  Traced on the surface that
1207    // was just fitted — not on the ideal envelope — so the two kept sheets
1208    // meet EXACTLY on the curve the trim is built from (`blend/carve.rs`).
1209    // EVERY rung is measured before anything is built on it, carve or not: the
1210    // rails are interpolants through the stations, and between stations they
1211    // can leave the rolling ball's own contacts — and so the carriers they are
1212    // supposed to ride — by far more than the kernel's intersection-fit
1213    // contract. A carve pays for surgery only on an accurate rung; a plain
1214    // collar pays nothing more than the measurement unless it misses.
1215    let (halfway, halfway_failing) = rail_deviation(&samples, &cr, &cs, low, range, budget, bar)?;
1216    // A rung that local insertion has refined is judged on the dense stencil of
1217    // exact contacts, not the halfway point alone (the NX collar's rung 24 read
1218    // 1.943844e-6 halfway and 2.009538e-6 at 7/16 against 2e-6). A rung that
1219    // passes without insertion is read exactly as it always was.
1220    // A FILLET wall is also judged between its rails, on every rung (carve
1221    // included): the rails can pass while the wall's interior — its weighted
1222    // apex row interpolated between stations — stands off the rolling ball
1223    // (the 20-degree seam-split row's accepted wall read 2.4335e-6 on a 16 x 9
1224    // probe grid and 2.4430e-6 at its worst interval's midpoint, mid-section,
1225    // against 2e-6). A chamfer is not a rolling ball and is not judged
1226    // against one. One stencil of exact contacts serves rails and wall.
1227    let stencil = if round > 0 {
1228        Some(crate::blend::chain::chain_dense_contacts(segments, radius, budget, &samples, LOCAL_RAIL_STENCIL)?)
1229    } else {
1230        None
1231    };
1232    // The wall's own centres, at twice the rail stencil's density: its probes
1233    // sit at the rail stencil's fractions, and each is read at both densities
1234    // (`declared_wall_probe`). The rails keep their own stencil, read exactly
1235    // as before.
1236    let wall_stencil = if !chamfer && wall_verdict_on() {
1237        Some(crate::blend::chain::chain_dense_contacts(segments, radius, budget, &samples, 2 * LOCAL_RAIL_STENCIL)?)
1238    } else {
1239        None
1240    };
1241    let (mut deviation, mut failing, _stencil_reads) = if round > 0 {
1242        let contacts = stencil.as_ref().expect("the local path solves the stencil");
1243        let (dense, unreadable) = dense_rail_misses(contacts, &samples, &cr, &cs, low, range, true)?;
1244        let reads = dense.len() + unreadable.len() + contacts.unsolved;
1245        let failed_to_read: Vec<usize> = contacts.unsolved_at.iter().chain(&unreadable).copied().collect();
1246        let verdict = combine_rail_verdict((halfway, halfway_failing), &dense, &failed_to_read, bar);
1247        crate::blend::carve::carve_trace(format_args!(
1248            "blend chain local rails: rung {per_segment} round {round}: stencil verdict {:.9e} (halfway {halfway:.9e}), \
1249             {} failing interval(s), {} unsolved contact(s)",
1250            verdict.0, verdict.1.len(), contacts.unsolved
1251        ));
1252        (verdict.0, verdict.1, reads)
1253    } else {
1254        (halfway, halfway_failing, 0)
1255    };
1256    // The wall between the rails, joined FAIL-CLOSED: an interval whose wall
1257    // reads over `bar`, or cannot be read, fails exactly as a rail interval
1258    // does, and the ladder (local insertion, climb, the existing refusal)
1259    // decides as it always has. Nothing else about the rung changes: a wall
1260    // inside `bar` adds reads, never stations.
1261    let mut _wall_worst = 0.0_f64;
1262    let mut _wall_spread = 0.0_f64;
1263    let mut wall_readings: Vec<(usize, f64, f64, f64)> = Vec::new();
1264    if let Some(contacts) = wall_stencil.as_ref() {
1265        let (wall, unreadable) =
1266            wall_interior_misses(contacts, &samples, &surface, low, range, radius, 2 * LOCAL_RAIL_STENCIL, WALL_SECTION_PROBES, true);
1267        wall_readings = wall.clone();
1268        // Acceptance on the DECLARED reading against the unchanged bar.
1269        for &(left, declared, _, uncertainty) in &wall {
1270            if declared > bar {
1271                failing.push(left);
1272            }
1273            _wall_worst = _wall_worst.max(declared);
1274            _wall_spread = _wall_spread.max(uncertainty);
1275        }
1276        failing.extend_from_slice(&unreadable);
1277        failing.sort_unstable();
1278        failing.dedup();
1279        deviation = deviation.max(_wall_worst);
1280        crate::blend::carve::carve_trace(format_args!(
1281            "blend chain wall: rung {per_segment} round {round}: wall interior worst {_wall_worst:.9e} over {} \
1282             interval(s), {} unreadable, bar {bar:.3e}",
1283            wall.len(),
1284            unreadable.len()
1285        ));
1286    }
1287    if deviation > bar || !failing.is_empty() {
1288        let in_segment = |segment: usize| {
1289            samples
1290                .iter()
1291                .filter(|sample| sample.segment == segment && (0..budget.count(segment) as isize).contains(&sample.position))
1292                .count()
1293        };
1294        let within_ceiling = (0..segments.len()).all(|segment| {
1295            in_segment(segment) + failing.iter().filter(|&&index| samples[index].segment == segment).count()
1296                <= CHAIN_CARVE_MAX_PER_SEGMENT
1297        });
1298        crate::blend::carve::carve_trace(format_args!(
1299            "blend chain local rails: rung {per_segment} round {round} stations {} failing {} \
1300             worst {deviation:.9e} bar {bar:.9e} ceiling {}",
1301            samples.len(),
1302            failing.len(),
1303            if within_ceiling { "ok" } else { "reached" }
1304        ));
1305        if fold != FoldPolicy::Refuse || round >= REFINE_ROUNDS || !within_ceiling {
1306            return Ok(Rung::TooCoarse { deviation });
1307        }
1308        // The insertion is the next round's attempt, whatever it returns.
1309        CHAIN_DEEPEST_ROUND.with(|deepest| deepest.set(deepest.get().max(round + 1)));
1310        match insert_chain_stations(segments, radius, budget, &mut samples, &failing) {
1311            Ok(()) => {}
1312            Err(ChainMarchError::Station(failure)) => return Ok(Rung::StationFailed(failure)),
1313            // An inserted station that does not reach its retained right
1314            // neighbour's branch: this rung cannot be refined coherently, so it
1315            // is as coarse as its measured rails say, and the ladder decides.
1316            Err(ChainMarchError::Incoherent(failure)) => {
1317                crate::blend::carve::carve_trace(format_args!(
1318                    "blend chain local rails: rung {per_segment} round {round}: segment {} station \
1319                     at t {:.9} does not reach its retained right neighbour: {}",
1320                    failure.segment, failure.t, failure.error
1321                ));
1322                return Ok(Rung::TooCoarse { deviation });
1323            }
1324            Err(ChainMarchError::Other(error)) => return Err(error),
1325        }
1326        assign_chain_parameters(&mut samples, segments.len(), budget, false);
1327        return build_closed_chain_from_samples(
1328            solid, segments, radius, chamfer, name, budget, fold, bar, samples, round + 1,
1329        );
1330    }
1331    crate::blend::carve::carve_trace(format_args!(
1332        "blend chain: {per_segment} stations per segment leaves the rails {deviation:.6e} \
1333         from the rolling ball, inside {bar:.3e}"
1334    ));
1335    // The CONSTRUCTION request, plain fillet rungs only: an ACCEPTED wall is
1336    // refined toward `KernelTolerances::model` — the identity tolerance, the
1337    // same 1e-7 the pcurve construction floor refines to under its looser
1338    // acceptance — locally, in exactly the intervals whose DECLARED reading
1339    // (`declared_wall_probe`: the proxy reading plus its uncertainty from
1340    // stencil spread AND change with node density) is over it: one rule for
1341    // the acceptance verdict, this request and its report. An estimate with
1342    // its uncertainty, NOT a proven bound.
1343    // Acceptance is unchanged: the refined rung must pass it again, and every
1344    // retained station, branch guard and ceiling holds. A wall already inside
1345    // `model` everywhere inserts nothing and builds exactly as it did. When
1346    // refinement cannot run or does not build, THIS accepted rung stands and
1347    // says so, typed (`WallModelReport`); never silently.
1348    let model = crate::KernelTolerances::for_solid(solid, 1e-7).model;
1349    let stations_now = (0..segments.len())
1350        .map(|segment| {
1351            samples
1352                .iter()
1353                .filter(|sample| sample.segment == segment && (0..budget.count(segment) as isize).contains(&sample.position))
1354                .count()
1355        })
1356        .max()
1357        .unwrap_or(0);
1358    let mut unmet: Option<(f64, crate::BudgetReason)> = None;
1359    // What the request found short, named in its report: the detail, which
1360    // component the residual is the reading of, and how many intervals it
1361    // could not read (then the true worst is unknown).
1362    let mut request_detail: Option<String> = None;
1363    let mut request_component = crate::MeasuredComponent::Wall;
1364    let mut request_unread = 0usize;
1365    if !chamfer && fold == FoldPolicy::Refuse && model_request_on() {
1366        let wall_missing = wall_model_missing(&wall_readings, model);
1367        // The RAILS against the same request: the shipped support curves ARE
1368        // these rails (`chain_surgery` splits `cr`/`cs` into the support
1369        // edges), so a rail standing off the exact construction ships that
1370        // miss on its carrier. Read against the exact contacts the wall
1371        // reading already solved (j/32 of every interval); an interval whose
1372        // rail reads over `model`, or cannot be read, is short exactly as a
1373        // wall interval is. Acceptance (`bar`) is unchanged.
1374        let (rail_dense, rail_unread) = match wall_stencil.as_ref() {
1375            Some(contacts) => dense_rail_misses(contacts, &samples, &cr, &cs, low, range, true)?,
1376            None => (Vec::new(), Vec::new()),
1377        };
1378        // Per contact, keyed by the interval's left sample: distinct intervals.
1379        let rail_over: std::collections::BTreeSet<usize> =
1380            rail_dense.iter().filter(|&&(_, miss)| !(miss <= model)).map(|&(left, _)| left).collect();
1381        let rail_unread_intervals: std::collections::BTreeSet<usize> = rail_unread.iter().copied().collect();
1382        let rail_missing: Vec<usize> = rail_over.union(&rail_unread_intervals).copied().collect();
1383        let mut missing: Vec<usize> = wall_missing.iter().chain(&rail_missing).copied().collect();
1384        missing.sort_unstable();
1385        missing.dedup();
1386        if !missing.is_empty() {
1387            // The same declared reading, over the intervals short.
1388            let wall_short = wall_readings
1389                .iter()
1390                .filter(|reading| wall_missing.contains(&reading.0))
1391                .map(|&(_, declared, _, _)| declared)
1392                .fold(0.0_f64, f64::max);
1393            let rail_short = rail_dense.iter().filter(|&&(_, miss)| !(miss <= model)).map(|&(_, miss)| miss).fold(0.0_f64, f64::max);
1394            let short = wall_short.max(rail_short);
1395            request_component = if rail_short > wall_short { crate::MeasuredComponent::Rails } else { crate::MeasuredComponent::Wall };
1396            request_unread = rail_unread_intervals.len();
1397            request_detail = Some(format!(
1398                "short of {model:.3e}: rails {rail_short:.3e} off the exact contacts over {} interval(s) ({} unread), \
1399                 wall {wall_short:.3e} over {} interval(s)",
1400                rail_over.len(),
1401                rail_unread_intervals.len(),
1402                wall_missing.len()
1403            ));
1404            let within_ceiling = (0..segments.len()).all(|segment| {
1405                let present = samples
1406                    .iter()
1407                    .filter(|sample| sample.segment == segment && (0..budget.count(segment) as isize).contains(&sample.position))
1408                    .count();
1409                present + missing.iter().filter(|&&index| samples[index].segment == segment).count()
1410                    <= CHAIN_CARVE_MAX_PER_SEGMENT
1411            });
1412            crate::blend::carve::carve_trace(format_args!(
1413                "blend chain wall model: rung {per_segment} round {round}: {} interval(s) short of {model:.3e} \
1414                 (worst reading + spread {short:.3e}), ceiling {}",
1415                missing.len(),
1416                if within_ceiling { "ok" } else { "reached" }
1417            ));
1418            unmet = if round >= model_rounds_limit() {
1419                Some((short, crate::BudgetReason::RoundsSpent))
1420            } else if !within_ceiling {
1421                Some((short, crate::BudgetReason::StationCeiling))
1422            } else {
1423                let mut refined = samples.clone();
1424                // The insertion is the next round's attempt, whatever it returns.
1425                CHAIN_DEEPEST_ROUND.with(|deepest| deepest.set(deepest.get().max(round + 1)));
1426                match insert_chain_stations(segments, radius, budget, &mut refined, &missing) {
1427                    Ok(()) => {
1428                        assign_chain_parameters(&mut refined, segments.len(), budget, false);
1429                        match build_closed_chain_from_samples(
1430                            solid, segments, radius, chamfer, name, budget, fold, bar, refined, round + 1,
1431                        ) {
1432                            Ok(Rung::Built(built)) => return Ok(Rung::Built(built)),
1433                            _ => Some((short, crate::BudgetReason::Rejected)),
1434                        }
1435                    }
1436                    Err(_) => {
1437                        // The insertion itself was the attempt.
1438                        CHAIN_DEEPEST_ROUND.with(|deepest| deepest.set(deepest.get().max(round + 1)));
1439                        Some((short, crate::BudgetReason::Incoherent))
1440                    }
1441                }
1442            };
1443        }
1444    }
1445    // `BREP_DEBUG_CHAIN_DENSE_RAILS=k` (diagnostic only): the accepted rung's
1446    // rails against exact contacts at k-1 interior fractions of every interval,
1447    // where `rail_deviation` reads only the halfway one. Nothing reads it to
1448    // decide.
1449    if let Some(subdivisions) = std::env::var("BREP_DEBUG_CHAIN_DENSE_RAILS").ok().and_then(|value| value.parse::<usize>().ok()) {
1450        let contacts = crate::blend::chain::chain_dense_contacts(segments, radius, budget, &samples, subdivisions.max(2))?;
1451        let mut worst = (0.0_f64, 0usize, 0isize, 0.0_f64);
1452        let mut unreadable = 0usize;
1453        for (left, next, fraction, p1, p2) in &contacts.solved {
1454            let (from, to) = (samples[*left].parameter, samples[*next].parameter);
1455            let global = (from + fraction * (to - from)).rem_euclid(1.0);
1456            let seed = (global - low) / range;
1457            let reach = (to - from).abs() / range;
1458            match rail_pair_miss(closest_on_rail(&cr, *p1, seed, reach).unwrap_or(f64::NAN), closest_on_rail(&cs, *p2, seed, reach).unwrap_or(f64::NAN)) {
1459                Some(miss) if miss > worst.0 => worst = (miss, samples[*left].segment, samples[*left].position, *fraction),
1460                Some(_) => {}
1461                None => unreadable += 1,
1462            }
1463        }
1464        eprintln!(
1465            "blend chain dense rails: rung {per_segment} round {round}: {} contacts at {subdivisions} per interval, \
1466             worst {:.9e} (halfway reading {halfway:.9e}, bar {bar:.3e}) at segment {} position {} fraction {:.4}; {} unsolved, {unreadable} unreadable",
1467            contacts.solved.len(), worst.0, worst.1, worst.2, worst.3, contacts.unsolved
1468        );
1469    }
1470    let crease = if fold == FoldPolicy::Carve {
1471        // THE BAR IS THE KERNEL'S OWN CONTRACT FOR THE QUANTITY MEASURED. What
1472        // the fit is held to is "does this coedge's curve-on-surface, pushed
1473        // back to 3-D, still trace the same locus as the edge's 3-D curve?",
1474        // which is `pcurve_consistency` — not `intersection_fit`, which is the
1475        // accuracy of a fitted intersection CURVE and is what the re-marched
1476        // wall itself is held to (`carve_ladder`). Measured on the
1477        // 2026-09-02 collar the crease's pcurves reach 5.4e-6 against that
1478        // 4e-3, so the margin is three decades and the bar is not what decides
1479        // the case either way.
1480        let bar = crate::KernelTolerances::for_solid(solid, 1e-7).pcurve_consistency;
1481        let Some(carved) = crate::blend::carve::trace_wall_crease(&surface, radius.abs())? else {
1482            return Err(KernelRefusal::internal(KernelStage::Refine, "carve_no_fold", format!(
1483                "blend carve: the wall re-marched at {per_segment} stations per segment carries                  no fold to carve, but the march at {CHAIN_PER_SEGMENT} refused one"
1484            )));
1485        };
1486        Some(crate::blend::carve::fit_crease(&surface, &carved, bar)?)
1487    } else {
1488        None
1489    };
1490
1491    // Single-face sides (e.g. one cap around the whole rim) get ONE uv
1492    // fit in the rows' own normalized space — identical parameterization
1493    // to cr/cs, so the closed support piece and its pcurve agree exactly.
1494    let single_face = |side: usize| -> bool {
1495        let first_id = segments[0].mate(side).face.id;
1496        segments
1497            .iter()
1498            .all(|segment| segment.mate(side).face.id == first_id)
1499    };
1500    let mut whole_pcurves: [Option<NurbsCurve>; 2] = [None, None];
1501    for side in 0..2 {
1502        if !single_face(side) {
1503            continue;
1504        }
1505        let mut row = Vec::new();
1506        let select = |station: &Station| -> [f64; 2] {
1507            if side == 0 {
1508                station.uv1
1509            } else {
1510                station.uv2
1511            }
1512        };
1513        let mut push_uv = |station: &Station| {
1514            let uv = select(station);
1515            row.push(Vec4::from_point(Vec3::new(uv[0], uv[1], 0.0), 1.0));
1516        };
1517        for offset in (1..=degree).rev() {
1518            push_uv(&global[count - offset].1.station);
1519        }
1520        for (_, sample) in &global {
1521            push_uv(&sample.station);
1522        }
1523        push_uv(&global[0].1.station);
1524        for offset in 1..=degree {
1525            push_uv(&global[offset].1.station);
1526        }
1527        whole_pcurves[side] = Some(fit_row(&row)?);
1528    }
1529
1530    // Per-face pcurve fits over each segment's full sample window
1531    // (in-segment + overshoot), in global parameters.
1532    let mut pcurves1 = Vec::with_capacity(segments.len());
1533    let mut pcurves2 = Vec::with_capacity(segments.len());
1534    for segment in 0..segments.len() {
1535        let mut window: Vec<&ChainSample> = samples
1536            .iter()
1537            .filter(|sample| sample.segment == segment)
1538            .collect();
1539        window.sort_by(|a, b| a.parameter.total_cmp(&b.parameter));
1540        let params: Vec<f64> = window.iter().map(|sample| sample.parameter).collect();
1541        let low = params[0];
1542        let high = *params.last().unwrap();
1543        let normalized: Vec<f64> = params
1544            .iter()
1545            .map(|parameter| (parameter - low) / (high - low))
1546            .collect();
1547        let fit_uv = |select: &dyn Fn(&Station) -> [f64; 2]| -> Result<NurbsCurve, KernelRefusal> {
1548            let points: Vec<Vec4> = window
1549                .iter()
1550                .map(|sample| {
1551                    let uv = select(&sample.station);
1552                    Vec4::from_point(Vec3::new(uv[0], uv[1], 0.0), 1.0)
1553                })
1554                .collect();
1555            let curve = fit::interpolate_homogeneous(&points, degree, &normalized).or_refuse(KernelStage::Refine, "interpolate_homogeneous")?;
1556            // Re-express in GLOBAL parameters: the curve's [0,1] domain
1557            // corresponds to [low, high]; keep as-is and remember the
1558            // affine map through the window bounds.
1559            Ok(curve)
1560        };
1561        pcurves1.push((low, high, fit_uv(&|station| station.uv1)?));
1562        pcurves2.push((low, high, fit_uv(&|station| station.uv2)?));
1563    }
1564
1565    let wall_surface = unmet.as_ref().map(|_| surface.clone());
1566    let built = chain_surgery(
1567        solid,
1568        segments,
1569        ChainRows {
1570            surface,
1571            cr,
1572            cs,
1573            u_domain,
1574            fit_low: low,
1575            fit_range: range,
1576            pcurves1,
1577            pcurves2,
1578            whole_pcurves,
1579            crease,
1580        },
1581        name,
1582    )?;
1583    if let (Some((residual, reason)), Some(surface)) = (unmet, wall_surface) {
1584        crate::blend::record_wall_model_report(crate::blend::WallModelReport {
1585            name: name.map(str::to_string),
1586            surface,
1587            request: model,
1588            residual,
1589            detail: request_detail,
1590            budget: crate::ApproximationBudget {
1591                reason,
1592                // Rounds ATTEMPTED (the deepest any refinement of this rung
1593                // reached); the stations and residual are this shipped rung's.
1594                rounds_used: CHAIN_DEEPEST_ROUND.with(|deepest| deepest.get()).max(round),
1595                rounds_limit: model_rounds_limit(),
1596                stations: stations_now,
1597                station_limit: CHAIN_CARVE_MAX_PER_SEGMENT,
1598                mechanism: crate::BudgetMechanism::LocalRounds,
1599                measured_component: request_component,
1600                unread: request_unread,
1601            },
1602        });
1603    }
1604    Ok(Rung::Built(built))
1605}
1606
1607pub(super) struct ChainRows {
1608    pub(super) surface: NurbsSurface,
1609    pub(super) cr: NurbsCurve,
1610    pub(super) cs: NurbsCurve,
1611    pub(super) u_domain: [f64; 2],
1612    /// Affine map from the rows' fit space to GLOBAL chain parameters:
1613    /// global = fit_low + p * fit_range (the global fit normalised its
1614    /// wrap-extended parameters onto [0, 1] before interpolating).
1615    pub(super) fit_low: f64,
1616    pub(super) fit_range: f64,
1617    /// Per-segment (window_low, window_high, uv curve over [0,1]) in
1618    /// GLOBAL parameters.
1619    pub(super) pcurves1: Vec<(f64, f64, NurbsCurve)>,
1620    pub(super) pcurves2: Vec<(f64, f64, NurbsCurve)>,
1621    /// Whole-chain uv fits (rows' fit space) for single-face sides.
1622    pub(super) whole_pcurves: [Option<NurbsCurve>; 2],
1623    /// The crease a CARVED wall is cut along: the inner loop of the blend
1624    /// face, one edge carrying two coedges of that same face.
1625    pub(super) crease: Option<crate::blend::carve::CarvedCrease>,
1626}
1627
1628impl ChainRows {
1629    pub(super) fn to_global(&self, fit_parameter: f64) -> f64 {
1630        self.fit_low + fit_parameter * self.fit_range
1631    }
1632}
1633
1634/// Extract the pcurve portion for a global window [a, b] from a
1635/// segment's fitted uv curve, re-parameterised so its domain maps
1636/// affinely onto the portion (matching the split support edge).
1637pub(super) fn pcurve_portion(fitted: &(f64, f64, NurbsCurve), a: f64, b: f64) -> Result<NurbsCurve, KernelRefusal> {
1638    let (low, high, curve) = fitted;
1639    // The chain parameterisation wraps with period 1; pick the period
1640    // copy of the window that overlaps this segment's fit range.
1641    let to_local = |value: f64| {
1642        let mut best = value;
1643        for candidate in [value, value + 1.0, value - 1.0] {
1644            if candidate >= low - 1e-9 && candidate <= high + 1e-9 {
1645                best = candidate;
1646                break;
1647            }
1648        }
1649        ((best - low) / (high - low)).clamp(0.0, 1.0)
1650    };
1651    let mut local_a = to_local(a);
1652    let mut local_b = to_local(b);
1653    if local_a > local_b {
1654        std::mem::swap(&mut local_a, &mut local_b);
1655    }
1656    let epsilon = 1e-9;
1657    let (_, tail) = if local_a > epsilon {
1658        curve.split(local_a).or_refuse(KernelStage::Refine, "split")?
1659    } else {
1660        (curve.clone(), curve.clone())
1661    };
1662    let tail = if local_a > epsilon {
1663        tail
1664    } else {
1665        curve.clone()
1666    };
1667    let portion = if local_b < 1.0 - epsilon {
1668        tail.split(local_b).or_refuse(KernelStage::Refine, "split")?.0
1669    } else {
1670        tail
1671    };
1672    Ok(portion)
1673}
1674
1675/// The single boundary edge of a side's mate face(s) incident to a chain
1676/// vertex, EXCLUDING the two chain edges meeting there — i.e. the SEAM
1677/// (same face on both segments) or the SPOKE (face changes) that the
1678/// support curve must cross.  `None` when the chain simply flows through
1679/// (same face, no seam — e.g. a stadium cap rim vertex).
1680pub(super) fn cross_edge_at(
1681    solid: &BrepSolid,
1682    face_before: &FaceRecord,
1683    loop_before: usize,
1684    face_after: &FaceRecord,
1685    loop_after: usize,
1686    vertex: u64,
1687    before_edge: u64,
1688    after_edge: u64,
1689) -> Result<Option<u64>, KernelRefusal> {
1690    let mut found: Option<u64> = None;
1691    let mut consider = |face: &FaceRecord, loop_index: usize| -> Result<(), KernelRefusal> {
1692        for coedge in &face.loops[loop_index].coedges {
1693            if coedge.edge_id == before_edge || coedge.edge_id == after_edge {
1694                continue;
1695            }
1696            let edge = solid
1697                .edges
1698                .iter()
1699                .find(|edge| edge.id == coedge.edge_id)
1700                .ok_or(KernelRefusal::internal(KernelStage::Refine, "missing_edge", "blend: loop references missing edge"))?;
1701            if edge.start_vertex_id == vertex || edge.end_vertex_id == vertex {
1702                if found.is_some() && found != Some(edge.id) {
1703                    return Err(KernelRefusal::unsupported(KernelStage::Classify, "chain_cross_edges", "blend: multiple cross edges at a chain vertex"));
1704                }
1705                found = Some(edge.id);
1706            }
1707        }
1708        Ok(())
1709    };
1710    consider(face_before, loop_before)?;
1711    if !(face_after.id == face_before.id && loop_after == loop_before) {
1712        consider(face_after, loop_after)?;
1713    }
1714    Ok(found)
1715}
1716
1717/// The nearest periodic image of a seam boundary to `value` — the boundary a
1718/// crossing endpoint must be pinned to, read off its ADJACENT INTERIOR sample.
1719fn nearest_seam_image(value: f64, low: f64, high: f64) -> f64 {
1720    let period = high - low;
1721    low + ((value - low) / period).round() * period
1722}
1723
1724/// The mate-face pcurve of a support PIECE, built by projecting the piece's
1725/// 3D curve onto the (analytic) mate surface and fitting.  Because a chain
1726/// support rides EXACTLY on its mate carrier, the projection is exact; the
1727/// periodic track is unwrapped so it never jumps a period across the carrier
1728/// seam, and crossing endpoints are pinned ONTO the seam so the piece and the
1729/// trimmed seam edge close the loop in parameter space.
1730///
1731/// The carrier is closed in EITHER direction, and the crossed seam is not
1732/// always the u meridian.  A cylinder/cone/sphere is closed in u only, so its
1733/// seam is the u0 ≡ u1 meridian — but a TORUS is biperiodic, and a tube welded
1734/// through a plane crosses the torus' v0 ≡ v1 PARALLEL (its outer equator)
1735/// instead.  Pinning u there drags the endpoint a half-carrier away from its
1736/// own 3D vertex (the collar loop then closes on a uv corner that is nowhere
1737/// near the vertex, and the mate face's trim is garbage), so the pinned AXIS is
1738/// chosen GEOMETRICALLY: whichever closed direction's seam actually passes
1739/// through the endpoint.  A carrier closed in u only can only ever take the u
1740/// branch, exactly as before.
1741pub(in crate::blend) fn project_piece_pcurve(
1742    piece: &NurbsCurve,
1743    surface: &NurbsSurface,
1744    start_is_crossing: bool,
1745    end_is_crossing: bool,
1746) -> Result<NurbsCurve, KernelRefusal> {
1747    project_piece_pcurve_from(piece, surface, start_is_crossing, end_is_crossing, None)
1748}
1749
1750
1751/// A trim pcurve `old` as a GUIDE for [`project_piece_pcurve_from`]: at track
1752/// fraction `f` its uv at the same fraction of its own domain, closed
1753/// directions wrapped and open ones clamped into `surface`'s domain; `None`
1754/// where that uv is not finite.
1755pub(in crate::blend) fn pcurve_guide<'a>(old: &'a NurbsCurve, surface: &NurbsSurface) -> Result<impl Fn(f64) -> Option<[f64; 2]> + 'a, KernelRefusal> {
1756    let [o0, o1] = old.domain().or_refuse(KernelStage::Refine, "domain")?;
1757    let (closed_u, closed_v) = surface.closed_directions().or_refuse(KernelStage::Refine, "closed_directions")?;
1758    let [u0, u1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
1759    let [v0, v1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
1760    let into = move |value: f64, closed: bool, low: f64, high: f64| {
1761        if closed && high > low {
1762            low + (value - low).rem_euclid(high - low)
1763        } else {
1764            value.clamp(low, high)
1765        }
1766    };
1767    Ok(move |fraction: f64| {
1768        let uv = old.evaluate(o0 + (o1 - o0) * fraction).ok()?;
1769        let seed = [into(uv.x, closed_u, u0, u1), into(uv.y, closed_v, v0, v1)];
1770        seed.iter().all(|value| value.is_finite()).then_some(seed)
1771    })
1772}
1773
1774/// [`project_piece_pcurve`], every sample's foot sought from `guide` at its
1775/// fraction (a uv on the branch the piece is known to run on — a support
1776/// trim's own march pcurve) instead of from its neighbour's foot. A foot
1777/// continued from its neighbour stays wherever that neighbour's did: at a
1778/// STATIONARY edge of the carrier (an extrusion of a profile whose derivative
1779/// vanishes at its start, S_u = 0 along u = 0) a foot seeded on that edge has
1780/// no gradient to leave it by, and every later sample stayed there with it
1781/// (the stationary-start prism's refit: one foot, [0, 0.9], for the whole
1782/// rail, 6.0 off it at the far end). Without a guide, as before.
1783pub(in crate::blend) fn project_piece_pcurve_from(
1784    piece: &NurbsCurve,
1785    surface: &NurbsSurface,
1786    start_is_crossing: bool,
1787    end_is_crossing: bool,
1788    guide: Option<&dyn Fn(f64) -> Option<[f64; 2]>>,
1789) -> Result<NurbsCurve, KernelRefusal> {
1790    project_piece_pcurve_within(piece, surface, start_is_crossing, end_is_crossing, guide, &[])
1791}
1792
1793/// [`project_piece_pcurve_from`], its track first refined (two bisections)
1794/// in the dense interval holding each fraction of `refine_near` — where a
1795/// caller that read the SHIPPED curve again found it over the floor between
1796/// the fitter's own stencil points — and then fitted at the same floor.
1797pub(in crate::blend) fn project_piece_pcurve_within(
1798    piece: &NurbsCurve,
1799    surface: &NurbsSurface,
1800    start_is_crossing: bool,
1801    end_is_crossing: bool,
1802    guide: Option<&dyn Fn(f64) -> Option<[f64; 2]>>,
1803    refine_near: &[f64],
1804) -> Result<NurbsCurve, KernelRefusal> {
1805    let [u0, u1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
1806    let [v0, v1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
1807    let (_, closed_v) = surface.closed_directions().or_refuse(KernelStage::Refine, "closed_directions")?;
1808    let period = u1 - u0;
1809    let period_v = v1 - v0;
1810    let [d0, d1] = piece.domain().or_refuse(KernelStage::Refine, "domain")?;
1811    // Dense reference projection; the pcurve is fit from an adaptively
1812    // thinned subset kept as coarse as tolerance allows.
1813    //
1814    // The track is as dense as the PIECE is, not a fixed 96. A carved wall's
1815    // re-marched rail carries hundreds of control points (574 on the
1816    // 2026-09-02 two-torus collar), and a pcurve fitted and checked at 97
1817    // samples of it was accepted 1.19e-3 off the rail it claims.
1818    let dense = (8 * piece.control_points.len()).clamp(96, PIECE_TRACK_CEILING);
1819    crate::blend::record_count("count.track.piece_samples", dense as u64 + 1);
1820    let mut proj_u = Vec::with_capacity(dense + 1);
1821    let mut proj_v = Vec::with_capacity(dense + 1);
1822    let mut point3 = Vec::with_capacity(dense + 1);
1823    let mut params = Vec::with_capacity(dense + 1);
1824    let mut previous_u: Option<f64> = None;
1825    // The foot each sample continues from: the track is ONE branch of the
1826    // carrier, so after the first sample each inversion starts from its
1827    // neighbour's foot rather than asking for the nearest point of the whole
1828    // surface, which on a carrier that comes back near itself is another sheet.
1829    let mut previous_foot: Option<[f64; 2]> = None;
1830    let mut feet: Vec<[f64; 2]> = Vec::with_capacity(dense + 1);
1831    for section in 0..=dense {
1832        let t = d0 + (d1 - d0) * section as f64 / dense as f64;
1833        let point = piece.evaluate(t).or_refuse(KernelStage::Refine, "evaluate")?;
1834        let guide = guide.filter(|_| {
1835            true
1836        });
1837        // The guide's seed and the neighbour's foot are both tried and the
1838        // NEARER foot kept: the seeded projector is local and returns a
1839        // stalled Newton's iterate without saying so, so neither seed is
1840        // trusted alone (a foot on its carrier is nearer than any stall).
1841        let seeds: Vec<[f64; 2]> = guide.and_then(|guide| guide(section as f64 / dense as f64)).into_iter().chain(previous_foot).collect();
1842        let mut projection: Option<crate::projection::SurfaceProjection> = None;
1843        for seed in &seeds {
1844            let found = crate::projection::project_point_to_surface_from_seed(surface, point, *seed).or_refuse(KernelStage::Refine, "project_point_to_surface_from_seed")?;
1845            if projection.as_ref().map_or(true, |known| found.distance < known.distance) {
1846                projection = Some(found);
1847            }
1848        }
1849        let projection = match projection {
1850            Some(projection) => projection,
1851            None => crate::project_point_to_surface(surface, point).or_refuse(KernelStage::Refine, "project_point_to_surface")?,
1852        };
1853        previous_foot = Some([projection.u, projection.v]);
1854        feet.push([projection.u, projection.v]);
1855        let mut u = projection.u;
1856        if let Some(previous) = previous_u {
1857            while u - previous > period * 0.5 {
1858                u -= period;
1859            }
1860            while previous - u > period * 0.5 {
1861                u += period;
1862            }
1863        }
1864        previous_u = Some(u);
1865        proj_u.push(u);
1866        proj_v.push(projection.v);
1867        point3.push(point);
1868        params.push(section as f64 / dense as f64);
1869    }
1870    // A biperiodic carrier needs the SAME unwrap in v, or a track that walks up
1871    // to the v seam comes back as 0 at the far end and the fit swings across
1872    // the whole carrier.  Anchor the chain on the first INTERIOR sample and
1873    // pull index 0 onto it afterwards: an endpoint sitting exactly ON the seam
1874    // inverts to either boundary at the solver's whim, and letting that
1875    // coin flip anchor the chain would shift the whole piece a period.
1876    if closed_v {
1877        for index in 2..=dense {
1878            while proj_v[index] - proj_v[index - 1] > period_v * 0.5 {
1879                proj_v[index] -= period_v;
1880            }
1881            while proj_v[index - 1] - proj_v[index] > period_v * 0.5 {
1882                proj_v[index] += period_v;
1883            }
1884        }
1885        while proj_v[0] - proj_v[1] > period_v * 0.5 {
1886            proj_v[0] -= period_v;
1887        }
1888        while proj_v[1] - proj_v[0] > period_v * 0.5 {
1889            proj_v[0] += period_v;
1890        }
1891    }
1892    // Interior mean decides which meridian a crossing endpoint sits on, and
1893    // whether the whole track needs a full-period shift into the domain.
1894    let mean_u: f64 = proj_u[1..dense].iter().sum::<f64>() / (dense - 1) as f64;
1895    let seam_u = if mean_u - u0 > u1 - mean_u { u1 } else { u0 };
1896    // Pin a crossing endpoint onto the seam it actually crosses.  Each
1897    // candidate keeps the OTHER coordinate as projected, so the losing axis
1898    // moves the endpoint bodily off its own 3D vertex — the residual names the
1899    // winner with no surface-type special-casing.  u keeps the whole-track
1900    // mean; a v run can legitimately cover a full period, so its boundary is
1901    // read off the ADJACENT INTERIOR sample instead.  Both pins land in the
1902    // track's own (pre-shift) period, so the shift below carries them along
1903    // with the rest of the track.
1904    let pinned = |index: usize,
1905                  neighbour: usize,
1906                  proj_u: &[f64],
1907                  proj_v: &[f64]|
1908     -> Result<(f64, f64), KernelRefusal> {
1909        let point = point3[index];
1910        let u_error = surface
1911            .evaluate(seam_u, proj_v[index]).or_refuse(KernelStage::Refine, "evaluate")?
1912            .sub(point)
1913            .length();
1914        if closed_v {
1915            let seam_v = nearest_seam_image(proj_v[neighbour], v0, v1);
1916            let v_error = surface
1917                .evaluate(proj_u[index], seam_v).or_refuse(KernelStage::Refine, "evaluate")?
1918                .sub(point)
1919                .length();
1920            if v_error < u_error {
1921                return Ok((proj_u[index], seam_v));
1922            }
1923        }
1924        Ok((seam_u, proj_v[index]))
1925    };
1926    if start_is_crossing {
1927        let (u, v) = pinned(0, 1, &proj_u, &proj_v)?;
1928        proj_u[0] = u;
1929        proj_v[0] = v;
1930    }
1931    if end_is_crossing {
1932        let (u, v) = pinned(dense, dense - 1, &proj_u, &proj_v)?;
1933        proj_u[dense] = u;
1934        proj_v[dense] = v;
1935    }
1936    let mut shift = 0.0;
1937    while mean_u + shift > u1 + 1e-9 {
1938        shift -= period;
1939    }
1940    while mean_u + shift < u0 - 1e-9 {
1941        shift += period;
1942    }
1943    for u in proj_u.iter_mut() {
1944        *u += shift;
1945    }
1946    if closed_v {
1947        let mean_v: f64 = proj_v[1..dense].iter().sum::<f64>() / (dense - 1) as f64;
1948        let mut shift_v = 0.0;
1949        while mean_v + shift_v > v1 + 1e-9 {
1950            shift_v -= period_v;
1951        }
1952        while mean_v + shift_v < v0 - 1e-9 {
1953            shift_v += period_v;
1954        }
1955        for v in proj_v.iter_mut() {
1956            *v += shift_v;
1957        }
1958    }
1959    // Coarsest interpolation whose fitted pcurve reproduces the projected TRACK
1960    // to the kernel's pcurve refinement floor — the floor every other pcurve fit
1961    // is held to, and the one the shell vector-area bar is built from.
1962    //
1963    // It used to stop at 0.002 absolute ("half the validator's tolerance"),
1964    // which is `pcurve_consistency / 2` on any model under 20 units and so four
1965    // decades looser than any other trim: a torus face carrying a collar's rail
1966    // was accepted 1.19e-3 off it, and that one coedge was the whole of a
1967    // 1.94e-4 shell vector-area residual, 14.5x its face's bar.
1968    //
1969    // The deviation is measured against the projected track, not the piece's
1970    // 3D points: a pcurve lies on its surface by construction, so a piece that
1971    // is itself off the carrier would hold the ladder at its top however fine
1972    // the fit, and that is a RAIL defect for the march to answer, not this fit.
1973    let tolerance = crate::pcurve::PCURVE_REFINEMENT_TOLERANCE;
1974    // The ladder is the one verified fitter (`track_fit`), which checks every
1975    // rung at the MIDPOINTS between dense samples, projected on their own —
1976    // checked at the dense samples, the top rung, which interpolates every one
1977    // of them, reads zero by construction and passes whatever lies between (on
1978    // the 574-control-point collar rail it read 9.6e-15) — and breaks the fit,
1979    // C1, where the track crosses a knot line of the carrier.
1980    let at = |fraction: f64| piece.evaluate(d0 + (d1 - d0) * fraction);
1981    let mut track = crate::blend::track_fit::Track {
1982        fractions: params,
1983        uv: proj_u.iter().zip(&proj_v).map(|(u, v)| [*u, *v]).collect(),
1984        feet,
1985        breaks: Vec::new(),
1986    };
1987    let breaks = crate::blend::track_fit::curve_breaks(piece, d0, d1, true);
1988    crate::blend::track_fit::polish_track(surface, &at, &mut track)?;
1989    crate::blend::track_fit::insert_knot_crossings(surface, &at, &mut track, &breaks)?;
1990    for &fraction in refine_near {
1991        for _ in 0..2 {
1992            let Some(index) = track.fractions.windows(2).position(|pair| pair[0] <= fraction && fraction <= pair[1]) else { break };
1993            if crate::blend::track_fit::refine_track_intervals(surface, &at, &mut track, &[index], PIECE_TRACK_CEILING)?.is_none() {
1994                break;
1995            }
1996        }
1997    }
1998    // Where the top rung still misses the floor, the track is refined ONLY in
1999    // the dense intervals whose out-of-sample checks missed (local insertion
2000    // concentrates the rail's stations, and a uniform dense track can leave one
2001    // sample per knot span where the rail turns hardest), within the dense
2002    // ceiling the track was built under; every other sample, the pinned ends
2003    // and the breaks are kept. The floor and the refusal below are unchanged.
2004    let (fit, inserted) = crate::blend::track_fit::fit_track_locally(
2005        surface,
2006        &mut track,
2007        &at,
2008        tolerance,
2009        REFINE_ROUNDS,
2010        PIECE_TRACK_CEILING,
2011    )?;
2012    if std::env::var("BREP_DEBUG_TRACK_FIT").is_ok() {
2013        eprintln!("TRACK_FIT closed-chain piece: {inserted} sample(s) inserted locally");
2014        eprintln!(
2015            "TRACK_FIT closed-chain piece: miss {:.3e} at {} samples of {} ({} breaks), on floor {}",
2016            fit.miss,
2017            fit.samples,
2018            track.fractions.len(),
2019            track.breaks.len(),
2020            fit.on_floor
2021        );
2022        // Where the miss sits, against what the piece is made of: an endpoint
2023        // pinned onto a seam reads at fraction 0 or 1; an interior miss names
2024        // the piece's knot span there and how that span compares with the
2025        // piece's mean (local insertion clusters spans at the crotch).
2026        if let Some(worst) = fit.worst {
2027            let parameter = d0 + (d1 - d0) * worst.fraction;
2028            let spans: Vec<[f64; 2]> = piece.knots.windows(2)
2029                .filter(|pair| pair[1] > pair[0] && pair[1] > d0 && pair[0] < d1)
2030                .map(|pair| [pair[0], pair[1]]).collect();
2031            let mean = (d1 - d0) / spans.len().max(1) as f64;
2032            let span = spans.iter().position(|pair| parameter >= pair[0] && parameter <= pair[1]);
2033            let width = span.map(|index| spans[index][1] - spans[index][0]);
2034            eprintln!(
2035                "TRACK_FIT closed-chain worst: fraction {:.9} parameter {:.9} (pinned start {start_is_crossing}, pinned end {end_is_crossing}) \
2036                 knot span {:?} of {} width {:?} vs mean {:.3e}; dense bracket [{:.9}, {:.9}] (interval {})",
2037                worst.fraction, parameter, span, spans.len(), width, mean,
2038                worst.bracket[0], worst.bracket[1], worst.interval
2039            );
2040        }
2041    }
2042    // A fit off the floor is refused, not handed on as the best rung. Before the
2043    // fitter broke at knot lines this ladder returned its best rung silently,
2044    // and on the 2026-09-02 two-torus collar that was 1.07e-7 to 8.46e-7 off at
2045    // the top rung on every collar that builds (measured 2026-09-17); broken,
2046    // each of them reaches the floor. What still misses is a rail the march tore
2047    // (2.7 to 3.3 on the oversized radii, a body refused or rejected anyway).
2048    if !fit.on_floor {
2049        return Err(KernelRefusal::non_convergence(KernelStage::Refine, "chain_pcurve_floor", format!(
2050            "{} a closed chain's support piece misses its pcurve by {:.3e} at {} samples, \
2051             against a floor of {tolerance:.1e}",
2052            crate::blend::PCURVE_OFF_FLOOR,
2053            fit.miss,
2054            fit.samples
2055        )));
2056    }
2057    Ok(fit.curve)
2058}