brep_kernel/geometry/pcurve.rs
1use crate::{
2 interpolate_curve, project_point_to_surface, project_point_to_surface_seeded, AnalyticSurface,
3 DiagnosticSeverity, KernelDiagnostics, KernelStage, KnotVector, NurbsCurve, NurbsSurface, Vec3,
4 Vec4, KNOT_IDENTITY_TOL,
5};
6
7const EPSILON: f64 = crate::tolerance::FP_FLOOR;
8const LINEAR_TOLERANCE: f64 = crate::tolerance::DEFAULT_MODEL;
9
10/// Coarse clocks only: no per-station timing overhead in a measured fit.
11struct PcurvePhaseClock {
12 started: Option<web_time::Instant>,
13 phase: &'static str,
14 samples: usize,
15 tracking: bool,
16}
17impl PcurvePhaseClock {
18 fn new(phase: &'static str, samples: usize, tracking: bool) -> Self {
19 Self { started: crate::profile::profile_enabled().then(web_time::Instant::now), phase, samples, tracking }
20 }
21}
22impl Drop for PcurvePhaseClock {
23 fn drop(&mut self) {
24 if let Some(started) = self.started {
25 crate::imprint::profile_emit(format_args!("pcurve.phase_time phase={} initial_samples={} tracking={} ms={:.3}", self.phase, self.samples, self.tracking, started.elapsed().as_secs_f64()*1000.0));
26 }
27 }
28}
29
30fn surface_domains(surface: &NurbsSurface) -> Result<([f64; 2], [f64; 2]), String> {
31 Ok((
32 KnotVector::new(surface.knots_u.clone(), surface.degree_u)?.domain(),
33 KnotVector::new(surface.knots_v.clone(), surface.degree_v)?.domain(),
34 ))
35}
36
37/// The carrier's closed directions, as [`NurbsSurface::closed_directions`]
38/// reads them (cached on the surface, seam tolerance 1e-6, and a sliver patch
39/// whose rows coincide without the surface travelling between them is OPEN).
40/// Until 2026-09-26 this was a second reading with its own tolerance; the
41/// unwrap in [`build_interpolant`] and the wrap in `evaluate_extended` must
42/// agree on what a seam is, or a trim across a sliver reads a period wide.
43fn surface_closedness(surface: &NurbsSurface) -> Result<(bool, bool), String> {
44 surface.closed_directions()
45}
46
47thread_local! {
48 static PCURVE_REJECT_BOUNDS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
49}
50struct PcurveRejectScope(bool);
51impl PcurveRejectScope {
52 fn open(enabled: bool) -> Self {
53 Self(PCURVE_REJECT_BOUNDS.with(|state| state.replace(enabled)))
54 }
55}
56impl Drop for PcurveRejectScope {
57 fn drop(&mut self) { PCURVE_REJECT_BOUNDS.with(|state| state.set(self.0)); }
58}
59
60thread_local! {
61 static PCURVE_STACK_POINT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
62}
63struct PcurveStackPointScope(bool);
64impl PcurveStackPointScope {
65 fn open(enabled: bool) -> Self { Self(PCURVE_STACK_POINT.with(|state| state.replace(enabled))) }
66}
67impl Drop for PcurveStackPointScope {
68 fn drop(&mut self) { PCURVE_STACK_POINT.with(|state| state.set(self.0)); }
69}
70
71/// The count-zero basis recurrence has the same operations as the value
72/// basis recurrence. Its homogeneous sum uses the same row/column order.
73/// Preserve derivative dehomogenization (multiply by reciprocal, not divide)
74/// while avoiding both the derivative grids and their allocation. Other
75/// degrees and open-direction extension keep the original API. Periodic
76/// wrap retains the original parameter arithmetic before value evaluation.
77fn section_extended_point(surface: &NurbsSurface, u: f64, v: f64, enabled: bool) -> Result<Vec3, String> {
78 if enabled && u.is_finite() && v.is_finite()
79 && surface.degree_u <= crate::curve::MAX_STACK_DEGREE
80 && surface.degree_v <= crate::curve::MAX_STACK_DEGREE {
81 let [u0,u1] = surface.domain_u()?;
82 let [v0,v1] = surface.domain_v()?;
83 let mut uu = u;
84 let mut vv = v;
85 if u < u0 || u > u1 || v < v0 || v > v1 {
86 let (closed_u, closed_v) = surface_closedness(surface)?;
87 // Identical to derivatives_extended: keep the original add,
88 // subtract and remainder order before taking the count-zero path.
89 if closed_u && (u < u0 || u > u1) { uu = u0 + (u - u0).rem_euclid(u1 - u0); }
90 if closed_v && (v < v0 || v > v1) { vv = v0 + (v - v0).rem_euclid(v1 - v0); }
91 }
92 if uu >= u0 && uu <= u1 && vv >= v0 && vv <= v1 {
93 let point = surface.evaluate_homogeneous(uu, vv)?;
94 if point.w.abs() <= EPSILON {
95 return Err("NurbsSurface: zero evaluated weight".into());
96 }
97 return Ok(Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w));
98 }
99 }
100 surface.evaluate_extended(u,v)
101}
102
103/// One immutable carrier/section pair; nested fits own separate maps. Keys
104/// preserve all parameter bits, and failed evaluations are never retained.
105struct PcurveSampleCache {
106 surface: usize,
107 curve: usize,
108 curves: rustc_hash::FxHashMap<u64, Vec3>,
109 surfaces: rustc_hash::FxHashMap<(u64, u64, bool, bool), Vec3>,
110 inversions: rustc_hash::FxHashMap<[u64; 3], ([f64; 2], f64)>,
111 requests: [usize; 4],
112 hits: [usize; 4],
113 speeds: rustc_hash::FxHashMap<(u64, u64), f64>,
114}
115thread_local! {
116 static PCURVE_SAMPLES: std::cell::RefCell<Vec<Option<PcurveSampleCache>>> = const { std::cell::RefCell::new(Vec::new()) };
117}
118struct PcurveSampleScope<'a>(std::marker::PhantomData<(&'a NurbsSurface, &'a NurbsCurve)>);
119impl<'a> PcurveSampleScope<'a> {
120 fn open(surface: &'a NurbsSurface, curve: &'a NurbsCurve, enabled: bool) -> Self {
121 let cache = enabled
122 .then(|| PcurveSampleCache { surface: surface as *const _ as usize,
123 curve: curve as *const _ as usize, curves: Default::default(),
124 surfaces: Default::default(), inversions: Default::default(), requests: [0; 4], hits: [0; 4], speeds: Default::default() });
125 PCURVE_SAMPLES.with(|stack| stack.borrow_mut().push(cache));
126 Self(std::marker::PhantomData)
127 }
128}
129impl Drop for PcurveSampleScope<'_> {
130 fn drop(&mut self) {
131 PCURVE_SAMPLES.with(|stack| {
132 if let Some(Some(cache)) = stack.borrow_mut().pop() {
133 if crate::profile::profile_enabled() {
134 profile_line!("pcurve.samples requests={:?} hits={:?} retained={:?}", cache.requests,
135 cache.hits, [cache.curves.len(), cache.surfaces.len(), cache.inversions.len(), cache.speeds.len()]);
136 }
137 }
138 });
139 }
140}
141const PCURVE_SAMPLE_CACHE_LIMIT: usize = 16384;
142fn section_u_speed(surface: &NurbsSurface, u: f64, v: f64) -> Result<f64, String> {
143 let key = (u.to_bits(), v.to_bits());
144 let (active, hit) = PCURVE_SAMPLES.with(|stack| {
145 let mut stack = stack.borrow_mut();
146 let Some(Some(cache)) = stack.last_mut() else { return (false, None) };
147 if cache.surface != surface as *const _ as usize { return (false, None) }
148 cache.requests[3] += 1;
149 let hit = cache.speeds.get(&key).copied();
150 cache.hits[3] += usize::from(hit.is_some());
151 (true, hit)
152 });
153 if let Some(hit) = hit { return Ok(hit) }
154 let speed = surface.deriv1(u, v)?.1.length();
155 if active { PCURVE_SAMPLES.with(|stack| {
156 let mut stack = stack.borrow_mut();
157 let cache = stack.last_mut().unwrap().as_mut().unwrap();
158 if cache.speeds.len() < PCURVE_SAMPLE_CACHE_LIMIT { cache.speeds.insert(key, speed); }
159 }); }
160 Ok(speed)
161}
162
163fn section_curve_point(curve: &NurbsCurve, parameter: f64) -> Result<Vec3, String> {
164 let key = parameter.to_bits();
165 let (active, hit) = PCURVE_SAMPLES.with(|stack| {
166 let mut stack = stack.borrow_mut();
167 let Some(Some(cache)) = stack.last_mut() else { return (false, None) };
168 if cache.curve != curve as *const _ as usize { return (false, None) }
169 cache.requests[0] += 1;
170 let hit = cache.curves.get(&key).copied();
171 cache.hits[0] += usize::from(hit.is_some());
172 (true, hit)
173 });
174 if let Some(hit) = hit { return Ok(hit) }
175 let point = curve.evaluate(parameter)?;
176 if active { PCURVE_SAMPLES.with(|stack| {
177 let mut stack = stack.borrow_mut();
178 let cache = stack.last_mut().unwrap().as_mut().unwrap();
179 if cache.curves.len() < PCURVE_SAMPLE_CACHE_LIMIT { cache.curves.insert(key, point); }
180 }); }
181 Ok(point)
182}
183fn section_surface_point(surface: &NurbsSurface, u: f64, v: f64, extended: bool) -> Result<Vec3, String> {
184 // Extended evaluation has two arithmetic paths. Keep them separate even
185 // if a nested caller changes the stack-point policy within this scope.
186 let stack_point = PCURVE_STACK_POINT.with(|state| state.get());
187 let key = (u.to_bits(), v.to_bits(), extended, stack_point);
188 let (active, hit) = PCURVE_SAMPLES.with(|stack| {
189 let mut stack = stack.borrow_mut();
190 let Some(Some(cache)) = stack.last_mut() else { return (false, None) };
191 if cache.surface != surface as *const _ as usize { return (false, None) }
192 cache.requests[1] += 1;
193 let hit = cache.surfaces.get(&key).copied();
194 cache.hits[1] += usize::from(hit.is_some());
195 (true, hit)
196 });
197 if let Some(hit) = hit { return Ok(hit) }
198 let point = if extended { section_extended_point(surface, u, v, stack_point)? } else { surface.evaluate(u, v)? };
199 if active { PCURVE_SAMPLES.with(|stack| {
200 let mut stack = stack.borrow_mut();
201 let cache = stack.last_mut().unwrap().as_mut().unwrap();
202 if cache.surfaces.len() < PCURVE_SAMPLE_CACHE_LIMIT { cache.surfaces.insert(key, point); }
203 }); }
204 Ok(point)
205}
206fn invert_checked(surface: &NurbsSurface, point: Vec3) -> Result<([f64; 2], f64), String> {
207 let key = [point.x.to_bits(), point.y.to_bits(), point.z.to_bits()];
208 let (active, hit) = PCURVE_SAMPLES.with(|stack| {
209 let mut stack = stack.borrow_mut();
210 let Some(Some(cache)) = stack.last_mut() else { return (false, None) };
211 if cache.surface != surface as *const _ as usize { return (false, None) }
212 cache.requests[2] += 1;
213 let hit = cache.inversions.get(&key).copied();
214 cache.hits[2] += usize::from(hit.is_some());
215 (true, hit)
216 });
217 if let Some(hit) = hit { return Ok(hit) }
218 let projection = invert_checked_uncached(surface, point)?;
219 if active { PCURVE_SAMPLES.with(|stack| {
220 let mut stack = stack.borrow_mut();
221 let cache = stack.last_mut().unwrap().as_mut().unwrap();
222 if cache.inversions.len() < PCURVE_SAMPLE_CACHE_LIMIT { cache.inversions.insert(key, projection); }
223 }); }
224 Ok(projection)
225}
226
227fn invert_checked_uncached(surface: &NurbsSurface, point: Vec3) -> Result<([f64; 2], f64), String> {
228 let _lap = crate::projection_profile::Lap::enter(crate::projection_profile::LANE_INVERT_CHECKED, surface);
229 if surface.is_affine()? {
230 let ([u0, _], [v0, _]) = surface_domains(surface)?;
231 let (origin, du, dv) = surface.deriv1(u0, v0)?;
232 let delta = point.sub(origin);
233 let uu = du.dot(du);
234 let uv = du.dot(dv);
235 let vv = dv.dot(dv);
236 let along_u = delta.dot(du);
237 let along_v = delta.dot(dv);
238 let determinant = uu * vv - uv * uv;
239 if determinant.abs() > EPSILON {
240 let parameter = [
241 u0 + (along_u * vv - along_v * uv) / determinant,
242 v0 + (along_v * uu - along_u * uv) / determinant,
243 ];
244 // The standoff is a MEASUREMENT here as on every other carrier:
245 // until 2026-09-26 this returned 0.0 for "affine, not measured",
246 // and a curve 1e-2 off its plane read as lying on it.
247 let foot = origin.add(du.scale(parameter[0] - u0)).add(dv.scale(parameter[1] - v0));
248 return Ok((parameter, point.sub(foot).length()));
249 }
250 }
251 let projection = project_point_to_surface(surface, point)?;
252 Ok(([projection.u, projection.v], projection.distance))
253}
254
255/// Whether an end station at a cone's apex takes its nearest regular
256/// station's u (`true`), as in `build_interpolant`, or the projector's
257/// arbitrary u there as before 2026-09-27. `BREP_PCURVE_CONE_APEX_U=0`
258/// restores the projector's u, for a same-binary A/B.
259fn cone_apex_u() -> bool {
260 switch!("BREP_PCURVE_CONE_APEX_U", true)
261}
262
263/// Whether a round with more candidates than the cap has room for keeps its
264/// WORST probes (`true`), or the first in span order as before 2026-09-27.
265/// `BREP_PCURVE_WORST_FIRST=0` restores span order, for a same-binary A/B.
266fn worst_first() -> bool {
267 switch!("BREP_PCURVE_WORST_FIRST", true)
268}
269
270/// `BREP_PCURVE_C0_JOIN=0` builds one C2 interpolant through the stations as
271/// before 2026-10-03 (the C0 joins at the curve's corners off), for a
272/// same-binary A/B. Read once per process.
273fn c0_joins() -> bool {
274 switch!("BREP_PCURVE_C0_JOIN", true)
275}
276
277fn seeded_inserts() -> bool {
278 switch!("BREP_PCURVE_SEEDED_INSERTS", true)
279}
280
281/// [`invert_checked`] for a station inserted between two whose feet are known:
282/// Newton from the pcurve's own `(u, v)` at that fraction, which already lies
283/// between the neighbours' feet on their branch, and the global search only
284/// when that local foot stands off further than the neighbours do (plus the
285/// refinement floor) — a local minimum the curve does not sit at. The global
286/// search was 30 s of a 54 s `abc_00000011` import (732k calls, 41 µs each,
287/// measured 2026-09-26). Planes and analytic carriers keep their closed forms.
288/// `BREP_PCURVE_SEEDED_INSERTS=0` restores the global search at every insert,
289/// for a same-binary A/B.
290fn invert_near(
291 surface: &NurbsSurface,
292 point: Vec3,
293 seed: [f64; 2],
294 neighbour_gap: f64,
295) -> Result<([f64; 2], f64), String> {
296 if !seeded_inserts() || surface.is_affine()? || surface.analytic().is_some() || surface.affine_sphere().is_some() {
297 return invert_checked(surface, point);
298 }
299 let seeded = project_point_to_surface_seeded(surface, point, seed[0], seed[1])?;
300 if seeded.distance <= neighbour_gap + PCURVE_REFINEMENT_TOLERANCE {
301 return Ok(([seeded.u, seeded.v], seeded.distance));
302 }
303 invert_checked(surface, point)
304}
305
306fn unwrap_periodic(values: &mut [f64], minimum: f64, maximum: f64) {
307 let period = maximum - minimum;
308 for index in 1..values.len() {
309 while values[index] - values[index - 1] > period / 2.0 {
310 values[index] -= period;
311 }
312 while values[index] - values[index - 1] < -period / 2.0 {
313 values[index] += period;
314 }
315 }
316 if values.len() > 1 {
317 while values[0] - values[1] > period / 2.0 {
318 values[0] -= period;
319 }
320 while values[0] - values[1] < -period / 2.0 {
321 values[0] += period;
322 }
323 }
324 // Recenter the whole (now continuous) sequence into the domain by the
325 // whole-period shift that leaves the LEAST parameter outside [minimum,
326 // maximum]. A single middle/mean sample is NOT representative of a curve
327 // that bulges out to touch — or straddle — the periodic seam: when that
328 // sample is a boundary-kissing point (the at-seam projection unwraps a hair
329 // past the boundary), a single-sample test misfires and shoves the entire
330 // curve a full period out of range (ABC helmet 00000011/12 side channels:
331 // a touching edge's parameter run snapped a whole period off its true
332 // interior, tearing the loop open in parameter space).
333 if values.len() > 1 {
334 let excursion = |shift: f64| -> f64 {
335 values
336 .iter()
337 .map(|value| {
338 let v = value + shift;
339 (minimum - v).max(0.0) + (v - maximum).max(0.0)
340 })
341 .sum::<f64>()
342 };
343 let mean = values.iter().copied().sum::<f64>() / values.len() as f64;
344 let center = 0.5 * (minimum + maximum);
345 let base_k = ((center - mean) / period).round() as i64;
346 let mut best_shift = 0.0;
347 let mut best_excursion = f64::INFINITY;
348 for k in (base_k - 1)..=(base_k + 1) {
349 let shift = k as f64 * period;
350 let value = excursion(shift);
351 if value < best_excursion {
352 best_excursion = value;
353 best_shift = shift;
354 }
355 }
356 if best_shift != 0.0 {
357 for value in values.iter_mut() {
358 *value += best_shift;
359 }
360 }
361 }
362}
363
364/// The interpolant through `raw_parameters`, and what building it CLAMPED:
365/// the largest parameter distance a sample was pulled back into an open
366/// direction's domain, and the 3D distance between the extended surface at
367/// the raw parameter and the clamped foot. Both zero when nothing was pulled.
368/// A closed direction wraps and is never clamped.
369fn build_interpolant(
370 surface: &NurbsSurface,
371 raw_parameters: &[[f64; 2]],
372 curve_parameters: &[f64],
373) -> Result<(NurbsCurve, f64, f64), String> {
374 build_interpolant_joined(surface, raw_parameters, curve_parameters, &[])
375}
376
377/// [`build_interpolant`] with C0 JOINS: `joins` are values of
378/// `curve_parameters` at which the edge curve itself has a corner (a knot of
379/// multiplicity ≥ its degree, `range_breaks`). One cubic through all the
380/// stations is C2 everywhere, so across a corner it overshoots on both sides
381/// by a first-order amount however many stations are inserted: `abc_00000011`
382/// face #585 edge 179 (79 such knots on a 241-point curve) read 2.055e-4 off
383/// at 153 stations with the file's curve 2.1e-7 off the carrier (measured
384/// 2026-10-03). The stations have been planted at those knots since
385/// 2026-09-26; what was missing was the interpolant honouring them. Each
386/// piece between joins is interpolated on its own and the pieces are
387/// concatenated at knots of multiplicity 3 — the join `blend/track_fit.rs`
388/// makes for a blend track. A piece shorter than four stations cannot carry
389/// a cubic, so its join is dropped and it merges with its neighbour.
390fn build_interpolant_joined(
391 surface: &NurbsSurface,
392 raw_parameters: &[[f64; 2]],
393 curve_parameters: &[f64],
394 joins: &[f64],
395) -> Result<(NurbsCurve, f64, f64), String> {
396 let _clock = PcurvePhaseClock::new("interpolant", raw_parameters.len(), false);
397 let ([u0, u1], [v0, v1]) = surface_domains(surface)?;
398 let (closed_u, closed_v) = surface_closedness(surface)?;
399 let mut parameters = raw_parameters.to_vec();
400 // A cone's apex has no longitude either: every u there is the same point,
401 // and the projector answers one arbitrarily (0, which unwraps to 1). A trim
402 // that ENDS at the apex then turns sideways in its last span —
403 // `abc 00008080` face #163 edge 476, a generatrix at u = 0.80255 whose
404 // apex station read u = 1.0 (2026-09-27). An apex station at either end of
405 // the run takes the u of its nearest regular station, so a generatrix's
406 // pcurve is the straight u = const it is. Interior apex crossings are left
407 // to the branch logic below. `BREP_PCURVE_CONE_APEX_U=0` switches this off,
408 // for a same-binary A/B.
409 if cone_apex_u() && matches!(surface.analytic(), Some(AnalyticSurface::RuledRevolution { .. })) && parameters.len() >= 2 {
410 let speeds = parameters
411 .iter()
412 .map(|parameter| section_u_speed(surface, parameter[0], parameter[1]))
413 .collect::<Result<Vec<_>, _>>()?;
414 let reference = speeds.iter().copied().fold(0.0_f64, f64::max);
415 let singular = |index: usize| speeds[index] <= reference * 1e-2;
416 if let (Some(first), Some(last)) = (
417 (0..parameters.len()).find(|&index| !singular(index)),
418 (0..parameters.len()).rfind(|&index| !singular(index)),
419 ) {
420 for index in 0..first {
421 parameters[index][0] = parameters[first][0];
422 }
423 for index in last + 1..parameters.len() {
424 parameters[index][0] = parameters[last][0];
425 }
426 }
427 }
428 // Longitude is undefined at a sphere pole: the surface's u tangent
429 // vanishes there, and closest-point inversion is free to return any u.
430 // Letting that arbitrary value participate in periodic unwrapping can move
431 // the first real meridian by a whole period (e.g. 0.75 -> -0.25). Analytic
432 // carriers are subsequently clamped, collapsing that meridian onto u=0.
433 //
434 // Keep this deliberately sphere- and singularity-gated. Split the u values
435 // into maximal non-pole runs, unwrap each run independently, and leave the
436 // pole samples untouched. The existing interpolant degree stays unchanged;
437 // lowering it here would also change endpoint-tangent decisions made later
438 // while stitching the face loop.
439 let sphere_u_singular = if matches!(surface.analytic(), Some(AnalyticSurface::Sphere { .. })) {
440 let speeds = parameters
441 .iter()
442 .map(|parameter| {
443 section_u_speed(surface, parameter[0], parameter[1])
444 })
445 .collect::<Result<Vec<_>, _>>()?;
446 let reference = speeds.iter().copied().fold(0.0_f64, f64::max);
447 speeds
448 .into_iter()
449 // Uniform edge stations need not hit the pole exactly (ABC 5360
450 // bottoms out at v=5.5e-4). In this small conditioning band,
451 // longitude is already numerically arbitrary and must not connect
452 // the two meridian runs across the pole.
453 .map(|speed| speed <= reference * 1e-2)
454 .collect::<Vec<_>>()
455 } else {
456 vec![false; parameters.len()]
457 };
458 // Do not perturb ordinary pole-touching trims. The special path is needed
459 // only when a pole band is bracketed by nonsingular samples and its raw
460 // longitude changes by at least half a period: a true through-pole branch
461 // reset whose two meridian runs must be unwrapped independently.
462 // Prefix/suffix pole samples on normal sphere caps retain the legacy
463 // interpolation byte-for-byte. Include the exactly-half-period case so
464 // floating-point noise cannot decide which entire run is lifted (ABC 5603).
465 let has_interior_pole_crossing = sphere_u_singular
466 .iter()
467 .position(|singular| !singular)
468 .zip(sphere_u_singular.iter().rposition(|singular| !singular))
469 .is_some_and(|(first, last)| sphere_u_singular[first..=last].iter().any(|value| *value));
470 let period = (u1 - u0).abs();
471 let interior_pole_branch_reset = closed_u
472 && has_interior_pole_crossing
473 && parameters.windows(2).zip(sphere_u_singular.windows(2)).any(
474 |(parameter_pair, singular_pair)| {
475 (singular_pair[0] || singular_pair[1])
476 && (parameter_pair[1][0] - parameter_pair[0][0]).abs()
477 >= 0.5 * period - 1e-12 * period.max(1.0)
478 },
479 );
480 if closed_u {
481 let mut values = parameters.iter().map(|value| value[0]).collect::<Vec<_>>();
482 if interior_pole_branch_reset {
483 let mut start = 0;
484 while start < values.len() {
485 while start < values.len() && sphere_u_singular[start] {
486 start += 1;
487 }
488 let mut end = start;
489 while end < values.len() && !sphere_u_singular[end] {
490 end += 1;
491 }
492 unwrap_periodic(&mut values[start..end], u0, u1);
493 start = end;
494 }
495 } else {
496 unwrap_periodic(&mut values, u0, u1);
497 }
498 for (parameter, value) in parameters.iter_mut().zip(values) {
499 parameter[0] = value;
500 }
501 }
502 if closed_v {
503 let mut values = parameters.iter().map(|value| value[1]).collect::<Vec<_>>();
504 unwrap_periodic(&mut values, v0, v1);
505 for (parameter, value) in parameters.iter_mut().zip(values) {
506 parameter[1] = value;
507 }
508 }
509 // A closed direction WRAPS: an edge that straddles the seam has no single
510 // in-domain parameter run, so keep the unwrapped (possibly slightly
511 // out-of-domain) values and let the periodic evaluator wrap them — clamping
512 // them onto the seam boundary would collapse the straddling geometry (and,
513 // via the interpolant, spike neighbouring stations). This applies to
514 // analytic carriers too: a refitted cylinder can have stepped rims that
515 // cross its seam. evaluate_extended wraps every closed direction.
516 // Open directions must stay in-domain; their extension is a ruling.
517 //
518 // The clamp is MEASURED, not silent: a sample pulled back into the domain
519 // is reported as the parameter distance it moved and the 3D distance
520 // between the extended surface at its raw parameter and the clamped foot
521 // ([`PcurveFitReport::clamped_excursion`]). Until 2026-09-30 a trim that
522 // ran past its chart came back folded onto the chart's boundary with
523 // nothing in the report to say so; on the rib-base document that was
524 // 3.03e-2 of volume on a body `validate()` passes.
525 let mut excursion = 0.0_f64;
526 let mut distance = 0.0_f64;
527 let mut points = Vec::with_capacity(parameters.len());
528 for parameter in parameters {
529 let u = if closed_u {
530 parameter[0]
531 } else {
532 parameter[0].clamp(u0, u1)
533 };
534 let v = if closed_v {
535 parameter[1]
536 } else {
537 parameter[1].clamp(v0, v1)
538 };
539 let pulled = ((parameter[0] - u).powi(2) + (parameter[1] - v).powi(2)).sqrt();
540 if pulled > 0.0 {
541 excursion = excursion.max(pulled);
542 let extended = surface.evaluate_extended(parameter[0], parameter[1])?;
543 distance = distance.max(extended.sub(surface.evaluate(u, v)?).length());
544 }
545 points.push(Vec3::new(u, v, 0.0));
546 }
547 let degree = 3usize.min(points.len() - 1);
548 let mut bounds: Vec<usize> = joins
549 .iter()
550 .filter_map(|join| curve_parameters.iter().position(|parameter| parameter == join))
551 .filter(|&index| index > 0 && index + 1 < curve_parameters.len())
552 .collect();
553 bounds.sort_unstable();
554 bounds.dedup();
555 if degree < 3 || bounds.is_empty() || !c0_joins() {
556 let curve = interpolate_curve(&points, degree, curve_parameters)?;
557 return Ok((curve, excursion, distance));
558 }
559 // Every piece needs four stations for a cubic: drop the join that
560 // closes a shorter piece, from the left, until all pieces carry one.
561 let mut pieces = vec![0usize];
562 for bound in bounds {
563 if bound + 1 - pieces[pieces.len() - 1] >= 4 {
564 pieces.push(bound);
565 }
566 }
567 if curve_parameters.len() - pieces[pieces.len() - 1] < 4 {
568 pieces.pop();
569 }
570 pieces.push(curve_parameters.len() - 1);
571 if pieces.len() < 3 {
572 let curve = interpolate_curve(&points, degree, curve_parameters)?;
573 return Ok((curve, excursion, distance));
574 }
575 let mut knots: Vec<f64> = Vec::with_capacity(curve_parameters.len() + 4);
576 let mut control: Vec<Vec4> = Vec::with_capacity(curve_parameters.len());
577 let last_piece = pieces.len() - 2;
578 for piece in 0..=last_piece {
579 let (from, to) = (pieces[piece], pieces[piece + 1]);
580 let fitted = interpolate_curve(&points[from..=to], 3, &curve_parameters[from..=to])?;
581 if piece == 0 {
582 knots.extend(fitted.knots[..4].iter().copied());
583 }
584 knots.extend(fitted.knots[4..fitted.knots.len() - 4].iter().copied());
585 let multiplicity = if piece == last_piece { 4 } else { 3 };
586 knots.extend(std::iter::repeat(curve_parameters[to]).take(multiplicity));
587 control.extend(fitted.control_points.iter().skip(usize::from(piece > 0)).copied());
588 }
589 let curve = NurbsCurve::new(3, knots, control)?;
590 Ok((curve, excursion, distance))
591}
592
593/// The 3D residual a pcurve's image must reach before `build_pcurve_on_surface`
594/// accepts it, and the sample ceiling it refines up to.
595///
596/// A trimmed face's boundary is only ever as accurate as these two numbers, and
597/// every mass property is an integral over that boundary — so this is the floor
598/// under area, volume and centroid for every curved trim in the kernel.
599///
600/// These were `min(1e-3, 1e-4 * scale)` and 160, where `scale` is
601/// `1 + ||curve midpoint||` — the DISTANCE FROM THE WORLD ORIGIN of the curve's
602/// midpoint, not the part's size. That expression pinned to an absolute 1e-3
603/// once the midpoint sat more than about nine units from the origin, so on an
604/// ordinary part the bar was 1e-3 and tracked nothing.
605///
606/// Measured on `offset-shell-box-bore-dished-face`, whose dished wall is a
607/// closed-form identity at 2468.431782123 (three independent routes agree). The
608/// floor is one of TWO errors there; the other is the refitted sphere carrier
609/// that `sphere_offset_surface` now builds in closed form, and before both moved
610/// they partly CANCELLED — which is why either alone reads as a regression:
611///
612/// ```text
613/// floor Box_O.S2 abs err relative gate total worst case
614/// 1e-3 2468.442270355 +1.049e-02 4.25e-06 (old)
615/// 1e-6 2468.431785429 +3.306e-06 1.34e-09 181.4 s 31.2 s
616/// 1e-7 2468.431782170 +4.746e-08 1.92e-11 218.5 s 32.1 s
617/// 1e-9 2468.431782103 -2.037e-08 -8.25e-12 305.5 s 46.8 s
618/// ```
619///
620/// 1e-7 is chosen: 70x the accuracy of 1e-6 for +20% gate time, where 1e-9 costs
621/// a further +40% for a 2.3x residual no oracle in this corpus could resolve.
622/// All four cells are one session on one binary, so the times are comparable to
623/// each other. Every case carrying a closed-form oracle moves TOWARD it and none
624/// away (`PushFaceTest` 339x, `PushFaceTest2` 275x); no case changes class at
625/// any floor.
626///
627/// A flat ABSOLUTE is what was measured, not what was designed. This codebase
628/// otherwise keys identity bands to part size (`KernelTolerances::heal_band`,
629/// the `1e-11*(1+scale)` blend family), and on a 3000 mm part 1e-7 is 3e-11
630/// relative — refinement will exhaust `MAX_PCURVE_SAMPLES` and return whatever
631/// it reached. Until 2026-09-12 it did so SILENTLY; now the fit's
632/// [`PcurveFitReport`] carries the residual actually achieved and a
633/// [`PcurveFitExit::SampleCeiling`] exit, and the owning operation's
634/// diagnostics count it (`pcurve.unmet_floor`). That is far better than the
635/// 1e-3 it replaces but it is not a size-aware bar; deriving one from the
636/// surface's own extent is separate, unmeasured work. Being absolute does remove this gate's dependence on
637/// distance from the origin as a side effect — a real translation-VARIANCE
638/// defect, the same anti-pattern `tolerance::merge_scale` records being removed
639/// elsewhere. `drop_tolerance` and `endpoint_tolerance` below still carry it.
640pub const PCURVE_REFINEMENT_TOLERANCE: f64 = 1e-7;
641
642/// Sample ceiling for a fit's FIRST refinement pass. 160 could not reach
643/// [`PCURVE_REFINEMENT_TOLERANCE`] on a curved trim of any length; 600 is the
644/// value every cell of the table above was measured at.
645///
646/// It BINDS. On `boolean_fuzz_corpus/25_cube_pierce_solid` the fitter stopped
647/// here 1.65e-3 of volume short of the converged answer (2000 and 4000 samples
648/// agree exactly). Raising it globally to 2000 was costed and rejected: +23%
649/// case-gate time, concentrated in three offset/fillet cases, and one case
650/// (`inbox-20260910-offset-shell-collapsed-fillet`) driven 26x outside its
651/// volume band. So it is not raised here: a fit that stops at this ceiling
652/// with a residual still worse than its edge curve's own distance from the
653/// surface re-enters refinement with [`PCURVE_RAISED_SAMPLES`]
654/// ([`refine_pcurve`]); one whose residual is already that gap stops here and
655/// says so ([`PcurveFitReport::off_surface`]), because no sample count can
656/// close it.
657const MAX_PCURVE_SAMPLES: usize = 600;
658
659/// Sample ceiling for the ONE raised pass a fit may take past
660/// [`MAX_PCURVE_SAMPLES`]. 2000 is the ceiling the convergence sweep measured
661/// on `25_cube_pierce_solid`: its 2000 and 4000 cells agree to every printed
662/// digit because refinement there stops itself near 1540 samples, so the cap
663/// is not what binds.
664const PCURVE_RAISED_SAMPLES: usize = 2000;
665
666/// What a pcurve fit ACHIEVED, returned beside the curve so a ceiling exit can
667/// never be mistaken for a met floor.
668///
669/// [`fit_pcurve_on_surface`] asks for [`PCURVE_REFINEMENT_TOLERANCE`] and stops
670/// at `MAX_PCURVE_SAMPLES` — or, where that ceiling bound and the floor is
671/// reachable, at `PCURVE_RAISED_SAMPLES`. Until 2026-09-12 a trim that ran out
672/// of samples came back exactly like one that met the floor; on
673/// `boolean_fuzz_corpus/25_cube_pierce_solid` that silence hid a 1.65e-3 volume
674/// error. Now every fit says which it was: `residual` is measured on the curve
675/// that is actually returned and `exit` names why refinement stopped.
676///
677/// This is a MEASUREMENT taken at a construction, not a band.
678#[derive(Clone, Copy, Debug, PartialEq)]
679pub struct PcurveFitReport {
680 /// Largest 3D distance between the returned pcurve's image on the surface
681 /// and the edge curve. On a [`PcurveFitExit::Converged`] exit whose final
682 /// round probed every span this is that round's maximum — the refinement's
683 /// own acceptance test. On every other exit, including a converged round
684 /// that skipped spans narrower than 1e-3 of the parameter range, it is a
685 /// sweep over EVERY span of the returned curve.
686 pub residual: f64,
687 /// The residual the refinement was asked to reach.
688 pub floor: f64,
689 /// Interpolation samples in the returned curve.
690 pub samples: usize,
691 /// Why refinement stopped.
692 pub exit: PcurveFitExit,
693 /// Largest distance from the edge curve to the surface at every station
694 /// the fit inverted — the part of `residual` no pcurve can remove, since a
695 /// pcurve's image lies ON the surface. Zero for an affine carrier. A value
696 /// above `floor` says the floor is unreachable for this input; a ceiling
697 /// exit is retried only while `residual` exceeds it by more than the floor,
698 /// i.e. while the returned curve is worse than its input.
699 pub off_surface: f64,
700 /// Whether the fit re-entered refinement with `PCURVE_RAISED_SAMPLES`
701 /// after `MAX_PCURVE_SAMPLES` bound with the floor unmet.
702 pub raised: bool,
703 /// Largest parameter distance a station's inversion was pulled back into
704 /// an OPEN direction's domain when the returned curve was interpolated: a
705 /// trim asked for outside its chart is folded onto the chart's boundary,
706 /// the edge stays right and the face becomes the wrong region on a body
707 /// `validate()` passes (3.03e-2 of volume on the rib-base document). Zero
708 /// when nothing was pulled. A REPORT, not a bar: nothing here refuses on
709 /// it. Its coverage is the clamp in `build_interpolant`, which is the only
710 /// place a raw out-of-domain parameter reaches today — an affine carrier's
711 /// inversion is unclamped, so an affine plane fitted through the station
712 /// lane reads here. It does NOT see a clamp the projector applied before
713 /// the fit: the analytic `Plane` closed form (a non-affine net) clamps to
714 /// its domain, `RuledRevolution` clamps its axial parameter to `[0, 1]`
715 /// and the general Newton clamps through `fit_parameter`; those stations
716 /// arrive in-domain and read as `off_surface` instead. The exact affine
717 /// lanes (`fit_pcurve_on_surface` on an affine carrier,
718 /// `affine_pcurve_on_range`) never clamp and read zero by construction.
719 pub clamped_excursion: f64,
720 /// The 3D distance the clamp corresponds to: between the extended surface
721 /// at the raw parameter and the clamped foot, at the station of the
722 /// largest such distance. Zero when nothing was pulled.
723 pub clamped_distance: f64,
724}
725
726impl PcurveFitReport {
727 /// Whether the returned curve reaches the floor it was asked for.
728 pub fn met_floor(&self) -> bool {
729 self.residual <= self.floor
730 }
731
732 /// Whether the returned curve is on the ACCEPTANCE BAR: the floor it was
733 /// asked for, plus the stations' own standoff from the carrier.
734 ///
735 /// A pcurve's image lies on the carrier, so at a station standing `g` off
736 /// it no image can come closer than `g`; a fit whose residual is within
737 /// the floor of `off_surface` is as good as the input allows, and one over
738 /// it is worse than its input — the same condition `refine_pcurve` uses
739 /// to decide that a raise can still buy accuracy. This is the bar every
740 /// station builder refuses over ([`build_pcurve_on_surface_stations`]).
741 /// It is blind to sideways error quadratically (a station tracked `d`
742 /// aside of the projection of a point `g` off the surface reads only
743 /// `d²/(2g)` further away), which is stated rather than hidden: on the
744 /// import corpus the stations that stand off their carriers are the
745 /// FILE's curves, and the bar there is the file's own number.
746 pub fn on_bar(&self) -> bool {
747 self.residual <= self.floor + self.off_surface
748 }
749}
750
751/// Why a pcurve refinement stopped. Only [`PcurveFitExit::Converged`] means the
752/// floor was met; every other variant is a BUDGET exit, and the report's
753/// residual was measured on the returned curve rather than assumed.
754#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
755pub enum PcurveFitExit {
756 /// A probe round found every station within the floor. An affine carrier
757 /// maps exactly and reports this with a zero residual.
758 Converged,
759 /// The sample ceiling bound before the floor was met: `MAX_PCURVE_SAMPLES`
760 /// when the raise was declined (the residual is already explained by
761 /// [`PcurveFitReport::off_surface`]), `PCURVE_RAISED_SAMPLES` when it was
762 /// taken and bound too.
763 SampleCeiling,
764 /// Every refinement round was spent and the last one still inserted
765 /// samples, so the curve it built was never re-checked by the loop.
766 RoundBudget,
767 /// A round found stations over the floor but could improve none of them:
768 /// the edge point could not be inverted inside the drop band, or (in the
769 /// raised pass) the pcurve already tracks the surface's nearest point to
770 /// within the floor. Either way the edge curve itself sits off the surface
771 /// there and no pcurve can close that gap; the residual reports how far.
772 /// The range lane's end-span challenger (its station fitter's TRACKING
773 /// mode) also exits here when a round's probes all track their feet
774 /// within the floor while the deviation residual, which includes the
775 /// edge's standoff, stays over it.
776 Stalled,
777 /// The last probe round found every span it PROBED within the floor, but
778 /// skipped spans narrower than the loop's own resolution floor — 1e-3 of
779 /// the parameter range in `refine_pcurve`, `SPAN_FLOOR` (1e-6) in
780 /// [`fit_pcurve_on_surface_stations`] — and a sweep over those found the
781 /// floor unmet.
782 /// Until 2026-09-12 this exit was reported as `Converged` with the probed
783 /// spans' maximum: on `25_cube_pierce_solid` a 1539-sample fit with 1537
784 /// narrow spans read 7e-8 that way while every span read 1.9e-4.
785 SpanFloor,
786}
787
788/// A fitted pcurve with what the fit achieved.
789#[derive(Clone, Debug)]
790pub struct PcurveFit {
791 pub curve: NurbsCurve,
792 pub report: PcurveFitReport,
793}
794
795/// Per-operation tally of pcurve fits and how many missed their floor.
796///
797/// [`build_pcurve_on_surface`] and its marched twin are called from some forty
798/// sites, most of which have no diagnostics record to write into. Rather than
799/// thread one through every layer, an operation that returns
800/// [`KernelDiagnostics`] opens a [`PcurveFitScope`]; every fit made on that
801/// thread while the scope is open is tallied here, and closing the scope hands
802/// the tally back for [`PcurveFitLedger::report_into`]. This is the shape the
803/// boolean's `CONFORMANCE_REPAIRS` counter already uses. With no scope open,
804/// recording is a no-op.
805///
806/// Scopes nest: CLOSING a child folds it into its parent, so an offset shell
807/// sees the fits of the booleans it ran. A scope that is DROPPED without being
808/// closed is discarded, not folded — a boolean attempt that refused (and may be
809/// retried under perturbation) must not leave its fits on the caller's tally.
810///
811/// Thread-local by construction: a fit made on a rayon worker (the `parallel`
812/// feature's STEP body import) is not seen by a scope on the calling thread. No
813/// diagnostics-returning operation builds pcurves off-thread today.
814#[derive(Clone, Copy, Debug, Default, PartialEq)]
815pub struct PcurveFitLedger {
816 /// Fits recorded, exact affine ones included.
817 pub fits: u64,
818 /// Fits whose returned curve did not reach its floor.
819 pub unmet: u64,
820 /// Unmet fits that stopped at a sample ceiling.
821 pub sample_ceiling: u64,
822 /// Unmet fits that spent every refinement round.
823 pub round_budget: u64,
824 /// Unmet fits that could insert nothing.
825 pub stalled: u64,
826 /// Unmet fits whose remaining over-floor spans were all below the loop's
827 /// 1e-3 span floor.
828 pub span_floor: u64,
829 /// Largest residual among the unmet fits; zero when there are none.
830 pub worst_unmet_residual: f64,
831 /// Fits that re-entered refinement with `PCURVE_RAISED_SAMPLES`, met or
832 /// not.
833 pub raised: u64,
834 /// Unmet ceiling exits that were NOT raised because the residual is
835 /// already within the floor of the edge curve's own gap from the surface.
836 pub raise_declined: u64,
837 /// Largest [`PcurveFitReport::off_surface`] over every fit.
838 pub worst_off_surface: f64,
839 /// Fits whose interpolant was clamped into an open direction's domain
840 /// ([`PcurveFitReport::clamped_excursion`] above zero), met or not.
841 pub clamped: u64,
842 /// Largest [`PcurveFitReport::clamped_excursion`] over every fit.
843 pub worst_clamped_excursion: f64,
844 /// Largest [`PcurveFitReport::clamped_distance`] over every fit.
845 pub worst_clamped_distance: f64,
846}
847
848impl PcurveFitLedger {
849 fn record(&mut self, report: &PcurveFitReport) {
850 self.fits += 1;
851 if report.raised {
852 self.raised += 1;
853 }
854 self.worst_off_surface = self.worst_off_surface.max(report.off_surface);
855 if report.clamped_excursion > 0.0 {
856 self.clamped += 1;
857 self.worst_clamped_excursion = self.worst_clamped_excursion.max(report.clamped_excursion);
858 self.worst_clamped_distance = self.worst_clamped_distance.max(report.clamped_distance);
859 }
860 if report.met_floor() {
861 return;
862 }
863 self.unmet += 1;
864 match report.exit {
865 PcurveFitExit::Converged => {}
866 PcurveFitExit::SampleCeiling => {
867 self.sample_ceiling += 1;
868 if !report.raised {
869 self.raise_declined += 1;
870 }
871 }
872 PcurveFitExit::RoundBudget => self.round_budget += 1,
873 PcurveFitExit::Stalled => self.stalled += 1,
874 PcurveFitExit::SpanFloor => self.span_floor += 1,
875 }
876 self.worst_unmet_residual = self.worst_unmet_residual.max(report.residual);
877 }
878
879 fn fold(&mut self, child: &PcurveFitLedger) {
880 self.fits += child.fits;
881 self.unmet += child.unmet;
882 self.sample_ceiling += child.sample_ceiling;
883 self.round_budget += child.round_budget;
884 self.stalled += child.stalled;
885 self.span_floor += child.span_floor;
886 self.worst_unmet_residual = self.worst_unmet_residual.max(child.worst_unmet_residual);
887 self.raised += child.raised;
888 self.raise_declined += child.raise_declined;
889 self.worst_off_surface = self.worst_off_surface.max(child.worst_off_surface);
890 self.clamped += child.clamped;
891 self.worst_clamped_excursion = self.worst_clamped_excursion.max(child.worst_clamped_excursion);
892 self.worst_clamped_distance = self.worst_clamped_distance.max(child.worst_clamped_distance);
893 }
894
895 /// Write the tally into an operation's diagnostics: the `pcurve.*`
896 /// counters and measurements always, plus one `pcurve.unmet_floor` event at
897 /// [`DiagnosticSeverity::Degraded`] when any fit missed its floor. Degraded,
898 /// not Error: the result is still the kernel's best construction and stays
899 /// shippable, but it no longer claims the accuracy its floor states.
900 pub fn report_into(&self, diagnostics: &mut KernelDiagnostics) {
901 diagnostics.count_n("pcurve.fits", self.fits);
902 diagnostics.count_n("pcurve.unmet_floor", self.unmet);
903 diagnostics.count_n("pcurve.exit.sample_ceiling", self.sample_ceiling);
904 diagnostics.count_n("pcurve.exit.round_budget", self.round_budget);
905 diagnostics.count_n("pcurve.exit.stalled", self.stalled);
906 diagnostics.count_n("pcurve.exit.span_floor", self.span_floor);
907 diagnostics.count_n("pcurve.budget_raised", self.raised);
908 diagnostics.count_n("pcurve.budget_raise_declined", self.raise_declined);
909 diagnostics.measure_max("pcurve.worst_unmet_residual", self.worst_unmet_residual);
910 diagnostics.measure_max("pcurve.worst_off_surface", self.worst_off_surface);
911 diagnostics.count_n("pcurve.clamped", self.clamped);
912 diagnostics.measure_max("pcurve.worst_clamped_excursion", self.worst_clamped_excursion);
913 diagnostics.measure_max("pcurve.worst_clamped_distance", self.worst_clamped_distance);
914 if self.unmet > 0 {
915 diagnostics.event(
916 DiagnosticSeverity::Degraded,
917 KernelStage::Refine,
918 "pcurve.unmet_floor",
919 format!(
920 "{} of {} pcurve fits did not reach the {:.0e} floor \
921 (sample ceiling {}, round budget {}, stalled {}, span floor {}); \
922 worst residual {:.3e}; budget raised on {}, declined on {}; \
923 edge curves sit off their surfaces by up to {:.3e}{}",
924 self.unmet,
925 self.fits,
926 PCURVE_REFINEMENT_TOLERANCE,
927 self.sample_ceiling,
928 self.round_budget,
929 self.stalled,
930 self.span_floor,
931 self.worst_unmet_residual,
932 self.raised,
933 self.raise_declined,
934 self.worst_off_surface,
935 if self.clamped > 0 {
936 format!(
937 "; {} fit(s) clamped into their chart by up to {:.3e} in parameter, {:.3e} in 3D",
938 self.clamped, self.worst_clamped_excursion, self.worst_clamped_distance
939 )
940 } else {
941 String::new()
942 },
943 ),
944 );
945 }
946 }
947}
948
949thread_local! {
950 /// The open [`FootMemo`] on this thread, if any ([`FootMemoScope`]).
951 static FOOT_MEMO: std::cell::RefCell<Option<FootMemo>> = const { std::cell::RefCell::new(None) };
952}
953
954/// Exact-input memo of the common reader's projections on ONE surface: the
955/// global foot of an edge point and the foot seeded at a (u, v), keyed by the
956/// bit patterns of their inputs. Both projectors are pure functions of
957/// (surface, point[, seed]), so a hit returns the value a fresh call would:
958/// the readings, decisions and fits are bit-identical with or without it.
959/// It exists for cost: within one coedge the same edge points are read by
960/// the trigger read, every tracking-mode verification round, the
961/// challenger's final read and the importer's keep read, each of which
962/// projected them again (`importTestWorking`, 2026-10-05: about 10k probes
963/// read 3-5 times on each of nine off-carrier refits, 3-5 s apiece).
964///
965/// Feet computed while [`defer_foot_inserts`] is on are appended to the
966/// `pending_*` lists instead of hashed, and folded into the maps at the first
967/// lookup that misses while it is off. A tracking pair that reads clean is
968/// never read again, so its 20-30k feet per read are never hashed (16% of
969/// the reader on the elephant, 2026-11-03); a candidate read or
970/// fit that follows sees every one of them. Deferral changes no answer: a
971/// lookup that misses recomputes the same pure function.
972struct FootMemo {
973 surface: *const NurbsSurface,
974 global: rustc_hash::FxHashMap<[u64; 3], Option<crate::SurfaceProjection>>,
975 seeded: rustc_hash::FxHashMap<[u64; 5], Option<crate::SurfaceProjection>>,
976 pending_global: Vec<([u64; 3], Option<crate::SurfaceProjection>)>,
977 pending_seeded: Vec<([u64; 5], Option<crate::SurfaceProjection>)>,
978}
979
980impl FootMemo {
981 fn new(surface: *const NurbsSurface) -> Self {
982 Self { surface, global: Default::default(), seeded: Default::default(), pending_global: Vec::new(), pending_seeded: Vec::new() }
983 }
984
985 /// Fold the deferred feet into the maps (a later insert of the same key
986 /// would carry the same value, so the order of folding is immaterial).
987 fn fold_pending(&mut self) {
988 if !self.pending_global.is_empty() {
989 self.global.reserve(self.pending_global.len());
990 self.global.extend(self.pending_global.drain(..));
991 }
992 if !self.pending_seeded.is_empty() {
993 self.seeded.reserve(self.pending_seeded.len());
994 self.seeded.extend(self.pending_seeded.drain(..));
995 }
996 }
997}
998
999thread_local! {
1000 static DEFER_FOOT_INSERTS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1001}
1002
1003/// Run `read` with new feet appended to the open memo's pending lists rather
1004/// than hashed ([`FootMemo`]); restores the previous setting.
1005pub(crate) fn defer_foot_inserts<T>(read: impl FnOnce() -> T) -> T {
1006 let previous = DEFER_FOOT_INSERTS.with(|cell| cell.replace(true));
1007 let result = read();
1008 DEFER_FOOT_INSERTS.with(|cell| cell.set(previous));
1009 result
1010}
1011
1012/// Opens a [`FootMemo`] for `surface` on this thread until dropped. Opening
1013/// one where a memo for the SAME surface is already open reuses it (an inner
1014/// scope, e.g. the range challenger inside an importer coedge); a memo for
1015/// another surface is set aside and restored on drop. The borrow of `surface`
1016/// keeps it alive and unmodified for the scope, so the pointer key cannot be
1017/// reused by another surface while the memo answers for it.
1018pub(crate) struct FootMemoScope<'a> {
1019 previous: Option<Option<FootMemo>>,
1020 _surface: std::marker::PhantomData<&'a NurbsSurface>,
1021 /// Not `Send`: the scope must close on the thread that opened it.
1022 _thread_bound: std::marker::PhantomData<*const ()>,
1023}
1024
1025impl<'a> FootMemoScope<'a> {
1026 pub(crate) fn open(surface: &'a NurbsSurface) -> Self {
1027 let key = surface as *const NurbsSurface;
1028 let previous = FOOT_MEMO.with(|memo| {
1029 let mut memo = memo.borrow_mut();
1030 if memo.as_ref().is_some_and(|open| std::ptr::eq(open.surface, key)) {
1031 None
1032 } else {
1033 Some(memo.replace(FootMemo::new(key)))
1034 }
1035 });
1036 Self { previous, _surface: std::marker::PhantomData, _thread_bound: std::marker::PhantomData }
1037 }
1038}
1039
1040impl Drop for FootMemoScope<'_> {
1041 fn drop(&mut self) {
1042 if let Some(previous) = self.previous.take() {
1043 FOOT_MEMO.with(|memo| *memo.borrow_mut() = previous);
1044 }
1045 }
1046}
1047
1048fn point_bits(point: Vec3) -> [u64; 3] {
1049 [point.x.to_bits(), point.y.to_bits(), point.z.to_bits()]
1050}
1051
1052/// [`project_point_to_surface`] as `.ok()`, through the open memo for this
1053/// surface when there is one. `#[track_caller]`: the projector still sees the
1054/// reader's own call site. Under a `BREP_PROJECTION_GLOBAL_SITES`/`_EXCEPT`
1055/// filter the projector's answer depends on that site, so the memo is not
1056/// consulted then.
1057#[track_caller]
1058fn memo_global_foot(surface: &NurbsSurface, point: Vec3) -> Option<crate::SurfaceProjection> {
1059 if crate::projection::global_site_filter_active() {
1060 return project_point_to_surface(surface, point).ok();
1061 }
1062 let key = point_bits(point);
1063 let deferring = DEFER_FOOT_INSERTS.with(|cell| cell.get());
1064 let hit = FOOT_MEMO.with(|memo| {
1065 memo.borrow_mut().as_mut().filter(|open| std::ptr::eq(open.surface, surface)).map(|open| {
1066 match open.global.get(&key).copied() {
1067 Some(foot) => Some(foot),
1068 None if !deferring && !open.pending_global.is_empty() => {
1069 open.fold_pending();
1070 open.global.get(&key).copied()
1071 }
1072 None => None,
1073 }
1074 })
1075 });
1076 match hit {
1077 Some(Some(foot)) => foot,
1078 Some(None) => {
1079 let foot = project_point_to_surface(surface, point).ok();
1080 FOOT_MEMO.with(|memo| {
1081 if let Some(open) = memo.borrow_mut().as_mut().filter(|open| std::ptr::eq(open.surface, surface)) {
1082 if deferring {
1083 open.pending_global.push((key, foot));
1084 } else {
1085 open.global.insert(key, foot);
1086 }
1087 }
1088 });
1089 foot
1090 }
1091 None => project_point_to_surface(surface, point).ok(),
1092 }
1093}
1094
1095/// [`project_point_to_surface_seeded`] as `.ok()`, through the open memo for
1096/// this surface when there is one.
1097fn memo_seeded_foot(surface: &NurbsSurface, point: Vec3, seed_u: f64, seed_v: f64) -> Option<crate::SurfaceProjection> {
1098 let [x, y, z] = point_bits(point);
1099 let key = [x, y, z, seed_u.to_bits(), seed_v.to_bits()];
1100 let deferring = DEFER_FOOT_INSERTS.with(|cell| cell.get());
1101 let hit = FOOT_MEMO.with(|memo| {
1102 memo.borrow_mut().as_mut().filter(|open| std::ptr::eq(open.surface, surface)).map(|open| {
1103 match open.seeded.get(&key).copied() {
1104 Some(foot) => Some(foot),
1105 None if !deferring && !open.pending_seeded.is_empty() => {
1106 open.fold_pending();
1107 open.seeded.get(&key).copied()
1108 }
1109 None => None,
1110 }
1111 })
1112 });
1113 match hit {
1114 Some(Some(foot)) => foot,
1115 Some(None) => {
1116 let foot = project_point_to_surface_seeded(surface, point, seed_u, seed_v).ok();
1117 FOOT_MEMO.with(|memo| {
1118 if let Some(open) = memo.borrow_mut().as_mut().filter(|open| std::ptr::eq(open.surface, surface)) {
1119 if deferring {
1120 open.pending_seeded.push((key, foot));
1121 } else {
1122 open.seeded.insert(key, foot);
1123 }
1124 }
1125 });
1126 foot
1127 }
1128 None => project_point_to_surface_seeded(surface, point, seed_u, seed_v).ok(),
1129 }
1130}
1131
1132thread_local! {
1133 /// The open [`PcurveFitScope`]s on this thread, innermost last.
1134 static PCURVE_FIT_SCOPES: std::cell::RefCell<Vec<PcurveFitLedger>> =
1135 const { std::cell::RefCell::new(Vec::new()) };
1136}
1137
1138/// `BREP_DEBUG_PCURVE_FIT` only: a process-wide id per [`refine_pcurve`]
1139/// call, so its first-pass and candidate `PCURVE-TRACK` lines pair up even
1140/// when fits run on several threads.
1141static PCURVE_TRACK_CALL: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1142
1143/// An open tally of the pcurve fits made on this thread — see
1144/// [`PcurveFitLedger`]. Open it where an operation starts, [`close`] it where
1145/// the operation returns its diagnostics; a `?` that unwinds past it drops the
1146/// tally.
1147///
1148/// [`close`]: PcurveFitScope::close
1149pub struct PcurveFitScope {
1150 /// Not `Send`: the scope must close on the thread that opened it.
1151 _thread_bound: std::marker::PhantomData<*const ()>,
1152}
1153
1154impl PcurveFitScope {
1155 pub fn open() -> Self {
1156 PCURVE_FIT_SCOPES.with(|scopes| scopes.borrow_mut().push(PcurveFitLedger::default()));
1157 Self {
1158 _thread_bound: std::marker::PhantomData,
1159 }
1160 }
1161
1162 /// Take the tally, folding it into the enclosing scope if there is one.
1163 pub fn close(self) -> PcurveFitLedger {
1164 let ledger = PCURVE_FIT_SCOPES.with(|scopes| {
1165 let mut scopes = scopes.borrow_mut();
1166 let ledger = scopes.pop().unwrap_or_default();
1167 if let Some(parent) = scopes.last_mut() {
1168 parent.fold(&ledger);
1169 }
1170 ledger
1171 });
1172 std::mem::forget(self);
1173 ledger
1174 }
1175}
1176
1177impl Drop for PcurveFitScope {
1178 fn drop(&mut self) {
1179 PCURVE_FIT_SCOPES.with(|scopes| {
1180 scopes.borrow_mut().pop();
1181 });
1182 }
1183}
1184
1185pub(crate) fn record_fit(report: &PcurveFitReport) {
1186 // Per-fit trace for the convergence probes (`trim_floor_sweep_probe` and
1187 // its offset-shell sibling): one line per fit with the whole report, the
1188 // way `BREP_DEBUG_PCURVE` traces an endpoint failure.
1189 if env_read!("BREP_DEBUG_PCURVE_FIT").is_some() {
1190 eprintln!("PCURVE-FIT {report:?}");
1191 }
1192 PCURVE_FIT_SCOPES.with(|scopes| {
1193 if let Some(open) = scopes.borrow_mut().last_mut() {
1194 open.record(report);
1195 }
1196 });
1197}
1198
1199/// Largest 3D distance between `pcurve`'s image and `curve` at the
1200/// quarter-points of every span of `parameters` — the stations refinement
1201/// probes, over EVERY span. Evaluations only; nothing is inverted. The sweep
1202/// measures, it does not decide.
1203fn probe_residual(
1204 surface: &NurbsSurface,
1205 curve: &NurbsCurve,
1206 pcurve: &NurbsCurve,
1207 parameters: &[f64],
1208 domain: [f64; 2],
1209 _u_domain: [f64; 2],
1210 _v_domain: [f64; 2],
1211) -> Result<f64, String> {
1212 probe_residual_at(surface, curve, pcurve, parameters, domain, &[0.25, 0.5, 0.75])
1213}
1214
1215/// Extra interior points per sample span, at `j/17` for `j = 1..=16`, that
1216/// [`tracking_acceptance_residual`] reads IN ADDITION to [`probe_residual`]'s
1217/// quarter, mid and three-quarter points (nineteen fractions per span in
1218/// all), so it can never read below the three-point maximum on the same
1219/// curve. The three quarter-points under-read the sixteen-point maximum by
1220/// 3.3–6.5 % on the constructed-cylinder union's imprint pcurves, and sixteen
1221/// sit within 0.4 % of sixty-four there (2026-10-04); the Converged path keeps
1222/// its own read.
1223const TRACKING_ACCEPTANCE_POINTS: usize = 16;
1224
1225/// The same-parameter image-vs-edge distance of `pcurve` at `locals` (span
1226/// fractions) inside every span of `parameters`: the largest, or an error if
1227/// any is not finite.
1228fn probe_residual_at(
1229 surface: &NurbsSurface,
1230 curve: &NurbsCurve,
1231 pcurve: &NurbsCurve,
1232 parameters: &[f64],
1233 [t0, t1]: [f64; 2],
1234 locals: &[f64],
1235) -> Result<f64, String> {
1236 let mut worst = 0.0_f64;
1237 for span in parameters.windows(2) {
1238 for &local in locals {
1239 let fraction = span[0] + (span[1] - span[0]) * local;
1240 let parameter = pcurve.evaluate(fraction)?;
1241 let on_surface =
1242 section_surface_point(surface, parameter.x, parameter.y, true)?;
1243 let on_curve = section_curve_point(curve, t0 + (t1 - t0) * fraction)?;
1244 // `f64::max` drops a NaN operand, so a non-finite distance would
1245 // leave `worst` reading the finite stations only. A residual that
1246 // can accept a curve must have measured every station: fail
1247 // closed instead.
1248 let distance = on_surface.sub(on_curve).length();
1249 if !distance.is_finite() {
1250 return Err(format!(
1251 "probe_residual: non-finite image distance {distance} at fraction {fraction}"
1252 ));
1253 }
1254 worst = worst.max(distance);
1255 }
1256 }
1257 Ok(worst)
1258}
1259
1260/// The probe fractions a native read takes for the given pcurves: both
1261/// domain ENDPOINTS, and the quarter points plus 16 interior points of every
1262/// span between each curve's OWN distinct knots (the installed interpolant's
1263/// spans, not the construction stations). Merged over all the curves, sorted
1264/// and deduplicated, so a standing fit and a candidate are read at the SAME
1265/// fractions.
1266pub(crate) fn native_probe_fractions(curves: &[&NurbsCurve]) -> Result<Vec<f64>, String> {
1267 let locals: Vec<f64> = [0.25, 0.5, 0.75]
1268 .into_iter()
1269 .chain((1..=TRACKING_ACCEPTANCE_POINTS).map(|index| index as f64 / (TRACKING_ACCEPTANCE_POINTS + 1) as f64))
1270 .collect();
1271 let mut fractions = Vec::new();
1272 for curve in curves {
1273 let [q0, q1] = curve.domain()?;
1274 fractions.push(q0);
1275 fractions.push(q1);
1276 let mut knots: Vec<f64> = curve.knots.iter().copied().filter(|k| *k >= q0 && *k <= q1).collect();
1277 knots.dedup();
1278 for span in knots.windows(2).filter(|w| w[1] > w[0]) {
1279 for &local in &locals {
1280 fractions.push(span[0] + (span[1] - span[0]) * local);
1281 }
1282 }
1283 }
1284 fractions.sort_by(f64::total_cmp);
1285 fractions.dedup();
1286 Ok(fractions)
1287}
1288
1289/// One pcurve read at given probe fractions against the edge curve (whose
1290/// parameter is t0 + (t1 - t0) x fraction): the image's distance from the
1291/// edge point's FOOT on the carrier (its TRACKING, the part a pcurve can
1292/// improve; the foot is seeded at the pcurve's own uv, closed form on an
1293/// analytic carrier), the image's distance from the edge point itself (the
1294/// same-parameter DEVIATION), and the edge point's distance from the carrier
1295/// (its INHERITED STANDOFF). Maxima over the probes; fails closed on any
1296/// non-finite point or distance.
1297#[derive(Clone, Copy, Debug, PartialEq)]
1298struct NativeRead {
1299 tracking: f64,
1300 deviation: f64,
1301 standoff: f64,
1302}
1303
1304fn native_read(surface: &NurbsSurface, curve: &NurbsCurve, pcurve: &NurbsCurve, [t0, t1]: [f64; 2], fractions: &[f64]) -> Result<NativeRead, String> {
1305 let mut read = NativeRead { tracking: 0.0, deviation: 0.0, standoff: 0.0 };
1306 for &fraction in fractions {
1307 let parameter = pcurve.evaluate(fraction)?;
1308 let image = section_surface_point(surface, parameter.x, parameter.y, true)?;
1309 let on_curve = section_curve_point(curve, t0 + (t1 - t0) * fraction)?;
1310 let foot = crate::projection::project_point_to_surface_from_seed(surface, on_curve, [parameter.x, parameter.y])?;
1311 let tracking = image.sub(foot.point).length();
1312 let deviation = image.sub(on_curve).length();
1313 if !(tracking.is_finite() && deviation.is_finite() && foot.distance.is_finite()) {
1314 return Err(format!("native read: non-finite reading at fraction {fraction}"));
1315 }
1316 read.tracking = read.tracking.max(tracking);
1317 read.deviation = read.deviation.max(deviation);
1318 read.standoff = read.standoff.max(foot.distance);
1319 }
1320 Ok(read)
1321}
1322
1323/// Whether an off-carrier tracking candidate replaces the standing fit, both
1324/// read at the SAME probes ([`native_probe_fractions`] of both curves): its
1325/// tracking finite, within the unchanged floor and strictly lower, AND its
1326/// same-parameter deviation no worse (the reported total, inherited standoff
1327/// included, must not grow), AND its own refinement fields finite.
1328fn standoff_candidate_accepted(standing: NativeRead, candidate: NativeRead, candidate_fields_finite: bool, floor: f64) -> bool {
1329 candidate_fields_finite
1330 && candidate.tracking.is_finite()
1331 && candidate.deviation.is_finite()
1332 && candidate.tracking <= floor
1333 && candidate.tracking < standing.tracking
1334 && candidate.deviation <= standing.deviation
1335}
1336
1337/// [`probe_residual_at`] at [`probe_residual`]'s three quarter-points plus
1338/// [`TRACKING_ACCEPTANCE_POINTS`] evenly spaced interior points of every span
1339/// (nineteen per span): the maximum of the old three-point reading and the
1340/// sixteen-point one. It is the reading a budget-exit tracking candidate is
1341/// accepted on, and the residual it is then reported with.
1342fn tracking_acceptance_residual(
1343 surface: &NurbsSurface,
1344 curve: &NurbsCurve,
1345 pcurve: &NurbsCurve,
1346 parameters: &[f64],
1347 domain: [f64; 2],
1348) -> Result<f64, String> {
1349 let locals: Vec<f64> = [0.25, 0.5, 0.75]
1350 .into_iter()
1351 .chain((1..=TRACKING_ACCEPTANCE_POINTS).map(|index| index as f64 / (TRACKING_ACCEPTANCE_POINTS + 1) as f64))
1352 .collect();
1353 probe_residual_at(surface, curve, pcurve, parameters, domain, &locals)
1354}
1355
1356/// Whether a projection-tracking candidate replaces the fit standing in
1357/// [`refine_pcurve`], given the `reading` it is judged on (see
1358/// [`judge_tracking_candidate`]). `standing_dense` says the standing fit's
1359/// reading is itself a nineteen-point one: a budget-exit candidate was taken.
1360///
1361/// While the standing reading is the fitter's own, a `Converged` candidate is
1362/// taken exactly as before (within the floor, better). A BUDGET exit
1363/// (`SampleCeiling`, `RoundBudget`, `Stalled`, `SpanFloor`) is taken only to
1364/// repair a standing fit that MISSES the floor, and only when its
1365/// nineteen-point reading is finite, within the unchanged floor and strictly
1366/// better. Once the standing reading is a nineteen-point one, every later
1367/// candidate is compared nineteen against nineteen: a Converged one must read
1368/// finite, within the floor and strictly better on the same stencil, and a
1369/// budget exit is never taken (the standing fit is then within the floor).
1370/// The exit label says how the loop stopped, not whether the curve is good: on
1371/// the 15° constructed-cylinder union all six imprint pcurves that shipped
1372/// 1.3e-7..7.0e-6 off had a tracking candidate reading 1.4e-8..9.95e-8 at
1373/// three points per span, discarded for its label (2026-10-04). A standing
1374/// fit already within the floor is never replaced by a budget exit, so every
1375/// in-floor fit is unchanged; a taken candidate keeps its own exit, samples
1376/// and raise, and reports the reading it was taken on.
1377fn tracking_candidate_accepted(standing: f64, standing_dense: bool, floor: f64, exit: PcurveFitExit, reading: f64) -> bool {
1378 if exit == PcurveFitExit::Converged && !standing_dense {
1379 return reading <= floor && reading < standing;
1380 }
1381 if exit != PcurveFitExit::Converged && !(standing > floor) {
1382 return false;
1383 }
1384 reading.is_finite() && reading <= floor && reading < standing
1385}
1386
1387/// The reading a tracking candidate is judged on, and whether it replaces the
1388/// standing fit: `own` (the candidate's own residual) for a Converged
1389/// candidate while the standing reading is the fitter's own; otherwise
1390/// `dense` ([`tracking_acceptance_residual`]'s nineteen points per span), read
1391/// only when the candidate could be taken. An error from `dense` declines.
1392/// [`refine_pcurve`] decides and traces through this one function.
1393fn judge_tracking_candidate(
1394 standing: f64,
1395 standing_dense: bool,
1396 floor: f64,
1397 exit: PcurveFitExit,
1398 own: f64,
1399 dense: impl FnOnce() -> Result<f64, String>,
1400) -> (Result<Option<f64>, String>, bool) {
1401 judge_tracking_candidate_with_bounds(standing, standing_dense, floor, exit, own, dense, false)
1402}
1403
1404fn judge_tracking_candidate_with_bounds(
1405 standing: f64,
1406 standing_dense: bool,
1407 floor: f64,
1408 exit: PcurveFitExit,
1409 own: f64,
1410 dense: impl FnOnce() -> Result<f64, String>,
1411 bounds: bool,
1412) -> (Result<Option<f64>, String>, bool) {
1413 // A budget exit's own residual probes the quarter points of every
1414 // returned span. They are a subset of the nineteen-point stencil, so its
1415 // maximum cannot fall below this already over-floor lower bound.
1416 if bounds && exit != PcurveFitExit::Converged && own > floor {
1417 if crate::profile::profile_enabled() { profile_line!("pcurve.reject_bound kind=quarter_residual"); }
1418 return (Ok(Some(own)), false);
1419 }
1420 let reading = if exit == PcurveFitExit::Converged && !standing_dense {
1421 Ok(Some(own))
1422 } else if exit == PcurveFitExit::Converged || standing > floor {
1423 dense().map(Some)
1424 } else {
1425 Ok(None)
1426 };
1427 let accepted = matches!(
1428 reading,
1429 Ok(Some(value)) if tracking_candidate_accepted(standing, standing_dense, floor, exit, value)
1430 );
1431 (reading, accepted)
1432}
1433
1434/// What one call to [`refine_pcurve`] achieved, beside the curve it left in
1435/// place.
1436struct Refinement {
1437 exit: PcurveFitExit,
1438 residual: f64,
1439 off_surface: f64,
1440 raised: bool,
1441 /// [`PcurveFitReport::clamped_excursion`] and
1442 /// [`PcurveFitReport::clamped_distance`] of the curve left in place.
1443 clamped: [f64; 2],
1444}
1445
1446/// Adaptive 3D-residual refinement of an interpolant, with the PER-TRIM sample
1447/// budget. Shared by [`fit_pcurve_on_surface`] (global inversion) and
1448/// [`fit_pcurve_on_surface_marched`] (seeded projection); `insert` is the one
1449/// step that differs — given a probe station's fraction, the current pcurve's
1450/// parameter there and the edge point, it returns the surface parameter to
1451/// interpolate through (or `None` to decline the station) and the distance
1452/// from the edge point to the surface at that station, its GAP.
1453///
1454/// **First pass.** Four rounds at [`MAX_PCURVE_SAMPLES`], each probing the
1455/// quarter-points of every span wider than 1e-3 of the parameter range and
1456/// inserting the stations over `floor`. Byte-for-byte the pre-2026-09-12
1457/// loop; every exit is classified ([`PcurveFitExit`]) and the residual is
1458/// measured on the RETURNED curve — over every span, unless the final round
1459/// probed every span, in which case that round's maximum already is.
1460///
1461/// **The raise.** A [`PcurveFitExit::SampleCeiling`] exit re-enters the four
1462/// rounds ONCE with [`PCURVE_RAISED_SAMPLES`], continuing from the samples it
1463/// has, when the residual exceeds `off_surface` by more than the floor — the
1464/// returned curve is worse than the edge curve's own distance from the
1465/// surface, so more samples CAN improve it. Measured on `25_cube_pierce_solid`
1466/// (2026-09-12): the edge curves there sit off their surfaces by up to 1.9e-4,
1467/// four of the eight ceiling exits already read their gap to four digits, and
1468/// raising those spent 900 inversions each for no change in the swept
1469/// residual. The raised pass therefore inserts only where the pcurve's surface
1470/// point is further than the floor from the PROJECTION of the edge point — its
1471/// tracking error, which the inversion just computed: it stops when the pcurve
1472/// tracks the surface's nearest point to the floor, which is the best any
1473/// pcurve can do, and reports [`PcurveFitExit::Stalled`] with the deviation it
1474/// actually reached when that is still over the floor. The limit of that pass
1475/// as the floor tightens is the projected edge curve itself, so a volume built
1476/// on it can be shown to converge; the deviation test alone cannot say that on
1477/// off-surface input. Grow-once, capped, deterministic.
1478#[allow(clippy::too_many_arguments)]
1479fn refine_pcurve(
1480 surface: &NurbsSurface,
1481 curve: &NurbsCurve,
1482 domain: [f64; 2],
1483 u_domain: [f64; 2],
1484 v_domain: [f64; 2],
1485 floor: f64,
1486 joins: &[f64],
1487 parameters: &mut Vec<f64>,
1488 raw: &mut Vec<[f64; 2]>,
1489 pcurve: &mut NurbsCurve,
1490 off_surface: f64,
1491 clamped: [f64; 2],
1492 mut insert: impl FnMut(f64, Vec3, Vec3) -> Result<(Option<[f64; 2]>, f64), String>,
1493) -> Result<Refinement, String> {
1494 // Trace only (`BREP_DEBUG_PCURVE_FIT`): reads values the passes already
1495 // computed and decides nothing.
1496 let trace = env_read!("BREP_DEBUG_PCURVE_FIT").is_some();
1497 let call = if trace {
1498 PCURVE_TRACK_CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1
1499 } else {
1500 0
1501 };
1502 let reject_bounds = PCURVE_REJECT_BOUNDS.with(|state| state.get());
1503 let mut best = refine_pcurve_pass(surface, curve, domain, u_domain, v_domain,
1504 floor, joins, parameters, raw, pcurve, off_surface, clamped, false, 1.0, false, &mut insert, false)?;
1505 if trace {
1506 eprintln!(
1507 "PCURVE-TRACK call={call} first-pass exit={:?} residual={:e} floor={floor:e} off_surface={:e} samples={} raised={} tracking={}",
1508 best.exit,
1509 best.residual,
1510 best.off_surface,
1511 parameters.len(),
1512 best.raised,
1513 if best.off_surface <= floor { "eligible" } else { "standoff-tracking" }
1514 );
1515 }
1516 if best.off_surface > floor {
1517 // The edge curve itself stands off the carrier by more than the floor
1518 // (its INHERITED standoff), so no pcurve can bring the image within the
1519 // floor of the edge point. What a pcurve CAN improve is its tracking
1520 // error: the image's distance from the edge point's foot. Measured
1521 // 2026-10-04 (a2b401d4d, 15-degree crossing, turn 295): a first pass
1522 // ended RoundBudget at 6.726e-6 over a 1.316e-7 standoff and every
1523 // tracking pass was skipped. No candidate refinement when the ACTUAL
1524 // tracking request is met: the first pass's own tracking, read on its
1525 // native spans and endpoints (a diagnostic read; it adds no station),
1526 // within the floor ([`standoff_tracking_rungs`]). A total residual
1527 // under standoff + floor does not prove that: a tangential slip t over
1528 // a standoff g reads sqrt(g^2 + t^2) < g + t.
1529 // `BREP_PCURVE_STANDOFF_TRACKING=0`: diagnostic A/B hatch, not a
1530 // policy. It restores the pre-lane answer (35c8610a1's parent: an
1531 // off-carrier first pass is returned as it stands) for a same-binary
1532 // attribution of a volume or census delta to this lane. Default (unset
1533 // or any other value) is unchanged. It does not touch the in-floor
1534 // tracking rungs or their raise rule, nor the range lane's end-span
1535 // challenger.
1536 if !standoff_tracking_enabled() {
1537 return Ok(best);
1538 }
1539 return standoff_tracking_rungs(surface, curve, domain, u_domain, v_domain, floor, joins,
1540 parameters, raw, pcurve, best, trace, call, &mut insert);
1541 }
1542 // Each target starts from the same baseline station set: retaining the
1543 // coarser target's inserts can exhaust the cap before the finer target
1544 // places its own error-directed stations. Keep the best rung
1545 // [`tracking_candidate_accepted`] takes.
1546 let baseline_parameters = parameters.clone();
1547 let baseline_raw = raw.clone();
1548 let baseline_curve = pcurve.clone();
1549 let baseline_off_surface = best.off_surface;
1550 let baseline_clamped = best.clamped;
1551 // Whether the first pass MISSED the floor: only then may a tracking
1552 // candidate that reaches the sample ceiling still missing its request take
1553 // the existing one-time raise (2026-10-04, 20-degree crossing, turn 295:
1554 // a 1.964e-6 first pass, a candidate at 600 samples reading 1.068e-7,
1555 // declined; the raise needed residual > off_surface + floor). A first pass
1556 // within the floor keeps exactly the work it did before.
1557 // (On this path the edge stands within the floor of the carrier, so a
1558 // first pass over the floor is a tracking miss, not inherited standoff.)
1559 let standing_misses = best.residual > floor;
1560 Ok(tracking_rungs(
1561 best,
1562 floor,
1563 trace,
1564 call,
1565 true,
1566 |fraction| {
1567 let mut refined_parameters = baseline_parameters.clone();
1568 let mut refined_raw = baseline_raw.clone();
1569 let mut refined_curve = baseline_curve.clone();
1570 let candidate = refine_pcurve_pass(surface, curve, domain, u_domain, v_domain,
1571 floor, joins, &mut refined_parameters, &mut refined_raw, &mut refined_curve,
1572 baseline_off_surface, baseline_clamped, true, fraction, standing_misses, &mut insert, reject_bounds && !standing_misses);
1573 (candidate, (refined_parameters, refined_raw, refined_curve))
1574 },
1575 |state: &RungState| tracking_acceptance_residual(surface, curve, &state.2, &state.0, domain),
1576 |state: &RungState| state.0.len(),
1577 |(refined_parameters, refined_raw, refined_curve): RungState| {
1578 *parameters = refined_parameters;
1579 *raw = refined_raw;
1580 *pcurve = refined_curve;
1581 },
1582 ))
1583}
1584
1585/// A tracking candidate's station parameters, raw surface parameters and
1586/// pcurve, as [`refine_pcurve`] hands them to [`tracking_rungs`].
1587type RungState = (Vec<f64>, Vec<[f64; 2]>, NurbsCurve);
1588
1589/// The tracking rungs of [`refine_pcurve`] after an eligible first pass: for
1590/// each tracking fraction, skip it if the standing fit is already that far
1591/// inside the floor, otherwise run the candidate pass (`run_pass`), judge it
1592/// ([`judge_tracking_candidate`], reading `dense` only when needed), trace
1593/// it, and either take it (`adopt` its state; it becomes the standing fit) or
1594/// stop. `samples` is the candidate's station count, for the trace. Separated
1595/// so the accepted-budget state can be driven with scripted passes;
1596/// `thread_dense` is `true` in production and `false` only to show, in a
1597/// test, what dropping the nineteen-point standing flag would take.
1598#[allow(clippy::too_many_arguments)]
1599fn tracking_rungs<S>(
1600 mut best: Refinement,
1601 floor: f64,
1602 trace: bool,
1603 call: u64,
1604 thread_dense: bool,
1605 mut run_pass: impl FnMut(f64) -> (Result<Refinement, String>, S),
1606 mut dense: impl FnMut(&S) -> Result<f64, String>,
1607 samples: impl Fn(&S) -> usize,
1608 mut adopt: impl FnMut(S),
1609) -> Refinement {
1610 // Whether the standing reading is a nineteen-point one (a budget-exit
1611 // candidate was taken); every later candidate is then judged on it.
1612 let mut standing_dense = false;
1613 let reject_bounds = PCURVE_REJECT_BOUNDS.with(|state| state.get());
1614 for fraction in [0.1, 0.01] {
1615 if best.residual <= floor * fraction {
1616 if trace {
1617 eprintln!(
1618 "PCURVE-TRACK call={call} candidate fraction={fraction} skipped best_residual={:e} <= {:e}",
1619 best.residual,
1620 floor * fraction
1621 );
1622 }
1623 continue;
1624 }
1625 let (candidate, state) = run_pass(fraction);
1626 // The reading the candidate is judged on and the decision (see
1627 // judge_tracking_candidate); an unreadable read declines like a
1628 // failed pass.
1629 let judged_dense = matches!(&candidate, Ok(seen) if seen.exit != PcurveFitExit::Converged || standing_dense);
1630 let (effective, accepted) = match &candidate {
1631 Ok(seen) => judge_tracking_candidate_with_bounds(best.residual, standing_dense, floor, seen.exit, seen.residual, || {
1632 dense(&state)
1633 }, reject_bounds),
1634 Err(_) => (Ok(None), false),
1635 };
1636 if trace {
1637 let judged_on = match &effective {
1638 Ok(Some(reading)) => format!("{reading:e}"),
1639 Ok(None) => "-".to_string(),
1640 Err(message) => format!("error:{message:?}"),
1641 };
1642 match &candidate {
1643 Ok(seen) => eprintln!(
1644 "PCURVE-TRACK call={call} candidate fraction={fraction} exit={:?} residual={:e} judged_on={judged_on} standing_dense={standing_dense} floor={floor:e} off_surface={:e} samples={} raised={} residual_meets_floor={} decision={}",
1645 seen.exit,
1646 seen.residual,
1647 seen.off_surface,
1648 samples(&state),
1649 seen.raised,
1650 seen.residual <= floor,
1651 if accepted { "accept" } else { "decline-stop" }
1652 ),
1653 Err(message) => eprintln!(
1654 "PCURVE-TRACK call={call} candidate fraction={fraction} error={message:?} decision=decline-stop"
1655 ),
1656 }
1657 }
1658 match candidate {
1659 Ok(mut candidate) if accepted => {
1660 // Report the reading it was taken on (its own, if Converged).
1661 if let Ok(Some(reading)) = effective {
1662 candidate.residual = reading;
1663 }
1664 // From here on the standing reading is a nineteen-point one.
1665 if thread_dense {
1666 standing_dense |= judged_dense;
1667 }
1668 adopt(state);
1669 best = candidate;
1670 }
1671 _ => break,
1672 }
1673 }
1674 best
1675}
1676
1677/// Whether [`refine_pcurve`] takes [`standoff_tracking_rungs`] for an
1678/// off-carrier first pass: true unless `BREP_PCURVE_STANDOFF_TRACKING=0`
1679/// (diagnostic A/B hatch, read once per process).
1680fn standoff_tracking_enabled() -> bool {
1681 switch!("BREP_PCURVE_STANDOFF_TRACKING", true)
1682}
1683
1684/// The tracking rungs for an edge curve that stands OFF its carrier by more
1685/// than the floor ([`refine_pcurve`]). The first pass is read natively
1686/// ([`native_read`] on its own knot spans and endpoints); when its tracking is
1687/// within the floor, or the read fails, nothing more is done. Otherwise each
1688/// candidate pass (fractions 0.1 then 0.01 of the floor, from the same
1689/// baseline stations, taking the one-time raise when its own tracking target
1690/// is cut short by the ceiling) is read with the standing fit at the SAME
1691/// probes (both curves' native spans and endpoints) and taken only per
1692/// [`standoff_candidate_accepted`]: tracking finite, within the unchanged
1693/// floor and strictly lower, same-parameter deviation no worse, refinement
1694/// fields finite. A taken fit is REPORTED with its same-probe deviation
1695/// (inherited standoff included, so still over the floor) and with an
1696/// `off_surface` that is the larger of its station reading and the probes'
1697/// standoff. Any error, or a candidate that does not qualify, stops the loop
1698/// and keeps the standing curve and report.
1699#[allow(clippy::too_many_arguments)]
1700fn standoff_tracking_rungs(
1701 surface: &NurbsSurface,
1702 curve: &NurbsCurve,
1703 domain: [f64; 2],
1704 u_domain: [f64; 2],
1705 v_domain: [f64; 2],
1706 floor: f64,
1707 joins: &[f64],
1708 parameters: &mut Vec<f64>,
1709 raw: &mut Vec<[f64; 2]>,
1710 pcurve: &mut NurbsCurve,
1711 mut best: Refinement,
1712 trace: bool,
1713 call: u64,
1714 insert: &mut impl FnMut(f64, Vec3, Vec3) -> Result<(Option<[f64; 2]>, f64), String>,
1715) -> Result<Refinement, String> {
1716 let first = native_probe_fractions(&[&*pcurve]).and_then(|probes| native_read(surface, curve, pcurve, domain, &probes));
1717 if trace {
1718 eprintln!("PCURVE-TRACK call={call} standoff-tracking first-pass native={first:?} floor={floor:e}");
1719 }
1720 let Ok(first) = first else { return Ok(best) };
1721 if first.tracking <= floor {
1722 return Ok(best);
1723 }
1724 let baseline_parameters = parameters.clone();
1725 let baseline_raw = raw.clone();
1726 let baseline_curve = pcurve.clone();
1727 for fraction in [0.1, 0.01] {
1728 let mut refined_parameters = baseline_parameters.clone();
1729 let mut refined_raw = baseline_raw.clone();
1730 let mut refined_curve = baseline_curve.clone();
1731 let candidate = refine_pcurve_pass(surface, curve, domain, u_domain, v_domain, floor, joins,
1732 &mut refined_parameters, &mut refined_raw, &mut refined_curve, best.off_surface, best.clamped,
1733 true, fraction, true, &mut *insert, false);
1734 let reads = candidate.as_ref().ok().map(|_| {
1735 native_probe_fractions(&[&*pcurve, &refined_curve]).and_then(|probes| {
1736 Ok((native_read(surface, curve, pcurve, domain, &probes)?, native_read(surface, curve, &refined_curve, domain, &probes)?))
1737 })
1738 });
1739 let fields_finite = matches!(&candidate, Ok(seen) if seen.residual.is_finite() && seen.off_surface.is_finite());
1740 let decision = match &reads {
1741 Some(Ok((standing, read))) => standoff_rung_decision(*standing, *read, fields_finite, floor),
1742 _ => RungDecision::Stop,
1743 };
1744 if trace {
1745 eprintln!(
1746 "PCURVE-TRACK call={call} standoff-tracking fraction={fraction} candidate={} reads={reads:?} decision={}",
1747 match &candidate { Ok(seen) => format!("{:?}/{:e}/raised={}", seen.exit, seen.residual, seen.raised), Err(message) => format!("error:{message:?}") },
1748 match decision { RungDecision::Accept => "accept", RungDecision::Continue => "decline-continue", RungDecision::Stop => "decline-stop" }
1749 );
1750 }
1751 let (Ok(mut taken), Some(Ok((_, read))), RungDecision::Accept) = (candidate, reads, decision) else {
1752 if decision == RungDecision::Continue {
1753 continue;
1754 }
1755 break;
1756 };
1757 taken.residual = read.deviation;
1758 taken.off_surface = taken.off_surface.max(read.standoff);
1759 *parameters = refined_parameters;
1760 *raw = refined_raw;
1761 *pcurve = refined_curve;
1762 best = taken;
1763 if read.tracking <= floor * 0.01 {
1764 break;
1765 }
1766 }
1767 Ok(best)
1768}
1769
1770/// What one off-carrier tracking rung does with its candidate
1771/// ([`standoff_tracking_rungs`]).
1772#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1773enum RungDecision {
1774 /// Taken ([`standoff_candidate_accepted`]).
1775 Accept,
1776 /// Not taken, but it read finite and LOWER tracking than the standing fit
1777 /// while over the floor: the finer fraction is still tried from the same
1778 /// baseline (fixture 25 on e90c30c94: one use read 2.33e-7 at 0.1 and
1779 /// stopped while its partner was taken at 9.0e-9).
1780 Continue,
1781 /// Not taken and not improving (or unreadable): the rungs end.
1782 Stop,
1783}
1784
1785fn standoff_rung_decision(standing: NativeRead, candidate: NativeRead, fields_finite: bool, floor: f64) -> RungDecision {
1786 if standoff_candidate_accepted(standing, candidate, fields_finite, floor) {
1787 RungDecision::Accept
1788 } else if candidate.tracking.is_finite() && candidate.tracking < standing.tracking {
1789 RungDecision::Continue
1790 } else {
1791 RungDecision::Stop
1792 }
1793}
1794
1795/// The one-time raise decision of [`refine_pcurve_pass`]: a ceiling exit not
1796/// yet raised, when the returned curve is worse than the edge's own gap by
1797/// more than the floor (the original rule), or when a tracking pass for an
1798/// unmet first pass returned a curve whose own tracking still misses the
1799/// pass's target (`unmet_tracking`, read on the returned curve). Nothing else
1800/// raises.
1801fn takes_raise(exit: PcurveFitExit, raised: bool, residual: f64, off_surface: f64, floor: f64, unmet_tracking: bool) -> bool {
1802 exit == PcurveFitExit::SampleCeiling && !raised && (residual > off_surface + floor || unmet_tracking)
1803}
1804
1805fn refine_pcurve_pass(
1806 surface: &NurbsSurface,
1807 curve: &NurbsCurve,
1808 [t0, t1]: [f64; 2],
1809 [u0, u1]: [f64; 2],
1810 [v0, v1]: [f64; 2],
1811 floor: f64,
1812 joins: &[f64],
1813 parameters: &mut Vec<f64>,
1814 raw: &mut Vec<[f64; 2]>,
1815 pcurve: &mut NurbsCurve,
1816 mut off_surface: f64,
1817 mut clamped: [f64; 2],
1818 track_projection: bool,
1819 tracking_fraction: f64,
1820 raise_unmet_tracking: bool,
1821 mut insert: impl FnMut(f64, Vec3, Vec3) -> Result<(Option<[f64; 2]>, f64), String>,
1822 reject_budget: bool,
1823) -> Result<Refinement, String> {
1824 let _clock = PcurvePhaseClock::new("refinement", parameters.len(), track_projection);
1825 let tracking_floor = if track_projection { floor * tracking_fraction } else { floor };
1826 let mut max_samples = MAX_PCURVE_SAMPLES;
1827 let mut raised = false;
1828 loop {
1829 // Every way out of this loop is CLASSIFIED (`PcurveFitExit`); a budget
1830 // exit returns the curve it built, but says so. Falling off the end
1831 // after a fourth round of inserts is the round-budget exit.
1832 let mut exit = PcurveFitExit::RoundBudget;
1833 let mut residual = None;
1834 for round in 0..4 {
1835 if parameters.len() >= max_samples {
1836 exit = PcurveFitExit::SampleCeiling;
1837 break;
1838 }
1839 let mut inserts = Vec::new();
1840 let mut round_max = 0.0_f64;
1841 let mut truncated = false;
1842 let mut skipped_narrow = false;
1843 for index in 0..parameters.len() - 1 {
1844 if parameters.len() + inserts.len() >= max_samples {
1845 truncated = true;
1846 break;
1847 }
1848 let start = parameters[index];
1849 let end = parameters[index + 1];
1850 // Keep construction's existing first-pass policy. Extended
1851 // residual probes must not silently change its station ladder;
1852 // projection tracking retains its established finer floor.
1853 if end - start < if track_projection { SPAN_FLOOR } else { 1e-3 } {
1854 skipped_narrow = true;
1855 continue;
1856 }
1857 for local_fraction in [0.25, 0.5, 0.75] {
1858 if parameters.len() + inserts.len() >= max_samples {
1859 truncated = true;
1860 break;
1861 }
1862 let fraction = start + (end - start) * local_fraction;
1863 let parameter = pcurve.evaluate(fraction)?;
1864 let on_surface =
1865 section_surface_point(surface, parameter.x, parameter.y, true)?;
1866 let on_curve = section_curve_point(curve, t0 + (t1 - t0) * fraction)?;
1867 let deviation = on_surface.sub(on_curve).length();
1868 // A non-finite distance must not reach `f64::max` (which
1869 // drops NaN) or the classification below, where a round
1870 // with an unreadable station could still exit Converged on
1871 // its readable ones. Fail closed: a first pass's error
1872 // goes to its caller, a tracking candidate's declines it.
1873 if !deviation.is_finite() {
1874 return Err(format!(
1875 "refine_pcurve_pass: non-finite station distance {deviation} at fraction {fraction}"
1876 ));
1877 }
1878 round_max = round_max.max(deviation);
1879 if deviation <= tracking_floor {
1880 continue;
1881 }
1882 let (surface_parameter, gap) = insert(fraction, parameter, on_curve)?;
1883 off_surface = off_surface.max(gap);
1884 if let Some(surface_parameter) = surface_parameter {
1885 // The raised pass stops where the pcurve TRACKS the
1886 // edge curve's projection to the floor — the best any
1887 // pcurve can do at this station. Not `deviation - gap`:
1888 // a surface point at tangential distance d from the
1889 // projection of a point g off the surface is only
1890 // d^2/(2g) further from it, so the deviation test is
1891 // quadratically blind to sideways error on off-surface
1892 // input (6e-6 of slop at g = 1.9e-4, floor 1e-7).
1893 if raised || track_projection {
1894 let projected =
1895 section_surface_point(surface, surface_parameter[0], surface_parameter[1], false)?;
1896 if on_surface.sub(projected).length() <= tracking_floor {
1897 continue;
1898 }
1899 }
1900 inserts.push((index + 1, fraction, surface_parameter));
1901 }
1902 }
1903 }
1904 if inserts.is_empty() {
1905 // Nothing to insert: every probed station is within the floor
1906 // (converged — unless a narrow span was skipped, in which case
1907 // the sweep below decides), or the ones over it could not be
1908 // improved (stalled — the edge curve is off the surface there).
1909 exit = if truncated {
1910 PcurveFitExit::SampleCeiling
1911 } else if round_max > floor {
1912 PcurveFitExit::Stalled
1913 } else if skipped_narrow {
1914 PcurveFitExit::SpanFloor
1915 } else {
1916 residual = Some(round_max);
1917 PcurveFitExit::Converged
1918 };
1919 break;
1920 }
1921 // With inserts still needed after the fourth non-truncated
1922 // round, this exit is RoundBudget. An in-floor standing fit
1923 // never adopts any budget exit; rebuilding and probing this
1924 // discarded candidate cannot affect its curve or report.
1925 if reject_budget && round == 3 && !truncated {
1926 if crate::profile::profile_enabled() { profile_line!("pcurve.reject_bound kind=fifth_round"); }
1927 return Err("pcurve tracking candidate requires a fifth round".into());
1928 }
1929 for (at, fraction, surface_parameter) in inserts.into_iter().rev() {
1930 parameters.insert(at, fraction);
1931 raw.insert(at, surface_parameter);
1932 }
1933 let (rebuilt, excursion, distance) = build_interpolant_joined(surface, raw, parameters, joins)?;
1934 *pcurve = rebuilt;
1935 clamped = [excursion, distance];
1936 if truncated {
1937 exit = PcurveFitExit::SampleCeiling;
1938 break;
1939 }
1940 }
1941 let residual = match residual {
1942 Some(measured) => measured,
1943 // A budget exit never re-checked the curve it is returning, and a
1944 // converged round that skipped narrow spans never checked those:
1945 // measure every span.
1946 None => probe_residual(
1947 surface,
1948 curve,
1949 pcurve,
1950 parameters,
1951 [t0, t1],
1952 [u0, u1],
1953 [v0, v1],
1954 )?,
1955 };
1956 if exit == PcurveFitExit::SpanFloor && residual <= floor {
1957 exit = PcurveFitExit::Converged;
1958 }
1959 // The per-trim budget decision. Deterministic, grow-once, capped (I3):
1960 // the ceiling bound and the returned curve is worse than the edge
1961 // curve's own gap from the surface by more than the floor, so samples
1962 // can still buy accuracy. A curve already at its gap stops here —
1963 // reported, not retried.
1964 // A TRACKING pass for a standing fit that missed the floor (and only
1965 // then) also takes the same one-time raise when it hits the ceiling
1966 // still over the floor: its request is the projection's tracking, and
1967 // the ceiling cut it short. The raised maximum itself is unchanged.
1968 // The added raise serves a TRACKING pass for an unmet first pass whose
1969 // RETURNED curve still misses the pass's own target: its tracking (the
1970 // image against the projected foot, read natively on its own knot spans
1971 // and endpoints) over the tracking floor (floor x fraction). Reaching
1972 // the ceiling alone is exhaustion, not a miss, and the inherited
1973 // standoff never triggers it. Read only at a ceiling exit not yet
1974 // raised; an unreadable read does not raise.
1975 let unmet_tracking = track_projection
1976 && raise_unmet_tracking
1977 && exit == PcurveFitExit::SampleCeiling
1978 && !raised
1979 && native_probe_fractions(&[&*pcurve])
1980 .and_then(|probes| native_read(surface, curve, pcurve, [t0, t1], &probes))
1981 .is_ok_and(|read| read.tracking > tracking_floor);
1982 if takes_raise(exit, raised, residual, off_surface, floor, unmet_tracking) {
1983 raised = true;
1984 max_samples = PCURVE_RAISED_SAMPLES;
1985 continue;
1986 }
1987 return Ok(Refinement {
1988 exit,
1989 residual,
1990 off_surface,
1991 raised,
1992 clamped,
1993 });
1994 }
1995}
1996
1997/// The exact inverse of an affine carrier's parameterization.
1998///
1999/// An affine patch (2x2 net, degree 1 both ways, equal weights) maps `uv` to 3D
2000/// by an affine map, so the inverse — a 3D point to the `uv` of its orthogonal
2001/// projection — is affine too. An affine map commutes with the rational basis:
2002/// sending a curve's control points through it and keeping the weights
2003/// produces the EXACT image of that curve in parameter space, not a fit of it.
2004struct AffineInversion {
2005 origin: Vec3,
2006 du: Vec3,
2007 dv: Vec3,
2008 u0: f64,
2009 v0: f64,
2010 uu: f64,
2011 uv: f64,
2012 vv: f64,
2013 determinant: f64,
2014}
2015
2016impl AffineInversion {
2017 /// `None` when `surface` is not affine, or when its two directions are
2018 /// parallel enough that the inverse is not determined. The caller decides
2019 /// what that means: a refusal on the whole-curve lane, the sampled lane on
2020 /// the range lane.
2021 fn of(surface: &NurbsSurface) -> Result<Option<Self>, String> {
2022 if !surface.is_affine()? {
2023 return Ok(None);
2024 }
2025 let ([u0, _], [v0, _]) = surface_domains(surface)?;
2026 let (origin, du, dv) = surface.deriv1(u0, v0)?;
2027 let uu = du.dot(du);
2028 let uv = du.dot(dv);
2029 let vv = dv.dot(dv);
2030 let determinant = uu * vv - uv * uv;
2031 if determinant.abs() <= 1e-18 {
2032 return Ok(None);
2033 }
2034 Ok(Some(Self {
2035 origin,
2036 du,
2037 dv,
2038 u0,
2039 v0,
2040 uu,
2041 uv,
2042 vv,
2043 determinant,
2044 }))
2045 }
2046
2047 fn uv_of(&self, point: Vec3) -> [f64; 2] {
2048 let delta = point.sub(self.origin);
2049 let along_u = delta.dot(self.du);
2050 let along_v = delta.dot(self.dv);
2051 [
2052 self.u0 + (along_u * self.vv - along_v * self.uv) / self.determinant,
2053 self.v0 + (along_v * self.uu - along_u * self.uv) / self.determinant,
2054 ]
2055 }
2056
2057 /// The exact image of `curve` in this carrier's parameter space, with the
2058 /// curve's own degree, knots and weights.
2059 fn image_of(&self, curve: &NurbsCurve) -> Result<NurbsCurve, String> {
2060 let control_points = curve
2061 .control_points
2062 .iter()
2063 .map(|control| {
2064 let [u, v] = self.uv_of(control.point()?);
2065 Ok(Vec4::from_point(Vec3::new(u, v, 0.0), control.w))
2066 })
2067 .collect::<Result<Vec<_>, String>>()?;
2068 NurbsCurve::new(curve.degree, curve.knots.clone(), control_points)
2069 }
2070}
2071
2072fn sphere_iso_enabled() -> bool {
2073 env_read!("BREP_PCURVE_SPHERE_ISO").is_none_or(|mode| mode != "0")
2074}
2075
2076/// A complete native meridian with the same rational basis as the section.
2077/// Equal positive weights and affinely identical knots make its image minus
2078/// the section a convex combination of control-point differences. Thus their
2079/// maximum norm bounds the whole curve, including the pole endpoints. This
2080/// avoids fitting an arbitrary longitude at a pole into a meridian's interior.
2081/// Every failed recognition retains the ordinary fitter and its diagnostics.
2082fn sphere_iso_fit(surface: &NurbsSurface, curve: &NurbsCurve) -> Option<PcurveFit> {
2083 // Native sphere recognition requires quadratic u. Avoid warming other
2084 // carriers, and validate before recognition indexes a deserialized net.
2085 if surface.degree_u != 2 { return None; }
2086 let [t0, t1] = curve.domain().ok()?;
2087 let [u0, u1] = surface.domain_u().ok()?;
2088 let [v0, v1] = surface.domain_v().ok()?;
2089 if !matches!(surface.analytic(), Some(AnalyticSurface::Sphere { .. })) { return None; }
2090 let middle = curve.evaluate(t0 + (t1 - t0) * 0.5).ok()?;
2091 let projected = project_point_to_surface(surface, middle).ok()?;
2092 let mut candidates = vec![projected.u];
2093 // Cardinal meridians can invert an ulp beside their native knot. A snap
2094 // is only a candidate: the complete control-net bound below must hold.
2095 for &knot in &surface.knots_u {
2096 if (knot - projected.u).abs() <= 1e-12 && !candidates.contains(&knot) { candidates.push(knot); }
2097 }
2098 for u in candidates {
2099 if !(u >= u0 && u <= u1) { continue; }
2100 let iso = surface.iso_curve_u(u).ok()?;
2101 if iso.degree != curve.degree || iso.knots.len() != curve.knots.len()
2102 || iso.control_points.len() != curve.control_points.len() { continue; }
2103 for reverse in [false, true] {
2104 let n = iso.control_points.len();
2105 // Rounded quotient equality alone does not prove equal bases.
2106 // TwoDiff proves the subtractions exact; FMA then proves that
2107 // each normalized knot times its span equals the exact offset.
2108 // Conservative magnitude bounds keep all product residuals well
2109 // above underflow. Unusual domains simply use the ordinary fit.
2110 fn exact_difference(a: f64, b: f64) -> Option<f64> {
2111 if !(a.is_finite() && b.is_finite()) || a.abs().max(b.abs()) > 1e100 { return None; }
2112 let x = a - b;
2113 let bv = a - x;
2114 let av = x + bv;
2115 let error = (a - av) + (bv - b);
2116 (error == 0.).then_some(x)
2117 }
2118 fn exact_fraction(offset: f64, span: f64) -> Option<f64> {
2119 if !(1e-100..=1e100).contains(&span) { return None; }
2120 let ratio = offset / span;
2121 if !(0.0..=1.0).contains(&ratio) || (ratio != 0. && ratio < 1e-100) { return None; }
2122 (ratio.mul_add(span, -offset) == 0.).then_some(ratio)
2123 }
2124 let knots_match = (|| {
2125 let section_span = exact_difference(t1, t0)?;
2126 let native_span = exact_difference(v1, v0)?;
2127 for (i, &knot) in curve.knots.iter().enumerate() {
2128 let section = exact_difference(knot, t0)?;
2129 let native = if reverse { exact_difference(v1, iso.knots[iso.knots.len() - 1 - i])? }
2130 else { exact_difference(iso.knots[i], v0)? };
2131 if exact_fraction(section, section_span)? != exact_fraction(native, native_span)? { return None; }
2132 }
2133 Some(())
2134 })().is_some();
2135 if !knots_match { continue; }
2136 let mut bound = 0.0_f64;
2137 let mut matches = true;
2138 for (i, control) in curve.control_points.iter().enumerate() {
2139 let native = &iso.control_points[if reverse { n - 1 - i } else { i }];
2140 if !(control.w > 0.) || control.w != native.w { matches = false; break; }
2141 let distance = control.point().ok()?.sub(native.point().ok()?).length();
2142 if !distance.is_finite() { matches = false; break; }
2143 bound = bound.max(distance);
2144 }
2145 if !matches || bound > PCURVE_REFINEMENT_TOLERANCE * 0.0625 { continue; }
2146 let (start, end) = if reverse { (v1, v0) } else { (v0, v1) };
2147 let pcurve = NurbsCurve::new(1, vec![0., 0., 1., 1.], vec![
2148 Vec4::from_point(Vec3::new(u, start, 0.), 1.),
2149 Vec4::from_point(Vec3::new(u, end, 0.), 1.),
2150 ]).ok()?;
2151 // Confirm the actual floating-point evaluation too, on the
2152 // ordinary first pass's 24 intervals and quarter stations. The
2153 // control-net proof covers between probes; these reads guard
2154 // roundoff from differing surface/curve evaluation orders.
2155 for i in 0..=96 {
2156 let fraction = i as f64 / 96.;
2157 let uv = pcurve.evaluate(fraction).ok()?;
2158 let image = surface.evaluate(uv.x, uv.y).ok()?;
2159 let on_curve = curve.evaluate(t0 + (t1 - t0) * fraction).ok()?;
2160 let error = image.sub(on_curve).length();
2161 if !error.is_finite() { matches = false; break; }
2162 bound = bound.max(error);
2163 }
2164 if !matches || bound > PCURVE_REFINEMENT_TOLERANCE * 0.0625 { continue; }
2165 return Some(PcurveFit { curve: pcurve, report: PcurveFitReport {
2166 residual: bound, floor: PCURVE_REFINEMENT_TOLERANCE, samples: 2,
2167 exit: PcurveFitExit::Converged, off_surface: bound, raised: false,
2168 clamped_excursion: 0., clamped_distance: 0.,
2169 } });
2170 }
2171 }
2172 None
2173}
2174
2175/// Build a parameter-space curve for a 3D curve lying on a surface, and say
2176/// what the fit achieved.
2177///
2178/// This mirrors the reference imprint implementation: affine patches map
2179/// homogeneous control points exactly; general patches use sampled inversion,
2180/// periodic seam unwrapping, and adaptive 3D residual refinement. The report
2181/// beside the curve carries the residual the RETURNED curve reaches and why
2182/// refinement stopped ([`PcurveFitReport`]); the curve itself is what
2183/// [`build_pcurve_on_surface`] has always returned.
2184pub fn fit_pcurve_on_surface(
2185 surface: &NurbsSurface,
2186 curve: &NurbsCurve,
2187) -> Result<PcurveFit, String> {
2188 if sphere_iso_enabled() {
2189 if let Some(fit) = sphere_iso_fit(surface, curve) { return Ok(fit); }
2190 }
2191 let mode = env_read!("BREP_PCURVE_EVAL_CACHE").unwrap_or_default();
2192 let bounds = env_read!("BREP_PCURVE_REJECT_BOUNDS").unwrap_or_else(|| "on".into());
2193 let stack = env_read!("BREP_PCURVE_STACK_POINT").unwrap_or_else(|| "on".into());
2194 if mode == "check" || bounds == "check" || stack == "check" {
2195 let old = fit_pcurve_on_surface_cached(surface, curve, false, false, false)?;
2196 if matches!(surface.analytic(), Some(AnalyticSurface::Sphere { .. })) {
2197 let candidate = fit_pcurve_on_surface_cached(surface, curve, mode == "check" || mode == "on" || mode == "1", bounds == "check" || bounds == "on" || bounds == "1", stack == "check" || stack == "on" || stack == "1");
2198 let identical = candidate.as_ref().is_ok_and(|new| {
2199 old.curve.degree == new.curve.degree
2200 && old.curve.knots.len() == new.curve.knots.len()
2201 && old.curve.knots.iter().zip(&new.curve.knots).all(|(a, b)| a.to_bits() == b.to_bits())
2202 && old.curve.control_points.len() == new.curve.control_points.len()
2203 && old.curve.control_points.iter().zip(&new.curve.control_points).all(|(a, b)| {
2204 [a.x.to_bits(), a.y.to_bits(), a.z.to_bits(), a.w.to_bits()]
2205 == [b.x.to_bits(), b.y.to_bits(), b.z.to_bits(), b.w.to_bits()]
2206 }) && old.report == new.report
2207 });
2208 if let Ok(path) = std::env::var("BREP_PCURVE_EVAL_CENSUS") {
2209 use std::io::Write;
2210 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
2211 let _ = writeln!(file, "pcurve.eval_census identical={identical} controls={} samples={}", old.curve.control_points.len(), old.report.samples);
2212 }
2213 }
2214 }
2215 return Ok(old);
2216 }
2217 fit_pcurve_on_surface_cached(surface, curve, mode == "1" || mode == "on", bounds == "1" || bounds == "on", stack == "1" || stack == "on")
2218}
2219
2220fn fit_pcurve_on_surface_cached(surface: &NurbsSurface, curve: &NurbsCurve, enabled: bool, bounds: bool, stack: bool) -> Result<PcurveFit, String> {
2221 let _stack = PcurveStackPointScope::open(stack && matches!(surface.analytic(), Some(AnalyticSurface::Sphere { .. })));
2222 // Non-sphere refinement repeats expensive global inversions and surface
2223 // probes. Retain successful exact inputs for this immutable fit only.
2224 // The older sphere-global experiment keeps its existing opt-in policy.
2225 let _samples = PcurveSampleScope::open(surface, curve, enabled
2226 || !matches!(surface.analytic(), Some(AnalyticSurface::Sphere { .. })));
2227 let _bounds = PcurveRejectScope::open(bounds && matches!(surface.analytic(), Some(AnalyticSurface::Sphere { .. })));
2228 let [t0, t1] = curve.domain()?;
2229 if surface.is_affine()? {
2230 let Some(inversion) = AffineInversion::of(surface)? else {
2231 return Err("build_pcurve_on_surface: singular affine parameterization".into());
2232 };
2233 let pcurve = inversion.image_of(curve)?;
2234 return Ok(PcurveFit {
2235 report: PcurveFitReport {
2236 residual: 0.0,
2237 floor: PCURVE_REFINEMENT_TOLERANCE,
2238 samples: pcurve.control_points.len(),
2239 exit: PcurveFitExit::Converged,
2240 off_surface: 0.0,
2241 raised: false,
2242 clamped_excursion: 0.0,
2243 clamped_distance: 0.0,
2244 },
2245 curve: pcurve,
2246 });
2247 }
2248
2249 let scale = 1.0 + curve.evaluate((t0 + t1) / 2.0)?.length();
2250 let drop_tolerance = 1e-4 * scale;
2251 let endpoint_tolerance = 1e-3 * scale;
2252 let refinement_tolerance = PCURVE_REFINEMENT_TOLERANCE;
2253 let ([u0, u1], [v0, v1]) = surface_domains(surface)?;
2254 let invert =
2255 |fraction: f64| invert_checked(surface, section_curve_point(curve, t0 + (t1 - t0) * fraction)?);
2256
2257 let mut parameters = Vec::with_capacity(25);
2258 let mut raw_surface_parameters = Vec::with_capacity(25);
2259 // The edge curve's own distance from the surface at every station inverted,
2260 // accepted or not: the floor below which no pcurve can bring the residual.
2261 let mut off_surface = 0.0_f64;
2262 // The edge's own C0 knots must remain corners of its parameter image.
2263 // Plant those stations before inversion; refinement retains their joins.
2264 let corners = range_breaks(curve, t0, t1, true);
2265 let mut stations: Vec<f64> = (0..=24).map(|index| index as f64 / 24.0).collect();
2266 // Replace nearby uniform seeds, never the native corner itself. Keep
2267 // endpoints and other native corners even when their spans are short.
2268 stations.retain(|station| {
2269 *station == 0.0 || *station == 1.0 || corners.contains(station)
2270 || !corners.iter().any(|corner| (*station - corner).abs() <= SPAN_FLOOR)
2271 });
2272 for &corner in &corners {
2273 let at = stations.partition_point(|value| *value < corner);
2274 if stations.get(at) != Some(&corner) {
2275 stations.insert(at, corner);
2276 }
2277 }
2278 let joins = corners;
2279 for (index, &fraction) in stations.iter().enumerate() {
2280 let (parameter, distance) = invert(fraction)?;
2281 off_surface = off_surface.max(distance);
2282 if distance > drop_tolerance && index != 0 && index != stations.len() - 1 {
2283 continue;
2284 }
2285 if distance > endpoint_tolerance {
2286 if std::env::var("BREP_DEBUG_PCURVE").is_ok() {
2287 let p3 = curve.evaluate(t0 + (t1 - t0) * fraction).ok();
2288 let p0 = curve.evaluate(t0).ok();
2289 let p1 = curve.evaluate(t1).ok();
2290 eprintln!(
2291 "PCURVE-FAIL idx={index} frac={fraction} dist={distance} endpt_tol={endpoint_tolerance} scale={scale}\n curve3D@frac={p3:?}\n curve3D@t0={p0:?} curve3D@t1={p1:?}\n surf_domain=u[{u0},{u1}] v[{v0},{v1}] invpar={parameter:?}\n surf@invpar={:?}",
2292 surface.evaluate(parameter[0], parameter[1]).ok()
2293 );
2294 }
2295 return Err(format!(
2296 "build_pcurve_on_surface: endpoint projection failed (distance={distance})"
2297 ));
2298 }
2299 parameters.push(fraction);
2300 raw_surface_parameters.push(parameter);
2301 }
2302
2303 let (mut pcurve, excursion, distance) =
2304 build_interpolant_joined(surface, &raw_surface_parameters, ¶meters, &joins)?;
2305 let refinement = refine_pcurve(
2306 surface,
2307 curve,
2308 [t0, t1],
2309 [u0, u1],
2310 [v0, v1],
2311 refinement_tolerance,
2312 &joins,
2313 &mut parameters,
2314 &mut raw_surface_parameters,
2315 &mut pcurve,
2316 off_surface,
2317 [excursion, distance],
2318 |fraction, _parameter, _on_curve| {
2319 let (surface_parameter, distance) = invert(fraction)?;
2320 Ok((
2321 (distance <= drop_tolerance).then_some(surface_parameter),
2322 distance,
2323 ))
2324 },
2325 )?;
2326 Ok(PcurveFit {
2327 curve: pcurve,
2328 report: PcurveFitReport {
2329 residual: refinement.residual,
2330 floor: refinement_tolerance,
2331 samples: parameters.len(),
2332 exit: refinement.exit,
2333 off_surface: refinement.off_surface,
2334 raised: refinement.raised,
2335 clamped_excursion: refinement.clamped[0],
2336 clamped_distance: refinement.clamped[1],
2337 },
2338 })
2339}
2340
2341/// [`fit_pcurve_on_surface`] for the callers that only need the curve. The
2342/// report is not dropped: it is tallied on the open [`PcurveFitScope`], if any,
2343/// so the operation that owns this fit can say in its diagnostics whether every
2344/// trim reached the floor.
2345pub fn build_pcurve_on_surface(
2346 surface: &NurbsSurface,
2347 curve: &NurbsCurve,
2348) -> Result<NurbsCurve, String> {
2349 let fit = fit_pcurve_on_surface(surface, curve)?;
2350 record_fit(&fit.report);
2351 Ok(fit.curve)
2352}
2353
2354/// Optional section-fit clock; records every early fallback without changing it.
2355struct SectionFitClock {
2356 started: Option<web_time::Instant>,
2357 global: std::time::Duration,
2358 seeded: bool,
2359}
2360impl SectionFitClock {
2361 fn new() -> Self {
2362 Self { started: crate::profile::profile_enabled().then(web_time::Instant::now),
2363 global: std::time::Duration::ZERO, seeded: false }
2364 }
2365}
2366impl Drop for SectionFitClock {
2367 fn drop(&mut self) {
2368 if let Some(started) = self.started {
2369 crate::imprint::profile_emit(format_args!("pcurve.section_time global={:.3} seeded={:.3} result={}",
2370 self.global.as_secs_f64() * 1000.0,
2371 started.elapsed().saturating_sub(self.global).as_secs_f64() * 1000.0,
2372 if self.seeded { "seeded" } else { "global" }));
2373 }
2374 }
2375}
2376
2377/// SECTION-PCURVE SEEDED MARCH (t222: cone × ABC 00000327, non-integral genus).
2378///
2379/// `build_pcurve_on_surface` inverts every sample by GLOBAL closest point. Where
2380/// the carrier surface is COMPRESSED / self-overlapping along the row the
2381/// section rides, the global search aliases interior samples onto a DISTANT
2382/// preimage sheet, folding the pcurve out past the trim boundary and re-crossing
2383/// it. The arrangement then splits the shared section at that phantom crossing,
2384/// while the mate face (whose carrier is not folded there) keeps the section
2385/// whole — so the two operands' fragments disagree and the section strands
2386/// one-use (non-integral genus). Ground truth for t222: 00000327 face 496's
2387/// v≈0.7875 row maps u∈[0.55,0.81] into a ~0.18mm neighbourhood of the section
2388/// endpoint A; the global pcurve for piece 9 folded out to u=0.806 and back,
2389/// re-crossing trim edge 349 at v≈0.784, 0.003 below the corner vertex A.
2390///
2391/// This marches the interior inversions with each seeded from the PREVIOUS
2392/// accepted parameters, so a marched sample
2393/// adopts the nearby branch instead of the momentarily-closest distant sheet.
2394/// The two ENDPOINT samples keep the deterministic global inversion (they are
2395/// the piece's shared junction vertices — same reasoning as
2396/// `repair_branch_jumps`). Additive + fail-soft: the marched chain is used ONLY
2397/// when it CLOSES onto the global far endpoint (its last interior sample is
2398/// parameter-adjacent to it, measured against the chain's own median step);
2399/// otherwise the plain global build is returned byte-for-byte. A single-preimage
2400/// carrier (the cone side of the same section) marches to the same samples the
2401/// global search already had. Use the analytic-aware seeded projector for both
2402/// the march and inserted stations: raw Newton stops at 1e-7 even on an exact
2403/// cylinder, introducing avoidable trim error. General folded carriers retain
2404/// their local seeded Newton path; analytic carriers take their closed form.
2405///
2406/// Escape hatch: `BREP_SECTION_PCURVE_MARCH=0`.
2407pub fn fit_pcurve_on_surface_marched(
2408 surface: &NurbsSurface,
2409 curve: &NurbsCurve,
2410) -> Result<PcurveFit, String> {
2411 fn run(surface: &NurbsSurface, curve: &NurbsCurve, eager: bool) -> Result<PcurveFit, String> {
2412 // The global fallback owns a nested scope; it cannot populate or
2413 // invalidate the marched fit's immutable carrier/curve entries.
2414 let _samples = PcurveSampleScope::open(surface, curve, true);
2415 let mut clock = SectionFitClock::new();
2416 // The global builder owns only scoped sample caches. Its fit/report is
2417 // discarded on a successful seeded route; do that work on fallback only.
2418 // Profile phase/sample lines for the discarded global fit disappear ON.
2419 let mut fallback = || {
2420 let started = clock.started.map(|_| web_time::Instant::now());
2421 let fit = fit_pcurve_on_surface(surface, curve);
2422 if let Some(started) = started { clock.global += started.elapsed(); }
2423 fit
2424 };
2425 let global = if eager { Some(fallback()?) } else { None };
2426 // The global builder's first fallible geometry read validates the
2427 // curve domain. Preserve that error priority before the affine probe
2428 // when the unused global fit is deferred (both inputs may be invalid).
2429 if !eager { curve.domain()?; }
2430 if env_read!("BREP_SECTION_PCURVE_MARCH").as_deref() == Some("0") || surface.is_affine()? {
2431 return global.map_or_else(&mut fallback, Ok);
2432 }
2433 let [t0, t1] = curve.domain()?;
2434 // A closed (ring) section has no distinct endpoints to anchor the march;
2435 // leave it to the existing path (closed rings are handled elsewhere).
2436 if curve.evaluate(t0)?.sub(curve.evaluate(t1)?).length() <= 1e-6 {
2437 return global.map_or_else(&mut fallback, Ok);
2438 }
2439 const SAMPLES: usize = 24;
2440 let fractions: Vec<f64> = (0..=SAMPLES).map(|i| i as f64 / SAMPLES as f64).collect();
2441 let edge: Vec<Vec3> = fractions
2442 .iter()
2443 .map(|fraction| curve.evaluate(t0 + (t1 - t0) * fraction))
2444 .collect::<Result<Vec<_>, _>>()?;
2445 let scale = 1.0 + curve.evaluate((t0 + t1) / 2.0)?.length();
2446 let endpoint_tolerance = 1e-3 * scale;
2447 let (first, first_distance) = invert_checked(surface, edge[0])?;
2448 let (last, last_distance) = invert_checked(surface, edge[SAMPLES])?;
2449 if first_distance > endpoint_tolerance || last_distance > endpoint_tolerance {
2450 return global.map_or_else(&mut fallback, Ok);
2451 }
2452 // The edge curve's distance from the surface at every station this chain
2453 // inverts — on the MARCHED branch, which is the one being fitted.
2454 let mut off_surface = first_distance.max(last_distance);
2455 let ([u0, u1], [v0, v1]) = surface_domains(surface)?;
2456 let u_span = (u1 - u0).abs().max(EPSILON);
2457 let v_span = (v1 - v0).abs().max(EPSILON);
2458 let (closed_u, closed_v) = surface_closedness(surface)?;
2459 let norm_step = |a: [f64; 2], b: [f64; 2]| -> f64 {
2460 let mut du = a[0] - b[0];
2461 if closed_u {
2462 while du > 0.5 * u_span {
2463 du -= u_span;
2464 }
2465 while du < -0.5 * u_span {
2466 du += u_span;
2467 }
2468 }
2469 let mut dv = a[1] - b[1];
2470 if closed_v {
2471 while dv > 0.5 * v_span {
2472 dv -= v_span;
2473 }
2474 while dv < -0.5 * v_span {
2475 dv += v_span;
2476 }
2477 }
2478 ((du / u_span).powi(2) + (dv / v_span).powi(2)).sqrt()
2479 };
2480 // Forward seeded march over the interior samples.
2481 let mut raw = vec![first];
2482 let mut previous = first;
2483 for index in 1..SAMPLES {
2484 let seeded =
2485 crate::projection::project_point_to_surface_from_seed(surface, edge[index], previous)?;
2486 let (_, global_distance) = invert_checked(surface, edge[index])?;
2487 // The seeded footpoint must still sit on the surface — as tight as the
2488 // fit band or the global answer already had it. If it fell off (the
2489 // seed led Newton into a valley), abandon the march and keep global.
2490 if seeded.distance > 4.0 * global_distance + endpoint_tolerance {
2491 return global.map_or_else(&mut fallback, Ok);
2492 }
2493 off_surface = off_surface.max(seeded.distance);
2494 previous = [seeded.u, seeded.v];
2495 raw.push(previous);
2496 }
2497 raw.push(last);
2498 // CLOSURE GATE: the last marched interior sample must be parameter-adjacent
2499 // to the global far endpoint — the march stayed on ONE branch the whole way.
2500 // Measure the endpoint gap against the chain's OWN median step so a genuine
2501 // long edge is not rejected while a sheet-hop (which leaves a big gap to the
2502 // endpoint) is.
2503 let mut steps: Vec<f64> = (1..raw.len())
2504 .map(|i| norm_step(raw[i], raw[i - 1]))
2505 .collect();
2506 let endpoint_gap = steps.pop().unwrap_or(0.0);
2507 steps.sort_by(f64::total_cmp);
2508 let median = steps
2509 .get(steps.len() / 2)
2510 .copied()
2511 .unwrap_or(0.0)
2512 .max(EPSILON);
2513 if endpoint_gap > (4.0 * median).max(0.05) {
2514 return global.map_or_else(&mut fallback, Ok);
2515 }
2516 // Build the interpolant from the marched samples, then refine — re-projecting
2517 // each insert SEEDED from the interpolant (already on the marched branch), so
2518 // a mid-refinement global inversion cannot re-alias onto the far sheet.
2519 let mut parameters = fractions;
2520 let (mut pcurve, excursion, distance) = build_interpolant(surface, &raw, ¶meters)?;
2521 let refinement_tolerance = PCURVE_REFINEMENT_TOLERANCE;
2522 // Same classified exits and budget ladder as `fit_pcurve_on_surface`. A
2523 // seeded insert always lands, so this loop cannot stall.
2524 let refinement = refine_pcurve(
2525 surface,
2526 curve,
2527 [t0, t1],
2528 [u0, u1],
2529 [v0, v1],
2530 refinement_tolerance,
2531 &[],
2532 &mut parameters,
2533 &mut raw,
2534 &mut pcurve,
2535 off_surface,
2536 [excursion, distance],
2537 |_fraction, seed, on_curve| {
2538 let seeded = crate::projection::project_point_to_surface_from_seed(surface, on_curve, [seed.x, seed.y])?;
2539 Ok((Some([seeded.u, seeded.v]), seeded.distance))
2540 },
2541 )?;
2542 clock.seeded = true;
2543 Ok(PcurveFit {
2544 curve: pcurve,
2545 report: PcurveFitReport {
2546 residual: refinement.residual,
2547 floor: refinement_tolerance,
2548 samples: parameters.len(),
2549 exit: refinement.exit,
2550 off_surface: refinement.off_surface,
2551 raised: refinement.raised,
2552 clamped_excursion: refinement.clamped[0],
2553 clamped_distance: refinement.clamped[1],
2554 },
2555 })
2556 }
2557
2558 let mode = match std::env::var("BREP_PCURVE_LAZY_GLOBAL") {
2559 Ok(mode) => mode,
2560 Err(std::env::VarError::NotPresent) => "on".to_owned(),
2561 Err(std::env::VarError::NotUnicode(_)) => String::new(),
2562 };
2563 if mode != "check" {
2564 return run(surface, curve, !matches!(mode.as_str(), "1" | "on"));
2565 }
2566 // Clone BEFORE eager evaluation: lazy must start with the same carrier
2567 // cache state, rather than benefiting from eager's recognition/projection.
2568 let lazy_surface = surface.clone();
2569 let lazy_curve = curve.clone();
2570 let eager = run(surface, curve, true);
2571 let lazy = run(&lazy_surface, &lazy_curve, false);
2572 let identical = match (&eager, &lazy) {
2573 (Ok(a), Ok(b)) => {
2574 a.curve.degree == b.curve.degree
2575 && a.curve.knots.len() == b.curve.knots.len()
2576 && a.curve.knots.iter().zip(&b.curve.knots).all(|(a,b)| a.to_bits() == b.to_bits())
2577 && a.curve.control_points.len() == b.curve.control_points.len()
2578 && a.curve.control_points.iter().zip(&b.curve.control_points).all(|(a,b)| {
2579 [a.x.to_bits(),a.y.to_bits(),a.z.to_bits(),a.w.to_bits()]
2580 == [b.x.to_bits(),b.y.to_bits(),b.z.to_bits(),b.w.to_bits()]
2581 })
2582 && a.report == b.report
2583 && [a.report.residual, a.report.floor, a.report.off_surface,
2584 a.report.clamped_excursion, a.report.clamped_distance].map(f64::to_bits)
2585 == [b.report.residual, b.report.floor, b.report.off_surface,
2586 b.report.clamped_excursion, b.report.clamped_distance].map(f64::to_bits)
2587 }
2588 (Err(a), Err(b)) => a == b,
2589 _ => false,
2590 };
2591 if let Ok(path) = std::env::var("BREP_PCURVE_LAZY_GLOBAL_CENSUS") {
2592 use std::io::Write;
2593 let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)
2594 .expect("open lazy-global differential ledger");
2595 writeln!(file, "pcurve.lazy_global identical={identical} eager_ok={} lazy_ok={} degree={} controls={}",
2596 eager.is_ok(), lazy.is_ok(), curve.degree, curve.control_points.len())
2597 .expect("append lazy-global differential ledger");
2598 }
2599 assert!(identical, "lazy global pcurve fallback changed the curve, report or error");
2600 eager
2601}
2602
2603/// [`fit_pcurve_on_surface_marched`] for the callers that only need the curve;
2604/// the report is tallied on the open [`PcurveFitScope`] exactly as
2605/// [`build_pcurve_on_surface`] does.
2606pub fn build_pcurve_on_surface_marched(
2607 surface: &NurbsSurface,
2608 curve: &NurbsCurve,
2609) -> Result<NurbsCurve, String> {
2610 let fit = fit_pcurve_on_surface_marched(surface, curve)?;
2611 record_fit(&fit.report);
2612 Ok(fit.curve)
2613}
2614
2615/// Build a pcurve for a represented subrange of a larger edge curve.
2616///
2617/// On an affine carrier the answer is exact and is taken as such
2618/// (`affine_pcurve_on_range`). Otherwise this samples only the represented
2619/// interval, which is essential when off-interval control points do not lie on
2620/// the target carrier — the exact lane restricts the net by knot insertion
2621/// first, so those control points are removed rather than ignored.
2622pub fn fit_pcurve_on_surface_range(
2623 surface: &NurbsSurface,
2624 curve: &NurbsCurve,
2625 edge_start: f64,
2626 edge_end: f64,
2627 forward: bool,
2628 tolerance: f64,
2629) -> Result<PcurveFit, String> {
2630 if let Some(exact) =
2631 affine_pcurve_on_range(surface, curve, edge_start, edge_end, forward, tolerance)?
2632 {
2633 return Ok(exact);
2634 }
2635 fit_pcurve_on_surface_range_dense(
2636 surface, curve, edge_start, edge_end, forward, tolerance, 64, 3, 513,
2637 )
2638}
2639
2640/// [`fit_pcurve_on_surface_range`] for the callers that only need the curve:
2641/// the fit is ACCEPTED or REFUSED on its bar (`accept_fit`), never returned
2642/// off it.
2643pub fn build_pcurve_on_surface_range(
2644 surface: &NurbsSurface,
2645 curve: &NurbsCurve,
2646 edge_start: f64,
2647 edge_end: f64,
2648 forward: bool,
2649 tolerance: f64,
2650) -> Result<NurbsCurve, String> {
2651 accept_fit(fit_pcurve_on_surface_range(
2652 surface, curve, edge_start, edge_end, forward, tolerance,
2653 )?)
2654}
2655
2656/// `BREP_DEBUG_STATION_FIT`: trace every station fit round by round, and the
2657/// stations around the worst span of a fit off its bar. Read once per process.
2658fn station_fit_tracing() -> bool {
2659 debug_flag!("BREP_DEBUG_STATION_FIT")
2660}
2661
2662/// Narrowest span, as a fraction of the parameter range, that
2663/// [`fit_pcurve_on_surface_stations`] subdivides. `refine_pcurve` floors at
2664/// 1e-3 from 25 base samples; this fitter starts from 64 and quarters, so a
2665/// floor of 1e-3 bound on its first pass and refused 67 of the 57-document
2666/// corpus's bodies (measured 2026-09-26). 1e-6 admits every halving a real
2667/// trim needs and stops the runaway that fits stations 4e-7 apart.
2668const SPAN_FLOOR: f64 = 1e-6;
2669
2670/// Rounds the raised pass of [`fit_pcurve_on_surface_stations`] may spend:
2671/// enough halvings for any span the sample cap can resolve.
2672const RAISED_ROUNDS: usize = 40;
2673
2674/// The refusal every station builder returns for a fit off its bar.
2675pub const PCURVE_OFF_BAR: &str = "pcurve fit refused: the trim misses its stations by";
2676
2677/// The one acceptance decision behind [`build_pcurve_on_surface_range`],
2678/// [`build_pcurve_on_surface_range_dense`] and
2679/// [`build_pcurve_on_surface_stations`]: a fit on its bar
2680/// ([`PcurveFitReport::on_bar`]) is the curve, a fit off it is a refusal that
2681/// names the residual, the bar, what was asked, how far the stations stand off
2682/// the carrier and why refinement stopped.
2683///
2684/// Until 2026-09-26 these builders returned whatever their last round built
2685/// without measuring it (the 2026-09-18 census: 4564 of 54556 imported pcurves
2686/// over the ask they were built to, 2072 of them at a station the refinement
2687/// itself had tested and given up on). Nothing here widens a band: the bar is
2688/// the caller's own ask plus what the input makes unreachable.
2689fn accept_fit(fit: PcurveFit) -> Result<NurbsCurve, String> {
2690 let report = fit.report;
2691 if report.on_bar() {
2692 return Ok(fit.curve);
2693 }
2694 Err(off_bar_message(&report))
2695}
2696
2697fn off_bar_message(report: &PcurveFitReport) -> String {
2698 format!(
2699 "{PCURVE_OFF_BAR} {:.3e} out of sample against a bar of {:.3e} (asked {:.1e}, the \
2700 stations stand {:.3e} off the carrier; {:?} at {} samples{}{})",
2701 report.residual,
2702 report.floor + report.off_surface,
2703 report.floor,
2704 report.off_surface,
2705 report.exit,
2706 report.samples,
2707 if report.raised { ", after the raise" } else { "" },
2708 if report.clamped_excursion > 0.0 {
2709 format!(
2710 "; clamped into the chart by {:.3e} in parameter, {:.3e} in 3D",
2711 report.clamped_excursion, report.clamped_distance
2712 )
2713 } else {
2714 String::new()
2715 },
2716 )
2717}
2718
2719/// The class slug of a pcurve fit refused off its bar
2720/// ([`crate::RefusalClass::NonConvergence`]): minted by
2721/// [`build_pcurve_on_surface_range_classed`] and read by
2722/// [`is_pcurve_off_bar`], so a dispatcher decides on the class, never on the
2723/// message text.
2724pub(crate) const PCURVE_OFF_BAR_WHAT: &str = "pcurve_fit_off_bar";
2725
2726/// Whether `refusal` is a pcurve fit that could not reach its bar.
2727pub(crate) fn is_pcurve_off_bar(refusal: &crate::KernelRefusal) -> bool {
2728 matches!(&refusal.class, crate::RefusalClass::NonConvergence { what } if what == PCURVE_OFF_BAR_WHAT)
2729}
2730
2731/// [`build_pcurve_on_surface_range`] with a typed refusal: a fit off its bar
2732/// is `NonConvergence { what: PCURVE_OFF_BAR_WHAT }` at `stage`, with the same
2733/// message; any other failure of the fit is `Internal` under `what`.
2734pub(crate) fn build_pcurve_on_surface_range_classed(
2735 surface: &NurbsSurface,
2736 curve: &NurbsCurve,
2737 edge_start: f64,
2738 edge_end: f64,
2739 forward: bool,
2740 tolerance: f64,
2741 stage: KernelStage,
2742 what: &'static str,
2743) -> Result<NurbsCurve, crate::KernelRefusal> {
2744 use crate::OrRefuse;
2745 let fit = fit_pcurve_on_surface_range(surface, curve, edge_start, edge_end, forward, tolerance)
2746 .or_refuse(stage, what)?;
2747 accept_fit_classed(fit, stage)
2748}
2749
2750/// [`accept_fit`] with the refusal typed: `NonConvergence { what:
2751/// PCURVE_OFF_BAR_WHAT }` at `stage`, the message unchanged.
2752pub(crate) fn accept_fit_classed(fit: PcurveFit, stage: KernelStage) -> Result<NurbsCurve, crate::KernelRefusal> {
2753 if fit.report.on_bar() {
2754 return Ok(fit.curve);
2755 }
2756 Err(crate::KernelRefusal {
2757 class: crate::RefusalClass::NonConvergence { what: PCURVE_OFF_BAR_WHAT.into() },
2758 stage,
2759 message: off_bar_message(&fit.report),
2760 })
2761}
2762
2763/// The EXACT pcurve for a coedge's subrange of `curve` on an affine carrier,
2764/// or `None` when there is no such answer and the sampled lane must run.
2765///
2766/// [`fit_pcurve_on_surface`] has always mapped a WHOLE curve's control net
2767/// exactly on an affine carrier. A coedge's trim is that same curve restricted
2768/// to `[edge_start, edge_end]` and oriented by `forward`, and both operations
2769/// are exact on a NURBS: [`NurbsCurve::split`] subdivides by knot insertion,
2770/// [`NurbsCurve::reversed`] mirrors the net, and rescaling the knots to
2771/// `[0, 1]` is affine in the edge's own parameter — which is precisely the
2772/// fraction convention the sampled lane uses, so the pcurve's correspondence
2773/// with the edge is the same one either lane produces.
2774///
2775/// Sampling where an exact answer exists leaves a real error: measured
2776/// 2026-09-17, a booleaned band rim's two planar trims sat 2.379e-4 and
2777/// 2.395e-4 off their edges, where this lane lands 1.4e-13 off.
2778///
2779/// The image is verified against the caller's own `tolerance` before it is
2780/// returned. The exact image of a curve that does NOT lie on this carrier is
2781/// its orthogonal projection, which is not that coedge's trim, so every such
2782/// case falls through to the sampled lane and keeps the refusals that lane
2783/// makes today.
2784fn affine_pcurve_on_range(
2785 surface: &NurbsSurface,
2786 curve: &NurbsCurve,
2787 edge_start: f64,
2788 edge_end: f64,
2789 forward: bool,
2790 tolerance: f64,
2791) -> Result<Option<PcurveFit>, String> {
2792 // Opt-out for A/B bisection of this lane against the sampled one, in the
2793 // shape of the two hatches beside it (`BREP_NO_PCURVE_REPAIR`,
2794 // `BREP_SECTION_PCURVE_MARCH`). It is what showed that the boolean's
2795 // planar residuals do NOT come through here: the same binary with this set
2796 // and unset read identically to every digit on 21_band_rim.
2797 if std::env::var("BREP_AFFINE_RANGE_PCURVE").as_deref() == Ok("0") {
2798 return Ok(None);
2799 }
2800 let Some(inversion) = AffineInversion::of(surface)? else {
2801 return Ok(None);
2802 };
2803 if !(edge_start.is_finite() && edge_end.is_finite() && edge_start < edge_end) {
2804 // The sampled lane owns this refusal; it is not this lane's to reword.
2805 return Ok(None);
2806 }
2807 let [t0, t1] = curve.domain()?;
2808 let start = edge_start.max(t0);
2809 let end = edge_end.min(t1);
2810 // A range narrower than the knot identity tolerance cannot be split onto
2811 // its own endpoints at all.
2812 if end - start <= 2.0 * KNOT_IDENTITY_TOL {
2813 return Ok(None);
2814 }
2815 let mut restricted = curve.clone();
2816 if end < t1 - KNOT_IDENTITY_TOL {
2817 restricted = restricted.split(end)?.0;
2818 }
2819 if start > t0 + KNOT_IDENTITY_TOL {
2820 restricted = restricted.split(start)?.1;
2821 }
2822 let mut pcurve = inversion.image_of(&restricted)?;
2823 if !forward {
2824 pcurve = pcurve.reversed()?;
2825 }
2826 let first = pcurve.knots[0];
2827 let last = pcurve.knots[pcurve.knots.len() - 1];
2828 if !(last - first > 0.0) {
2829 return Ok(None);
2830 }
2831 let knots = pcurve
2832 .knots
2833 .iter()
2834 .map(|knot| (knot - first) / (last - first))
2835 .collect::<Vec<_>>();
2836 let pcurve = NurbsCurve::new(pcurve.degree, knots, pcurve.control_points.clone())?;
2837
2838 // Verify against the range the CALLER asked for, not the one the
2839 // restriction produced: `split` snaps onto a nearby knot, so a snap that
2840 // moved the correspondence reads here as a deviation. Sampling at
2841 // fractions that are not the stations any lane interpolates through keeps
2842 // this a check on the whole trim rather than on its endpoints.
2843 let scale = 1.0 + curve.evaluate((start + end) / 2.0)?.length();
2844 let bar = tolerance.max(1e-11 * scale);
2845 let mut residual = 0.0_f64;
2846 for index in 0..=32 {
2847 let fraction = index as f64 / 32.0;
2848 let uv = pcurve.evaluate(fraction)?;
2849 // `evaluate` CLAMPS out-of-domain uv. A planar carrier's domain is a
2850 // bounding rectangle that a booleaned trim can leave, and an affine
2851 // patch's extension is the same plane, so read it unclamped.
2852 let on_carrier = surface.evaluate_extended(uv.x, uv.y)?;
2853 let edge_fraction = if forward { fraction } else { 1.0 - fraction };
2854 let on_edge = curve.evaluate(start + (end - start) * edge_fraction)?;
2855 residual = residual.max(on_carrier.sub(on_edge).length());
2856 if residual > bar {
2857 return Ok(None);
2858 }
2859 }
2860 let samples = pcurve.control_points.len();
2861 Ok(Some(PcurveFit {
2862 curve: pcurve,
2863 report: PcurveFitReport {
2864 residual,
2865 // The floor this lane VERIFIED against: the caller's ask, or the
2866 // floating-point floor of an exact map on a part this size. An
2867 // exact image is on its bar by construction; a caller asking for
2868 // less than the map's own precision is not refused for it.
2869 floor: bar,
2870 samples,
2871 exit: PcurveFitExit::Converged,
2872 off_surface: 0.0,
2873 raised: false,
2874 clamped_excursion: 0.0,
2875 clamped_distance: 0.0,
2876 },
2877 }))
2878}
2879
2880fn align_collapsed_endpoints(surface: &NurbsSurface, raw: &mut [[f64; 2]]) -> Result<(), String> {
2881 let anchor = surface.control_points[0][0].point()?;
2882 let mut extent = 0.0_f64;
2883 for control in surface.control_points.iter().flatten() {
2884 extent = extent.max(control.point()?.sub(anchor).length());
2885 }
2886 // This is a geometric identity check, independent of the fitting allowance
2887 // and of world position. Positive rational weights keep the whole iso-curve
2888 // inside the convex hull of its Euclidean controls. Cap the size coupling
2889 // at the linear tolerance so a large patch cannot erase a thin finite row.
2890 let pole_band = (1e-10 * (1.0 + extent)).min(LINEAR_TOLERANCE);
2891 for (endpoint, neighbor) in [(0, 1), (raw.len() - 1, raw.len() - 2)] {
2892 let point = surface.evaluate(raw[endpoint][0], raw[endpoint][1])?;
2893 for axis in 0..2 {
2894 let mut candidate = raw[endpoint];
2895 candidate[axis] = raw[neighbor][axis];
2896 if candidate == raw[endpoint]
2897 || surface
2898 .evaluate(candidate[0], candidate[1])?
2899 .sub(point)
2900 .length()
2901 > pole_band
2902 {
2903 continue;
2904 }
2905 let row = if axis == 0 {
2906 surface.iso_curve_v(raw[endpoint][1])?
2907 } else {
2908 surface.iso_curve_u(raw[endpoint][0])?
2909 };
2910 let mut collapsed = true;
2911 for control in &row.control_points {
2912 if control.point()?.sub(point).length() > pole_band {
2913 collapsed = false;
2914 break;
2915 }
2916 }
2917 if collapsed {
2918 raw[endpoint] = candidate;
2919 }
2920 }
2921 }
2922 Ok(())
2923}
2924
2925/// Re-seat any raw inversion sample that BRANCH-JUMPED — snapped to a distant
2926/// fold of a self-overlapping general carrier — back onto the branch traced by
2927/// its neighbours.
2928///
2929/// Each entry in `raw` is an INDEPENDENT global closest-point inversion of the
2930/// corresponding 3D edge sample. Every one is geometrically valid (on the
2931/// surface, at the edge), but they need not be CONTIGUOUS: where a rational
2932/// B-spline surface folds back over the small trimmed patch it carries, the
2933/// momentarily-closest fold flips from one sample to the next, so the raw
2934/// polygon zig-zags across the whole domain and self-crosses — and the region
2935/// its interpolant bounds no longer covers the true face (its surface flux can
2936/// exceed its own area). We anchor on the LONGEST run of mutually-continuous
2937/// samples — the fold the trimmed patch actually lies on, since a small face
2938/// lives on ONE fold and the jumped samples are the minority the global search
2939/// snapped elsewhere — then walk outward in both directions, re-seeding Newton
2940/// from the last good parameters and adopting the continuous footpoint whenever
2941/// it is geometrically as valid as the global one. Anchoring on consensus (not
2942/// blindly on sample 0, which can itself be an outlier that would drag the
2943/// whole edge onto the wrong branch and tear the loop open at its endpoint)
2944/// leaves continuous nonsingular fits unchanged. Certified collapsed endpoint
2945/// rows first adopt their neighbour's branch; a bad seed in the subsequent
2946/// jump repair cannot make a sample worse than the global search already had it.
2947fn repair_branch_jumps(
2948 surface: &NurbsSurface,
2949 edge_points: &[Vec3],
2950 raw: &mut [[f64; 2]],
2951 tolerance: f64,
2952) -> Result<(), String> {
2953 if raw.len() < 3 {
2954 return Ok(());
2955 }
2956 // Affine carriers invert linearly and exactly — no folds, nothing to chase.
2957 // Checked first so the common planar/affine face never touches the env.
2958 if surface.is_affine()? {
2959 return Ok(());
2960 }
2961 // Opt-out for A/B bisection of a STEP-import regression against this repair.
2962 if std::env::var("BREP_NO_PCURVE_REPAIR").is_ok() {
2963 return Ok(());
2964 }
2965 // A pole has no unique coordinate along its collapsed row. Continuing the
2966 // adjacent sample's branch is safe only when the ENTIRE row is collapsed;
2967 // coincident points across an ordinary seam or fold are not enough.
2968 align_collapsed_endpoints(surface, raw)?;
2969 let ([u0, u1], [v0, v1]) = surface_domains(surface)?;
2970 let u_span = (u1 - u0).abs().max(EPSILON);
2971 let v_span = (v1 - v0).abs().max(EPSILON);
2972 let (closed_u, closed_v) = surface_closedness(surface)?;
2973 // Normalised, seam-aware step between two parameter samples. Wrapping the
2974 // closed directions keeps a legitimate seam crossing SMALL, so the seam /
2975 // periodic-unwrap machinery elsewhere is never disturbed by this repair.
2976 let norm_step = |a: [f64; 2], b: [f64; 2]| -> f64 {
2977 let mut du = a[0] - b[0];
2978 if closed_u {
2979 while du > 0.5 * u_span {
2980 du -= u_span;
2981 }
2982 while du < -0.5 * u_span {
2983 du += u_span;
2984 }
2985 }
2986 let mut dv = a[1] - b[1];
2987 if closed_v {
2988 while dv > 0.5 * v_span {
2989 dv -= v_span;
2990 }
2991 while dv < -0.5 * v_span {
2992 dv += v_span;
2993 }
2994 }
2995 ((du / u_span).powi(2) + (dv / v_span).powi(2)).sqrt()
2996 };
2997 // A jump crosses a large fraction of the WHOLE domain — orders above the
2998 // per-sample motion of any real (even domain-spanning) edge, which advances
2999 // ~1/N of its traversal between consecutive stations.
3000 const JUMP_THRESHOLD: f64 = 0.2;
3001 let count = raw.len();
3002
3003 // Locate the longest maximal run of consecutive continuous samples — the
3004 // branch to anchor on. If nothing jumps, this spans [0, count-1] and both
3005 // re-seat passes below are empty, leaving `raw` untouched.
3006 let (mut best_start, mut best_len, mut run_start) = (0usize, 1usize, 0usize);
3007 for index in 1..count {
3008 if norm_step(raw[index], raw[index - 1]) > JUMP_THRESHOLD {
3009 if index - run_start > best_len {
3010 best_len = index - run_start;
3011 best_start = run_start;
3012 }
3013 run_start = index;
3014 }
3015 }
3016 if count - run_start > best_len {
3017 best_len = count - run_start;
3018 best_start = run_start;
3019 }
3020 let (spine_lo, spine_hi) = (best_start, best_start + best_len - 1);
3021
3022 // Adopt the continuous footpoint only when it (a) still sits on the surface
3023 // — as tight as the fit band or the global answer — and (b) genuinely
3024 // closes the jump rather than trading it for another. Returns the parameter
3025 // to carry forward as the next seed (the repaired one, or the untouched
3026 // global when no repair applies).
3027 let reseat = |edge_point: Vec3,
3028 previous: [f64; 2],
3029 global: [f64; 2]|
3030 -> Result<[f64; 2], String> {
3031 let global_step = norm_step(global, previous);
3032 if global_step <= JUMP_THRESHOLD {
3033 return Ok(global);
3034 }
3035 let seeded =
3036 project_point_to_surface_seeded(surface, edge_point, previous[0], previous[1])?;
3037 let candidate = [seeded.u, seeded.v];
3038 let global_residual = surface
3039 .evaluate(global[0], global[1])?
3040 .sub(edge_point)
3041 .length();
3042 let on_surface = seeded.distance <= 4.0 * global_residual + tolerance.max(1e-12);
3043 if on_surface && norm_step(candidate, previous) < 0.5 * global_step {
3044 if std::env::var("BREP_DEBUG_PCURVE").is_ok() {
3045 eprintln!(
3046 "pcurve repair: jump {global_step:.4} ({global:?}) -> {:.4} ({candidate:?}) res {:.2e}->{:.2e}",
3047 norm_step(candidate, previous),
3048 global_residual,
3049 seeded.distance
3050 );
3051 }
3052 Ok(candidate)
3053 } else {
3054 Ok(global)
3055 }
3056 };
3057
3058 // Walk forward off the spine's high end, then backward off its low end,
3059 // chaining each repaired sample as the next seed so continuity propagates.
3060 //
3061 // Apart from the certified collapsed rows above, the two ENDPOINT samples
3062 // (fraction 0 and 1) are NEVER moved: they are the
3063 // edge's shared loop vertices. The global inversion is deterministic, so the
3064 // two coedges meeting at a vertex land it at the SAME parameters even when
3065 // that vertex sits on a fold reachable from two branches — moving one side
3066 // to a different branch tears the loop open there (`synthesize_pole_edge`
3067 // then rejects the non-collapsed gap). A vertex's genuine fold transition
3068 // (an edge whose interior rides u≈0.08 but whose endpoint must meet its
3069 // neighbour at u≈0.95) is exactly this case and must be preserved, not
3070 // "continuity-repaired" back onto the interior branch.
3071 let mut previous = raw[spine_hi];
3072 for index in (spine_hi + 1)..count.saturating_sub(1) {
3073 raw[index] = reseat(edge_points[index], previous, raw[index])?;
3074 previous = raw[index];
3075 }
3076 let mut previous = raw[spine_lo];
3077 for index in (1..spine_lo).rev() {
3078 raw[index] = reseat(edge_points[index], previous, raw[index])?;
3079 previous = raw[index];
3080 }
3081 Ok(())
3082}
3083
3084/// Range fitter with explicit sampling knobs, reporting what the fit achieved.
3085/// [`fit_pcurve_on_surface_range`] keeps the long-standing (base 64, 3
3086/// refinement rounds, 513 cap) budget; a fit that binds at that cap with the
3087/// bar unmet grows once to `PCURVE_RAISED_SAMPLES`
3088/// ([`fit_pcurve_on_surface_stations`]).
3089///
3090/// NOTE (2026-09-03): this comment used to say "STEP import retries failed fits
3091/// with a denser budget". No caller passes these knobs any more — that retry
3092/// was removed and the sentence outlived it. The knobs are kept because they
3093/// are the honest way to ASK whether a fit had headroom left, which is what
3094/// `examples/pcurve_residual_probe.rs` uses them for: on every ABC corpus
3095/// coedge that failed `validate`'s pcurve check, budgets up to (512, 8, 8193)
3096/// with a 1000x tighter target reproduced the coarse fit's deviation to six
3097/// decimals, because that deviation is the edge-vs-surface closest-point
3098/// residual and a curve ON the surface cannot beat it.
3099#[allow(clippy::too_many_arguments)]
3100pub fn fit_pcurve_on_surface_range_dense(
3101 surface: &NurbsSurface,
3102 curve: &NurbsCurve,
3103 edge_start: f64,
3104 edge_end: f64,
3105 forward: bool,
3106 tolerance: f64,
3107 base_samples: usize,
3108 refinement_rounds: usize,
3109 parameter_cap: usize,
3110) -> Result<PcurveFit, String> {
3111 if !(edge_start.is_finite() && edge_end.is_finite() && edge_start < edge_end) {
3112 return Err("build_pcurve_on_surface_range: invalid edge interval".into());
3113 }
3114 let evaluate_edge = |fraction: f64| {
3115 let edge_fraction = if forward { fraction } else { 1.0 - fraction };
3116 curve.evaluate(edge_start + (edge_end - edge_start) * edge_fraction)
3117 };
3118 let breaks = range_breaks(curve, edge_start, edge_end, forward);
3119 let ask = tolerance.max(PCURVE_REFINEMENT_TOLERANCE);
3120 let fit = fit_station_pcurve(
3121 surface,
3122 &evaluate_edge,
3123 &breaks,
3124 &breaks,
3125 ask,
3126 base_samples,
3127 refinement_rounds,
3128 parameter_cap,
3129 SPAN_FLOOR,
3130 )?;
3131 end_span_challenger(surface, curve, &evaluate_edge, edge_start, edge_end, forward, &breaks, ask, base_samples, refinement_rounds, parameter_cap, fit)
3132}
3133
3134/// The edge curve's OWN distinct interior knots inside `[edge_start,
3135/// edge_end]`, mapped through the trim's range and sense to coedge
3136/// fractions, that fall inside `pcurve`'s FIRST or LAST span (between the
3137/// domain end and its nearest interior knot), sorted.
3138fn native_end_knot_fractions(curve: &NurbsCurve, edge_start: f64, edge_end: f64, forward: bool, pcurve: &NurbsCurve) -> Result<Vec<f64>, String> {
3139 let [q0, q1] = pcurve.domain()?;
3140 let mut interior: Vec<f64> = pcurve.knots.iter().copied().filter(|k| *k > q0 && *k < q1).collect();
3141 interior.dedup();
3142 let (Some(&first), Some(&last)) = (interior.first(), interior.last()) else { return Ok(Vec::new()) };
3143 let (low, high) = (edge_start.min(edge_end), edge_start.max(edge_end));
3144 let span = edge_end - edge_start;
3145 let mut fractions: Vec<f64> = curve
3146 .knots
3147 .iter()
3148 .copied()
3149 .filter(|k| *k > low && *k < high)
3150 .map(|k| {
3151 let along = (k - edge_start) / span;
3152 if forward { along } else { 1.0 - along }
3153 })
3154 .filter(|f| f.is_finite() && ((*f > q0 && *f < first) || (*f > last && *f < q1)))
3155 .collect();
3156 fractions.sort_by(f64::total_cmp);
3157 fractions.dedup();
3158 Ok(fractions)
3159}
3160
3161/// The admission rule for a foot read against an edge point: finite distance,
3162/// finite point, finite (u, v) inside the native domain. Shared by
3163/// [`common_station_read`] and the station fitter's tracking mode.
3164fn foot_admitted(foot: &crate::SurfaceProjection, [u0, u1]: [f64; 2], [v0, v1]: [f64; 2]) -> bool {
3165 let finite = |p: Vec3| p.x.is_finite() && p.y.is_finite() && p.z.is_finite();
3166 foot.distance.is_finite() && finite(foot.point) && foot.u.is_finite() && foot.v.is_finite() && (u0..=u1).contains(&foot.u) && (v0..=v1).contains(&foot.v)
3167}
3168
3169/// The tracking mode's verification probes: the current pcurve's native probes
3170/// ([`native_probe_fractions`]: endpoints, quarters and 16 interior points of
3171/// each knot span), its EXACT knots (the final common read includes them, and
3172/// the native probes do not), and the caller's fixed fractions; finite, in
3173/// [0, 1], sorted, deduplicated.
3174fn verification_fractions(pcurve: &NurbsCurve, verify: &[f64]) -> Result<Vec<f64>, String> {
3175 let mut fractions = native_probe_fractions(&[pcurve])?;
3176 fractions.extend(pcurve.knots.iter().copied());
3177 fractions.extend(verify.iter().copied());
3178 fractions.retain(|f| f.is_finite() && (0.0..=1.0).contains(f));
3179 fractions.sort_by(f64::total_cmp);
3180 fractions.dedup();
3181 Ok(fractions)
3182}
3183
3184/// The off-carrier candidate admission, at the same probes and shared feet
3185/// (`[tracking, deviation, excess]` per curve, [`common_station_read_with_excess`]):
3186/// every reading finite, the candidate's tracking strictly lower, and EITHER
3187/// its maximum deviation no worse than the legacy's (the original rule) OR the
3188/// candidate meeting the ORIGINAL floors outright, tracking and excess both
3189/// within `PCURVE_REFINEMENT_TOLERANCE` (1e-7).
3190///
3191/// Why the second branch: for fits whose images follow the shared admitted
3192/// feet, the maximum deviation can be dominated by the edge's standoff.
3193/// Their measured maxima can then differ by rounding while tracking improves.
3194/// This does not assert that the admitted foot is a certified global nearest
3195/// point or that an arbitrary pcurve has the same maximum deviation.
3196/// next5 fixture 25 (BREP_DEBUG_STATION_FIT): every tracking candidate reached
3197/// the floor (9.52e-8 .. 9.94e-8 tracking) and was declined on a deviation
3198/// 1.2e-14 .. 6.7e-13 above the legacy's at 2.1e-5 .. 9.0e-4 standoffs, while
3199/// the legacy kept 5.9e-7 .. 8.5e-5 tracking. A candidate within the original
3200/// floors on both readings is what those controls require; it is not a
3201/// standoff allowance and no bar moves.
3202pub(crate) fn floor_admits(old: [f64; 3], new: [f64; 3]) -> bool {
3203 let floor = PCURVE_REFINEMENT_TOLERANCE;
3204 old.iter().chain(new.iter()).all(|v| v.is_finite())
3205 && new[0] < old[0]
3206 && (new[1] <= old[1] || (new[0] <= floor && new[2] <= floor))
3207}
3208
3209/// The common reader's foot: the NEAREST admitted ([`foot_admitted`]) of the
3210/// global projection, the projection seeded at the reference (legacy) uv and
3211/// the one seeded at the candidate (image) uv, in THAT order, so that between
3212/// admitted feet at equal distance the first in this order is taken
3213/// (`min_by` keeps the first minimum). [`common_station_read`] and the station
3214/// fitter's tracking mode both take their foot here, so an equal-distance tie
3215/// resolves to the same foot in both.
3216fn common_foot(
3217 global: Option<crate::SurfaceProjection>,
3218 reference: Option<crate::SurfaceProjection>,
3219 candidate: Option<crate::SurfaceProjection>,
3220 domain_u: [f64; 2],
3221 domain_v: [f64; 2],
3222) -> Option<crate::SurfaceProjection> {
3223 [global, reference, candidate]
3224 .into_iter()
3225 .flatten()
3226 .filter(|f| foot_admitted(f, domain_u, domain_v))
3227 .min_by(|a, b| a.distance.total_cmp(&b.distance))
3228}
3229
3230/// The probes a RANGE trim is read at by the common reader: `curves`'
3231/// native spans (endpoints, quarters and 16 interior points of each knot
3232/// span) and exact knots, plus the edge curve's own knots CLIPPED to
3233/// `[t0, t1]` (with the range ends) and the same 19 points on every clipped
3234/// span, mapped through the coedge's sense to fractions; finite, in [0, 1],
3235/// sorted, deduplicated.
3236pub(crate) fn range_use_probes(curves: &[&NurbsCurve], edge_curve: &NurbsCurve, t0: f64, t1: f64, forward: bool) -> Result<Vec<f64>, String> {
3237 let mut probes = native_probe_fractions(curves)?;
3238 for curve in curves {
3239 probes.extend(curve.knots.iter().copied());
3240 }
3241 let span = t1 - t0;
3242 if span.is_finite() && span != 0.0 {
3243 let (low, high) = (t0.min(t1), t0.max(t1));
3244 let mut knots: Vec<f64> = edge_curve.knots.iter().copied().filter(|k| *k >= low && *k <= high).collect();
3245 knots.push(low);
3246 knots.push(high);
3247 knots.sort_by(f64::total_cmp);
3248 knots.dedup();
3249 let locals: Vec<f64> = [0.25, 0.5, 0.75]
3250 .into_iter()
3251 .chain((1..=TRACKING_ACCEPTANCE_POINTS).map(|index| index as f64 / (TRACKING_ACCEPTANCE_POINTS + 1) as f64))
3252 .collect();
3253 let mut ts = knots.clone();
3254 for pair in knots.windows(2).filter(|w| w[1] > w[0]) {
3255 for &local in &locals {
3256 ts.push(pair[0] + (pair[1] - pair[0]) * local);
3257 }
3258 }
3259 for t in ts {
3260 let along = (t - t0) / span;
3261 probes.push(if forward { along } else { 1.0 - along });
3262 }
3263 }
3264 probes.retain(|f| f.is_finite() && (0.0..=1.0).contains(f));
3265 probes.sort_by(f64::total_cmp);
3266 probes.dedup();
3267 Ok(probes)
3268}
3269
3270/// [`common_station_read`] for two RANGE trims of one coedge (`old`, `new`,
3271/// both on the coedge fraction domain [0, 1]) against the edge curve over
3272/// `[t0, t1]` in the coedge's sense, at [`range_use_probes`] of both: the same
3273/// probes and one shared admitted foot per probe, finite or an error. A trim
3274/// not on [0, 1] is an error.
3275// Unused by the import lane since the tracking trigger and keep were withdrawn
3276// for this merge; kept, with its controls, for their re-proposal.
3277#[cfg_attr(not(test), allow(dead_code))]
3278pub(crate) fn range_common_read(
3279 surface: &NurbsSurface,
3280 edge_curve: &NurbsCurve,
3281 t0: f64,
3282 t1: f64,
3283 forward: bool,
3284 old: &NurbsCurve,
3285 new: &NurbsCurve,
3286) -> Result<((f64, f64), (f64, f64)), String> {
3287 let (o, n) = range_common_read_with_excess(surface, edge_curve, t0, t1, forward, old, new)?;
3288 Ok(((o[0], o[1]), (n[0], n[1])))
3289}
3290
3291/// [`range_common_read`] with each trim's maximum pointwise excess over the
3292/// shared admitted foot's own distance: `[tracking, deviation, excess]` per
3293/// trim ([`common_station_read_with_excess`]).
3294// Unused by the import lane since the tracking trigger and keep were withdrawn
3295// for this merge; kept, with its controls, for their re-proposal.
3296#[cfg_attr(not(test), allow(dead_code))]
3297pub(crate) fn range_common_read_with_excess(
3298 surface: &NurbsSurface,
3299 edge_curve: &NurbsCurve,
3300 t0: f64,
3301 t1: f64,
3302 forward: bool,
3303 old: &NurbsCurve,
3304 new: &NurbsCurve,
3305) -> Result<([f64; 3], [f64; 3]), String> {
3306 for curve in [old, new] {
3307 if !matches!(curve.domain(), Ok([a, b]) if a == 0.0 && b == 1.0) {
3308 return Err("range common read: a trim not on [0, 1]".into());
3309 }
3310 }
3311 let probes = range_use_probes(&[old, new], edge_curve, t0, t1, forward)?;
3312 let evaluate = |fraction: f64| -> Result<Vec3, String> {
3313 let along = if forward { fraction } else { 1.0 - fraction };
3314 edge_curve.evaluate(t0 + (t1 - t0) * along)
3315 };
3316 common_station_read_with_excess(surface, &evaluate, old, new, &probes)
3317}
3318
3319/// The legacy and candidate pcurves read against the edge at the SAME probes
3320/// and against the SAME foot per probe: at each fraction, the edge point's
3321/// foot is the NEAREST admitted one among the global projection and the
3322/// projections seeded at the legacy and the candidate image's (u, v). A foot
3323/// is admitted with a finite distance and point and a finite (u, v) inside the
3324/// native domain. Both images are then measured against that one shared foot
3325/// (TRACKING) and against the edge point (same-parameter DEVIATION). The
3326/// shared foot is the nearest of these three, not a certified global nearest.
3327/// Any non-finite image, point or distance, or a probe with no admitted foot,
3328/// is an error (the caller keeps the legacy fit). Returns
3329/// ((legacy tracking, legacy deviation), (candidate tracking, candidate
3330/// deviation)), maxima over the probes.
3331pub(crate) fn common_station_read(
3332 surface: &NurbsSurface,
3333 evaluate_edge: &dyn Fn(f64) -> Result<Vec3, String>,
3334 legacy: &NurbsCurve,
3335 candidate: &NurbsCurve,
3336 probes: &[f64],
3337) -> Result<((f64, f64), (f64, f64)), String> {
3338 let (old, new) = common_station_read_with_excess(surface, evaluate_edge, legacy, candidate, probes)?;
3339 Ok(((old[0], old[1]), (new[0], new[1])))
3340}
3341
3342/// [`common_station_read`] that also returns, per curve, the maximum
3343/// pointwise EXCESS: the same-parameter deviation minus the shared admitted
3344/// foot's own distance at that probe (the part over the edge's standoff
3345/// there, which the every-use controls bar at the original 1e-7). Returns
3346/// `[tracking, deviation, excess]` for the legacy and the candidate.
3347pub(crate) fn common_station_read_with_excess(
3348 surface: &NurbsSurface,
3349 evaluate_edge: &dyn Fn(f64) -> Result<Vec3, String>,
3350 legacy: &NurbsCurve,
3351 candidate: &NurbsCurve,
3352 probes: &[f64],
3353) -> Result<([f64; 3], [f64; 3]), String> {
3354 let (domain_u, domain_v) = surface_domains(surface)?;
3355 let finite = |p: Vec3| p.x.is_finite() && p.y.is_finite() && p.z.is_finite();
3356 // [tracking, deviation, excess] per curve; the excess (deviation minus
3357 // the shared foot's distance, per probe) starts at -inf, so a read with no
3358 // probe is not finite and admits nothing.
3359 let mut read = ([0.0_f64, 0.0, f64::NEG_INFINITY], [0.0_f64, 0.0, f64::NEG_INFINITY]);
3360 // Reading ONE curve against itself (`legacy` and `candidate` the same
3361 // object, as a tracking read and a trigger's read are): the candidate's
3362 // parameter, image and seeded projection are the reference's on identical
3363 // inputs, so each is computed once (same results, same tie order).
3364 let same = std::ptr::eq(legacy, candidate);
3365 // On an affine-sphere carrier with no analytic kind, the global projector
3366 // and the seeded one both return the ellipsoid's closed form whenever it
3367 // answers (`projection::project_point_to_surface` and
3368 // `project_point_to_surface_seeded`), whatever the seed: one closed form
3369 // stands for all three feet (2026-11-03: two thirds of the
3370 // elephant's tracking projections).
3371 let ellipsoid = if same && surface.analytic().is_none() { surface.affine_sphere() } else { None };
3372 for &fraction in probes {
3373 let point = evaluate_edge(fraction)?;
3374 let old_uv = legacy.evaluate(fraction)?;
3375 let new_uv = if same { old_uv } else { candidate.evaluate(fraction)? };
3376 let old_image = surface.evaluate_extended(old_uv.x, old_uv.y)?;
3377 let new_image = if same { old_image } else { surface.evaluate_extended(new_uv.x, new_uv.y)? };
3378 if !(finite(point) && finite(old_image) && finite(new_image)) {
3379 return Err(format!("common station read: non-finite point at fraction {fraction}"));
3380 }
3381 let closed_form = ellipsoid.and_then(|ellipsoid| ellipsoid.project(surface, point));
3382 let foot = if let Some(foot) = closed_form {
3383 common_foot(Some(foot), Some(foot), Some(foot), domain_u, domain_v)
3384 } else {
3385 let reference_foot = memo_seeded_foot(surface, point, old_uv.x, old_uv.y);
3386 let candidate_foot = if same {
3387 reference_foot
3388 } else {
3389 memo_seeded_foot(surface, point, new_uv.x, new_uv.y)
3390 };
3391 common_foot(
3392 memo_global_foot(surface, point),
3393 reference_foot,
3394 candidate_foot,
3395 domain_u,
3396 domain_v,
3397 )
3398 }
3399 .ok_or_else(|| format!("common station read: no admitted foot at fraction {fraction}"))?;
3400 let values = [
3401 old_image.sub(foot.point).length(),
3402 old_image.sub(point).length(),
3403 new_image.sub(foot.point).length(),
3404 new_image.sub(point).length(),
3405 ];
3406 if values.iter().any(|v| !v.is_finite()) {
3407 return Err(format!("common station read: non-finite distance at fraction {fraction}"));
3408 }
3409 if !foot.distance.is_finite() {
3410 return Err(format!("common station read: non-finite foot distance at fraction {fraction}"));
3411 }
3412 read.0[0] = read.0[0].max(values[0]);
3413 read.0[1] = read.0[1].max(values[1]);
3414 read.0[2] = read.0[2].max(values[1] - foot.distance);
3415 read.1[0] = read.1[0].max(values[2]);
3416 read.1[1] = read.1[1].max(values[3]);
3417 read.1[2] = read.1[2].max(values[3] - foot.distance);
3418 }
3419 Ok(read)
3420}
3421
3422/// The interior probe fractions of the edge curve's own FIRST and LAST native
3423/// knot spans inside `[edge_start, edge_end]` (quarter points and 16 interior
3424/// points of each), mapped through the trim's range and sense to coedge
3425/// fractions in [0, 1].
3426fn edge_end_span_fractions(curve: &NurbsCurve, edge_start: f64, edge_end: f64, forward: bool) -> Vec<f64> {
3427 let (low, high) = (edge_start.min(edge_end), edge_start.max(edge_end));
3428 let mut knots: Vec<f64> = curve.knots.iter().copied().filter(|k| *k > low && *k < high).collect();
3429 knots.dedup();
3430 let spans = match (knots.first(), knots.last()) {
3431 (Some(&first), Some(&last)) => vec![(low, first), (last, high)],
3432 _ => vec![(low, high)],
3433 };
3434 let span = edge_end - edge_start;
3435 let mut out = Vec::new();
3436 for (a, b) in spans {
3437 if !(b > a) {
3438 continue;
3439 }
3440 for local in [0.25, 0.5, 0.75].into_iter().chain((1..=TRACKING_ACCEPTANCE_POINTS).map(|index| index as f64 / (TRACKING_ACCEPTANCE_POINTS + 1) as f64)) {
3441 let along = (a + (b - a) * local - edge_start) / span;
3442 let fraction = if forward { along } else { 1.0 - along };
3443 if fraction.is_finite() && (0.0..=1.0).contains(&fraction) {
3444 out.push(fraction);
3445 }
3446 }
3447 }
3448 out.sort_by(f64::total_cmp);
3449 out.dedup();
3450 out
3451}
3452
3453/// At most this many of the legacy pcurve's worst spans are seeded.
3454const WORST_SPANS: usize = 4;
3455/// Interior stations seeded in each of them.
3456const WORST_SPAN_SEEDS: usize = 7;
3457
3458/// The seed fractions of [`end_span_challenger`]'s worst-span source: the
3459/// legacy pcurve's own spans (between its distinct knots) whose same-parameter
3460/// deviation, read at the quarter points and 16 interior points, and at any of
3461/// `extra_probes` (the edge's own end-span interior points mapped to coedge
3462/// fractions) that fall inside the span, exceeds
3463/// `bar`, worst first, at most [`WORST_SPANS`] of them, each seeded with
3464/// [`WORST_SPAN_SEEDS`] evenly spaced interior fractions. Fails closed on any
3465/// non-finite reading.
3466fn legacy_worst_span_seeds(
3467 evaluate_edge: &dyn Fn(f64) -> Result<Vec3, String>,
3468 surface: &NurbsSurface,
3469 pcurve: &NurbsCurve,
3470 bar: f64,
3471 extra_probes: &[f64],
3472) -> Result<Vec<f64>, String> {
3473 let [q0, q1] = pcurve.domain()?;
3474 let mut knots: Vec<f64> = pcurve.knots.iter().copied().filter(|k| *k >= q0 && *k <= q1).collect();
3475 knots.dedup();
3476 let locals: Vec<f64> = [0.25, 0.5, 0.75]
3477 .into_iter()
3478 .chain((1..=TRACKING_ACCEPTANCE_POINTS).map(|index| index as f64 / (TRACKING_ACCEPTANCE_POINTS + 1) as f64))
3479 .collect();
3480 let mut over: Vec<(f64, f64, f64)> = Vec::new();
3481 for span in knots.windows(2).filter(|w| w[1] > w[0]) {
3482 let mut worst = 0.0_f64;
3483 let inside = extra_probes.iter().copied().filter(|f| *f > span[0] && *f < span[1]);
3484 for fraction in locals.iter().map(|local| span[0] + (span[1] - span[0]) * local).chain(inside) {
3485 let uv = pcurve.evaluate(fraction)?;
3486 let deviation = surface.evaluate_extended(uv.x, uv.y)?.sub(evaluate_edge(fraction)?).length();
3487 if !deviation.is_finite() {
3488 return Err(format!("worst-span read: non-finite deviation at fraction {fraction}"));
3489 }
3490 worst = worst.max(deviation);
3491 }
3492 if worst > bar {
3493 over.push((worst, span[0], span[1]));
3494 }
3495 }
3496 over.sort_by(|a, b| b.0.total_cmp(&a.0).then(a.1.total_cmp(&b.1)));
3497 over.truncate(WORST_SPANS);
3498 Ok(over
3499 .into_iter()
3500 .flat_map(|(_, lo, hi)| (1..=WORST_SPAN_SEEDS).map(move |index| lo + (hi - lo) * index as f64 / (WORST_SPAN_SEEDS + 1) as f64))
3501 .collect())
3502}
3503
3504/// Why the range lane's [`end_span_challenger`] runs.
3505#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3506enum ChallengerTrigger {
3507 /// The legacy residual misses `ask + standoff` (the original trigger).
3508 Deviation,
3509 /// The legacy residual is within `ask + standoff`, the edge stands off its
3510 /// carrier by more than the ask, and the legacy fit's COMMON tracking
3511 /// (`common_station_read` at the fixed probes) misses the ask.
3512 Tracking,
3513}
3514
3515/// The challenger's trigger. The original rule reads the same-parameter
3516/// DEVIATION against `ask + standoff`; over a standoff `g` a tangential slip
3517/// `d` adds only about d²/(2g) to it, so a slip up to sqrt(ask² + 2·ask·g)
3518/// never triggers (about 1.3e-5 at g = 9e-4, the fixture-25 inherited edges).
3519/// When the deviation rule does not fire and the edge stands off its carrier
3520/// by more than the ask (the same gate `refine_pcurve` takes its
3521/// standoff-tracking lane on), the legacy fit's common tracking is read
3522/// (`tracking`, called only then): over the ask, the challenger runs on
3523/// tracking. An unread tracking (None or non-finite) never triggers.
3524fn challenger_trigger(residual: f64, off_surface: f64, ask: f64, tracking: impl FnOnce() -> Option<f64>) -> Option<ChallengerTrigger> {
3525 if residual > ask + off_surface {
3526 return Some(ChallengerTrigger::Deviation);
3527 }
3528 if !(off_surface > ask) {
3529 return None;
3530 }
3531 match tracking() {
3532 Some(value) if value.is_finite() && value > ask => Some(ChallengerTrigger::Tracking),
3533 _ => None,
3534 }
3535}
3536
3537/// The END-SPAN CHALLENGER of the range lane (2026-10-05, the four original
3538/// fitter rows of `abc_00000011/12` edges 7 and 243). On e90c30c94 their worst
3539/// excess (4.58e-6 and 5.14e-6) sat in the installed pcurve's LAST span, at the
3540/// edge's start, where the edge curve's own first knot span is only 4.5e-6
3541/// (e7) and 1.2e-4 (e243) wide while the fitter's end span was 1.4e-4 wide at
3542/// its cap. It runs when [`challenger_trigger`] fires: the legacy fit misses
3543/// its bar (residual over the ask plus its standoff), or, on an edge standing
3544/// off its carrier by more than the ask, its common tracking misses the ask.
3545/// It refits with the edge's own interior knots that
3546/// fall inside the legacy pcurve's first or last span as additional stations
3547/// (not joins; corners stay the legacy ones), through the same fitter with the
3548/// unchanged ask, SPAN_FLOOR, cap and rounds. The candidate replaces the legacy
3549/// fit only when both, read at COMMON probes (both pcurves' own knot spans,
3550/// knots and endpoints, plus the edge's own knots and knot spans mapped to
3551/// fractions) against one SHARED foot per probe ([`common_station_read`]),
3552/// read finite, the candidate's tracking is strictly lower and its
3553/// same-parameter deviation no worse. Otherwise, or on any error or unreadable read, the legacy fit
3554/// stands exactly.
3555#[allow(clippy::too_many_arguments)]
3556fn end_span_challenger(
3557 surface: &NurbsSurface,
3558 curve: &NurbsCurve,
3559 evaluate_edge: &dyn Fn(f64) -> Result<Vec3, String>,
3560 edge_start: f64,
3561 edge_end: f64,
3562 forward: bool,
3563 breaks: &[f64],
3564 ask: f64,
3565 base_samples: usize,
3566 refinement_rounds: usize,
3567 parameter_cap: usize,
3568 legacy: PcurveFit,
3569) -> Result<PcurveFit, String> {
3570 // On the bar by deviation: nothing is read (the original zero-work
3571 // path). The TRACKING trigger (538dd700c: an off-carrier edge on its
3572 // deviation bar challenged on its common tracking) is withdrawn from this
3573 // merge (Claude master's ruling, 2026-10-05): bisected as the first commit
3574 // that moved step_import's a_trim_off_its_bar_is_accepted_and_named, and
3575 // the four original fitter rows do not need it (they enter on the
3576 // deviation trigger).
3577 if !(legacy.report.residual > ask + legacy.report.off_surface) {
3578 return Ok(legacy);
3579 }
3580 // The trigger read, the candidate's tracking verification and the final
3581 // read project the same edge points again: memoized, same answers.
3582 let _feet = FootMemoScope::open(surface);
3583 let Ok(end_knots) = native_end_knot_fractions(curve, edge_start, edge_end, forward, &legacy.curve) else { return Ok(legacy) };
3584 // The legacy fit's own WORST native spans (2026-10-05, root's boundary
3585 // audit of the four rows): the e90 witnesses read a 4.58e-6 / 5.14e-6
3586 // pointwise excess at q 0.99996 inside the closed native domain, in the
3587 // legacy pcurve's last span, where no edge knot falls. Each span between the
3588 // legacy pcurve's own distinct knots is read at its 19 probes; the spans
3589 // whose same-parameter deviation exceeds the ask plus the legacy standoff,
3590 // worst first and at most WORST_SPANS of them, each get WORST_SPAN_SEEDS
3591 // evenly spaced interior stations. Unreadable reads decline.
3592 // The edge's OWN first and last native knot spans, read at their quarter
3593 // points and 16 interior points, mapped through the trim's range and sense
3594 // to coedge fractions (e7's first edge span [0, 4.496e-6] of a
3595 // 0.018661-long trim maps reversed to q in [0.99976, 1], overlapping the
3596 // legacy pcurve's last span [0.99986, 1]).
3597 let edge_end_probes = edge_end_span_fractions(curve, edge_start, edge_end, forward);
3598 // The FIXED part of the common probe set, known before the candidate is
3599 // fitted: the legacy pcurve's own spans, endpoints and knots, and the
3600 // edge's own end-span probes, knot-span quarters and knots (every probe
3601 // that could have TRIGGERED the challenger among them). The candidate fit
3602 // verifies against these as well as its own spans, so it stops on the
3603 // reader that judges it.
3604 let Ok(mut fixed) = native_probe_fractions(&[&legacy.curve]) else { return Ok(legacy) };
3605 fixed.extend(edge_end_probes.iter().copied());
3606 let (low, high) = (edge_start.min(edge_end), edge_start.max(edge_end));
3607 let mut edge_knots: Vec<f64> = curve.knots.iter().copied().filter(|k| *k >= low && *k <= high).collect();
3608 edge_knots.push(low);
3609 edge_knots.push(high);
3610 edge_knots.sort_by(f64::total_cmp);
3611 edge_knots.dedup();
3612 let to_fraction = |t: f64| {
3613 let along = (t - edge_start) / (edge_end - edge_start);
3614 if forward { along } else { 1.0 - along }
3615 };
3616 for span in edge_knots.windows(2).filter(|w| w[1] > w[0]) {
3617 for local in [0.25, 0.5, 0.75] {
3618 fixed.push(to_fraction(span[0] + (span[1] - span[0]) * local));
3619 }
3620 }
3621 fixed.extend(edge_knots.iter().map(|&t| to_fraction(t)));
3622 fixed.extend(end_knots.iter().copied());
3623 fixed.extend(legacy.curve.knots.iter().copied());
3624 fixed.retain(|f| f.is_finite() && (0.0..=1.0).contains(f));
3625 fixed.sort_by(f64::total_cmp);
3626 fixed.dedup();
3627 let trigger = challenger_trigger(legacy.report.residual, legacy.report.off_surface, ask, || {
3628 common_station_read(surface, evaluate_edge, &legacy.curve, &legacy.curve, &fixed).ok().map(|((tracking, _), _)| tracking)
3629 });
3630 let Some(trigger) = trigger else { return Ok(legacy) };
3631 let Ok(worst_seeds) = legacy_worst_span_seeds(evaluate_edge, surface, &legacy.curve, ask + legacy.report.off_surface, &edge_end_probes) else { return Ok(legacy) };
3632 // The original guard holds for the deviation trigger only: a tracking
3633 // trigger needs no added station (the tracking mode and its verification
3634 // read the fixed probes).
3635 if trigger == ChallengerTrigger::Deviation && end_knots.is_empty() && worst_seeds.is_empty() {
3636 return Ok(legacy);
3637 }
3638 let mut stations: Vec<f64> = breaks.iter().copied().chain(end_knots.iter().copied()).chain(worst_seeds.iter().copied()).collect();
3639 stations.sort_by(f64::total_cmp);
3640 stations.dedup();
3641 // The fitter's cap is max(requested cap, initial station count): the added
3642 // stations must not raise it. Decline when the merged initial count
3643 // (base stations plus every break and end knot) could exceed the
3644 // requested cap.
3645 let initial = base_samples.max(1).checked_add(1).and_then(|count| count.checked_add(stations.len()));
3646 if initial.is_none_or(|count| count > parameter_cap) {
3647 return Ok(legacy);
3648 }
3649 let Ok(candidate) = fit_station_pcurve_mode(surface, evaluate_edge, &stations, breaks, ask, base_samples, refinement_rounds, parameter_cap, SPAN_FLOOR, true, Some(&legacy.curve), &fixed) else {
3650 return Ok(legacy);
3651 };
3652 if !(candidate.report.residual.is_finite() && candidate.report.off_surface.is_finite() && candidate.report.clamped_excursion.is_finite() && candidate.report.clamped_distance.is_finite()) {
3653 return Ok(legacy);
3654 }
3655 // Common probes: the fixed set plus the candidate's own knot spans,
3656 // endpoints and knots (the same set as both curves' native probes plus
3657 // the edge's, as before).
3658 let Ok(mut probes) = native_probe_fractions(&[&candidate.curve]) else { return Ok(legacy) };
3659 probes.extend(fixed.iter().copied());
3660 probes.extend(candidate.curve.knots.iter().copied());
3661 probes.retain(|f| f.is_finite() && (0.0..=1.0).contains(f));
3662 probes.sort_by(f64::total_cmp);
3663 probes.dedup();
3664 let readings = common_station_read_with_excess(surface, evaluate_edge, &legacy.curve, &candidate.curve, &probes);
3665 let accepted = matches!(&readings, Ok((old, new)) if floor_admits(*old, *new));
3666 if station_fit_tracing() {
3667 eprintln!(
3668 "STATION-FIT end-span challenger: trigger={trigger:?} end_knots={} worst_span_seeds={} edge_end_probes={} legacy={:?}/{:e} candidate={:?}/{:e} common={readings:?} decision={}",
3669 end_knots.len(), worst_seeds.len(), edge_end_probes.len(), legacy.report.exit, legacy.report.residual, candidate.report.exit, candidate.report.residual,
3670 if accepted { "candidate" } else { "legacy" }
3671 );
3672 }
3673 Ok(if accepted { candidate } else { legacy })
3674}
3675
3676
3677/// [`fit_pcurve_on_surface_range_dense`] for the callers that only need the
3678/// curve, accepted or refused on its bar (`accept_fit`).
3679#[allow(clippy::too_many_arguments)]
3680pub fn build_pcurve_on_surface_range_dense(
3681 surface: &NurbsSurface,
3682 curve: &NurbsCurve,
3683 edge_start: f64,
3684 edge_end: f64,
3685 forward: bool,
3686 tolerance: f64,
3687 base_samples: usize,
3688 refinement_rounds: usize,
3689 parameter_cap: usize,
3690) -> Result<NurbsCurve, String> {
3691 accept_fit(fit_pcurve_on_surface_range_dense(
3692 surface,
3693 curve,
3694 edge_start,
3695 edge_end,
3696 forward,
3697 tolerance,
3698 base_samples,
3699 refinement_rounds,
3700 parameter_cap,
3701 )?)
3702}
3703
3704/// The fractions of `[edge_start, edge_end]`, walked in the coedge's sense, at
3705/// which `curve` is not C2: interior knots of multiplicity at least its
3706/// degree. A rational-quadratic arc past a quarter turn is two segments joined
3707/// at a double knot, and an interpolant through uniform samples across that
3708/// joint cannot represent the second-derivative jump: on the 2026-09-18 import
3709/// census 1511 of the 2979 arc trims blamed on the fitter had their worst
3710/// miss at fraction 0.49–0.51, the mid double knot, exactly as the fillet's
3711/// corner patch had measured (`blend/track_fit.rs::curve_breaks`, which this
3712/// mirrors for a range).
3713pub(crate) fn range_breaks(
3714 curve: &NurbsCurve,
3715 edge_start: f64,
3716 edge_end: f64,
3717 forward: bool,
3718) -> Vec<f64> {
3719 let mut fractions = Vec::new();
3720 let span = edge_end - edge_start;
3721 if !(span.abs() > 0.0) {
3722 return fractions;
3723 }
3724 let (low, high) = (edge_start.min(edge_end), edge_start.max(edge_end));
3725 let mut index = 0;
3726 while index < curve.knots.len() {
3727 let value = curve.knots[index];
3728 let run = curve.knots[index..]
3729 .iter()
3730 .take_while(|knot| **knot == value)
3731 .count();
3732 if value > low && value < high && run >= curve.degree.max(1) {
3733 let along = (value - edge_start) / span;
3734 fractions.push(if forward { along } else { 1.0 - along });
3735 }
3736 index += run;
3737 }
3738 fractions.sort_by(f64::total_cmp);
3739 fractions
3740}
3741
3742/// The body of [`fit_pcurve_on_surface_range_dense`], parameterized by the
3743/// 3D station the pcurve must pass through at each coedge fraction instead of
3744/// by an edge curve and a range.
3745///
3746/// The station supplier is the whole difference between deriving a pcurve and
3747/// reading one. `fit_pcurve_on_surface_range_dense` supplies points off the
3748/// edge's own 3D curve, so the answer is "the nearest point of the carrier",
3749/// which is ambiguous exactly where a trim is interesting — a seam branch, a
3750/// near-tangential approach, a pole. The STEP importer's supplied-pcurve lane
3751/// supplies points off the VENDOR's stated trim instead, and the ambiguity is
3752/// gone because the vendor resolved it.
3753///
3754/// `stations(fraction)` must be continuous in `fraction` over `[0, 1]` and land
3755/// on (or very near) `surface`; `breaks` are the fractions at which it is not
3756/// C2 (`range_breaks`), each of which becomes a station so no kink sits
3757/// between two nodes. Everything else — inversion, branch repair, refinement
3758/// and the measurement — is shared with the derived lane by construction.
3759///
3760/// The SURFACE's knot-line crossings are deliberately not stations here,
3761/// although `blend/track_fit.rs` makes them so. Measured 2026-09-26 over the
3762/// 57-document import corpus: with each crossing bisected on the fraction and
3763/// projected seeded from the left station, 34 bodies refused (19 with a foot
3764/// that misses its global distance rejected) against 15 without the pass —
3765/// every extra refusal a fit stalled around a crossing station, on
3766/// `AssemblyExample-Assembly` and `abc_00000014` among others. The raised
3767/// pass resolves the O(h²) tail across a crossing on its own.
3768///
3769/// # What the fit is measured against, and when it stops
3770///
3771/// The stations' own standoff from the carrier is read FIRST, from the base
3772/// inversions, and the refinement target is the ask plus that standoff
3773/// ([`PcurveFitReport::on_bar`]): a station standing `g` off the surface
3774/// cannot be imaged closer than `g`, so chasing it to the ask alone spends the
3775/// whole budget for nothing (the 2026-09-18 census: 2052 imported pcurves at
3776/// the 513 cap, 1958 of them on a curve that is not on its carrier).
3777///
3778/// Every probe and the final measurement read the carrier through
3779/// `evaluate_extended`: a pcurve riding a periodic seam legitimately carries
3780/// parameters past the domain, and the clamped `evaluate` this loop used until
3781/// 2026-09-26 read every such station as a gross miss — an insert per round
3782/// until the cap, on a trim that was right.
3783///
3784/// The RETURNED curve is measured out of sample — at the quarter points of
3785/// every span between its nodes, where an interpolant is exact at the nodes
3786/// and extremal between — unless its last round probed every span and found
3787/// nothing to insert, in which case that round's maximum already is that
3788/// measurement. A fit that binds at `parameter_cap` or spends its rounds with
3789/// the bar still unmet grows ONCE to `PCURVE_RAISED_SAMPLES` (deterministic).
3790/// The report says which exit it took and what it reached; the `build_*`
3791/// wrappers refuse over the bar, this function never does.
3792#[allow(clippy::too_many_arguments)]
3793pub fn fit_pcurve_on_surface_stations(
3794 surface: &NurbsSurface,
3795 stations: &dyn Fn(f64) -> Result<Vec3, String>,
3796 breaks: &[f64],
3797 tolerance: f64,
3798 base_samples: usize,
3799 refinement_rounds: usize,
3800 parameter_cap: usize,
3801) -> Result<PcurveFit, String> {
3802 fit_station_pcurve(
3803 surface, stations, breaks, &[], tolerance.max(PCURVE_REFINEMENT_TOLERANCE),
3804 base_samples, refinement_rounds, parameter_cap, SPAN_FLOOR,
3805 )
3806}
3807
3808/// Precision construction for stations on recognized analytic carriers. Unlike
3809/// general projection, their inverse maps reach floating point accuracy. This
3810/// is an explicit construction contract, not a change to general fit policy.
3811pub(crate) fn analytic_station_pcurve(
3812 surface: &NurbsSurface,
3813 stations: &dyn Fn(f64) -> Result<Vec3, String>,
3814 tolerance: f64,
3815) -> Result<NurbsCurve, String> {
3816 if surface.analytic().is_none() {
3817 return Err("analytic pcurve requires an analytic carrier".into());
3818 }
3819 let mut breaks = Vec::new();
3820 for _ in 0..4 {
3821 let fit = fit_station_pcurve(surface, stations, &breaks, &[], tolerance, 64, 64, 8192, 1e-10)?;
3822 if fit.report.residual > tolerance || fit.report.off_surface > tolerance {
3823 return Err(format!(
3824 "analytic pcurve construction missed its contract: {:?}",
3825 fit.report
3826 ));
3827 }
3828 let mut failed = Vec::new();
3829 // Independent Gauss stations in every returned knot span, rather than
3830 // re-reading the interpolation nodes or the fitter's quarter stations.
3831 for pair in fit.curve.knots.windows(2) {
3832 if pair[1] <= pair[0] {
3833 continue;
3834 }
3835 for local in [
3836 0.06943184420297371,
3837 0.33000947820757187,
3838 0.6699905217924281,
3839 0.9305681557970262,
3840 ] {
3841 let t = pair[0] + (pair[1] - pair[0]) * local;
3842 let uv = fit.curve.evaluate(t)?;
3843 if surface
3844 .evaluate_extended(uv.x, uv.y)?
3845 .sub(stations(t)?)
3846 .length()
3847 > tolerance
3848 {
3849 failed.push(t);
3850 }
3851 }
3852 }
3853 if failed.is_empty() {
3854 return Ok(fit.curve);
3855 }
3856 if breaks.len() + failed.len() > 8192 {
3857 break;
3858 }
3859 breaks.extend(failed);
3860 breaks.sort_by(f64::total_cmp);
3861 breaks.dedup();
3862 }
3863 Err("analytic pcurve failed independent knot-span witnesses".into())
3864}
3865
3866#[allow(clippy::too_many_arguments)]
3867fn fit_station_pcurve(
3868 surface: &NurbsSurface,
3869 stations: &dyn Fn(f64) -> Result<Vec3, String>,
3870 breaks: &[f64],
3871 corners: &[f64],
3872 tolerance: f64,
3873 base_samples: usize,
3874 refinement_rounds: usize,
3875 parameter_cap: usize,
3876 span_floor: f64,
3877) -> Result<PcurveFit, String> {
3878 fit_station_pcurve_mode(surface, stations, breaks, corners, tolerance, base_samples, refinement_rounds, parameter_cap, span_floor, false, None, &[])
3879}
3880
3881/// [`fit_station_pcurve`] with an optional TRACKING mode, taken only by the
3882/// range lane's [`end_span_challenger`]; every other caller keeps the legacy
3883/// mode byte for byte. The legacy fitter probes, ranks and stops on the
3884/// image's same-parameter DEVIATION from the edge point. On an edge standing
3885/// off its carrier that deviation never falls under the ask, so every probe is
3886/// a candidate, the cap goes to the probes with the largest STANDOFF, and a
3887/// tangential slip of `d` adds only about d²/(2g) to a deviation over a
3888/// standoff `g`: the four original rows' challenger met its standoff (4.670e-6)
3889/// and kept a common tracking of 1.434e-6 (root's e24 run, 2026-10-05). In
3890/// tracking mode each probe reads its TRACKING, the image against the edge
3891/// point's foot (the nearer admitted one of the global projection and the
3892/// projection seeded at the image's uv: finite, inside the native domain, as
3893/// [`common_station_read`] admits). A probe is a candidate only over the ask,
3894/// the cap goes to the worst TRACKING first, and the one raise is taken when
3895/// the last round's tracking still misses the ask. The reported residual
3896/// stays the same-parameter deviation (standoff included). A probe with no
3897/// admitted foot is an error, which the challenger turns into the exact legacy
3898/// fit.
3899#[allow(clippy::too_many_arguments)]
3900fn fit_station_pcurve_mode(
3901 surface: &NurbsSurface,
3902 stations: &dyn Fn(f64) -> Result<Vec3, String>,
3903 breaks: &[f64],
3904 corners: &[f64],
3905 tolerance: f64,
3906 base_samples: usize,
3907 refinement_rounds: usize,
3908 parameter_cap: usize,
3909 span_floor: f64,
3910 track_projection: bool,
3911 reference: Option<&NurbsCurve>,
3912 verify: &[f64],
3913) -> Result<PcurveFit, String> {
3914 // Read only in tracking mode: the legacy mode makes no extra call.
3915 let domains = if track_projection { Some(surface_domains(surface)?) } else { None };
3916 // The tracking reading at one fraction, by the SAME foot rule as
3917 // [`common_station_read`], the reader the challenger accepts on: the
3918 // nearest admitted foot among the global projection and the projections
3919 // seeded at this image's uv and at the reference (legacy) pcurve's uv at
3920 // the same fraction. Returns the tracking and that foot (uv, distance),
3921 // which is where a tracking insert is placed.
3922 let tracking_of = |represented: Vec3, edge_point: Vec3, uv: Vec3, fraction: f64| -> Result<(f64, [f64; 2], f64), String> {
3923 let (domain_u, domain_v) = domains.ok_or_else(|| "station tracking: legacy mode".to_string())?;
3924 let finite = |p: Vec3| p.x.is_finite() && p.y.is_finite() && p.z.is_finite();
3925 let reference_seed = match reference {
3926 Some(curve) => curve.evaluate(fraction).ok().and_then(|seed| memo_seeded_foot(surface, edge_point, seed.x, seed.y)),
3927 None => None,
3928 };
3929 // The common reader's order (global, reference, candidate), so an
3930 // equal-distance tie takes the same foot here as there.
3931 let foot = common_foot(
3932 memo_global_foot(surface, edge_point),
3933 reference_seed,
3934 memo_seeded_foot(surface, edge_point, uv.x, uv.y),
3935 domain_u,
3936 domain_v,
3937 )
3938 .ok_or_else(|| "station tracking: no admitted foot".to_string())?;
3939 let tracking = represented.sub(foot.point).length();
3940 if !(finite(represented) && tracking.is_finite()) {
3941 return Err("station tracking: non-finite reading".into());
3942 }
3943 Ok((tracking, [foot.u, foot.v], foot.distance))
3944 };
3945 // The last round's tracking missed the ask (tracking mode only).
3946 let mut tracking_unmet = false;
3947 let evaluate_edge = stations;
3948 let base_samples = base_samples.max(1);
3949 // The general caller floors its ask: its projector places a foot
3950 // to about `LINEAR_TOLERANCE` (1e-7), so a caller asking for 1e-9 asks for
3951 // more than any station can deliver. Chased to such an ask the fit spends
3952 // its cap and reads its own noise — the direct-edit heals asked 1e-9 on
3953 // stations 9.9e-8 off their carrier and were refused by 8.5e-9
3954 // (2026-09-26). The floor is the ask's floor; the report says what was
3955 // reached against it.
3956
3957 let mut parameters = (0..=base_samples)
3958 .map(|index| index as f64 / base_samples as f64)
3959 .collect::<Vec<_>>();
3960 // A break within the span floor of a station IS that station: two nodes
3961 // closer than that make the interpolant swing between them, and over a
3962 // stretch that short the kink moves the fit by nothing a bar can read.
3963 // `abc_00000011` face #549's curve carries a triple knot 4e-7 before its
3964 // end; as a station it put the cubic 1.594e-2 off between it and the last
3965 // one (measured 2026-09-26), where the file's curve sits 1e-7 off.
3966 // A break that is a CORNER of the curve (`corners`, a subset of
3967 // `breaks`: the range lane passes its curve's knots of multiplicity >=
3968 // degree as both) is a C0 JOIN of the interpolant
3969 // (`build_interpolant_joined`): one cubic through the corner overshoots
3970 // on both sides whatever is inserted. A break that is only a station
3971 // (an analytic witness, a caller's own marker) joins nothing.
3972 let mut joins = Vec::with_capacity(corners.len());
3973 for &fraction in breaks {
3974 if !(fraction > 0.0 && fraction < 1.0) {
3975 continue;
3976 }
3977 let corner = corners.contains(&fraction);
3978 let at = parameters.partition_point(|parameter| *parameter < fraction);
3979 let near_left = at > 0 && fraction - parameters[at - 1] <= span_floor;
3980 let near_right = at < parameters.len() && parameters[at] - fraction <= span_floor;
3981 if near_left || near_right {
3982 // The break IS that station (a corner at a base station, e.g.
3983 // the midpoint of a two-piece curve at 32/64): no insert, but the
3984 // station is still the join.
3985 if corner {
3986 joins.push(if near_right { parameters[at] } else { parameters[at - 1] });
3987 }
3988 continue;
3989 }
3990 parameters.insert(at, fraction);
3991 if corner {
3992 joins.push(fraction);
3993 }
3994 }
3995 // The 3D edge point behind each raw inversion sample, kept parallel so the
3996 // continuity repair can re-seed a jumped station from its own footpoint.
3997 let mut edge_points = parameters
3998 .iter()
3999 .map(|fraction| evaluate_edge(*fraction))
4000 .collect::<Result<Vec<_>, _>>()?;
4001 // The stations' standoff from the carrier: the part of every residual no
4002 // pcurve can remove, known before a single probe is spent.
4003 let mut off_surface = 0.0_f64;
4004 let mut raw = Vec::with_capacity(edge_points.len());
4005 let mut gaps = Vec::with_capacity(edge_points.len());
4006 for point in &edge_points {
4007 let (parameter, gap) = invert_checked(surface, *point)?;
4008 off_surface = off_surface.max(gap);
4009 raw.push(parameter);
4010 gaps.push(gap);
4011 }
4012 repair_branch_jumps(surface, &edge_points, &mut raw, tolerance)?;
4013 let (mut pcurve, mut excursion, mut distance) =
4014 build_interpolant_joined(surface, &raw, ¶meters, &joins)?;
4015
4016 let mut cap = parameter_cap.max(parameters.len());
4017 let mut rounds = refinement_rounds;
4018 let mut raised = false;
4019 loop {
4020 let mut exit = PcurveFitExit::RoundBudget;
4021 let mut converged_max = None;
4022 // This pass's last round probed every span (none narrow, none
4023 // truncated) and every probe tracked within the ask (tracking mode).
4024 let mut tracking_met = false;
4025 for _ in 0..rounds {
4026 if parameters.len() >= cap {
4027 exit = PcurveFitExit::SampleCeiling;
4028 break;
4029 }
4030 // The target is the ASK, bare: a station standing off the carrier
4031 // is chased to the foot it projects to, as the fitter has always
4032 // done. A target of ask + standoff (one landing, 2026-09-26) let
4033 // such a fit stop at its base stations with the interpolant
4034 // wandering between them, folded a molex wall's mesh and put a
4035 // trim of abc_00000023 2.2e-5 off its own parameter; the bar
4036 // below still reads what it reached.
4037 let target = tolerance;
4038 let mut candidates = Vec::new();
4039 let mut round_max = 0.0_f64;
4040 let mut truncated = false;
4041 let mut skipped_narrow = false;
4042 let worst_first = worst_first();
4043 'spans: for index in 0..parameters.len() - 1 {
4044 // The loop's own resolution floor: a span narrower than this
4045 // is not subdivided. Without it the
4046 // raised pass inserted into a vanishing span at a trim's end
4047 // until the cap — `abc_00000011` face #549's last stations sat
4048 // 4e-7 apart with their feet clamped at the patch edge, and the
4049 // cubic through them swung 1.594e-2 off between them (measured
4050 // 2026-09-26). A miss that survives only inside such a span is
4051 // reported as [`PcurveFitExit::SpanFloor`].
4052 if parameters[index + 1] - parameters[index] < span_floor {
4053 skipped_narrow = true;
4054 continue;
4055 }
4056 for local in [0.25, 0.5, 0.75] {
4057 if !worst_first && parameters.len() + candidates.len() >= cap {
4058 truncated = true;
4059 break 'spans;
4060 }
4061 let fraction =
4062 parameters[index] + (parameters[index + 1] - parameters[index]) * local;
4063 let uv = pcurve.evaluate(fraction)?;
4064 let represented = surface.evaluate_extended(uv.x, uv.y)?;
4065 let edge_point = evaluate_edge(fraction)?;
4066 let deviation = represented.sub(edge_point).length();
4067 // The probe's reading: its deviation (legacy), or its
4068 // tracking against the admitted foot (tracking mode).
4069 let (reading, foot) = if track_projection {
4070 let (tracking, foot_uv, foot_gap) = tracking_of(represented, edge_point, uv, fraction)?;
4071 (tracking, Some((foot_uv, foot_gap)))
4072 } else {
4073 (deviation, None)
4074 };
4075 round_max = round_max.max(reading);
4076 if reading <= target {
4077 continue;
4078 }
4079 candidates.push((index, fraction, edge_point, uv, reading, foot));
4080 }
4081 }
4082 // Tracking mode's VERIFICATION, before it may stop: a round whose
4083 // quarter probes all track within the ask is read again at the
4084 // probes the challenger's acceptance reads, the current pcurve's
4085 // own knot spans (endpoints, quarters and 16 interior points: the
4086 // interpolant's knots are averages of three stations, not the
4087 // stations) and the caller's fixed fractions (the legacy pcurve's
4088 // spans and the edge's own). A probe over the ask becomes a
4089 // candidate at its own fraction, placed at its own foot. One
4090 // within the span floor of a station cannot be inserted: it is
4091 // counted, and the fit may not report its tracking as met. On e24
4092 // the four rows stopped on their quarter probes and read 1.144e-7 /
4093 // 1.590e-7 at the common probes (a4c84871 runtime, 2026-10-05).
4094 let mut verify_blocked = false;
4095 if track_projection && candidates.is_empty() && !truncated && !skipped_narrow {
4096 for fraction in verification_fractions(&pcurve, verify)? {
4097 let index = parameters.partition_point(|parameter| *parameter <= fraction).saturating_sub(1).min(parameters.len() - 2);
4098 let uv = pcurve.evaluate(fraction)?;
4099 let represented = surface.evaluate_extended(uv.x, uv.y)?;
4100 let edge_point = evaluate_edge(fraction)?;
4101 let (tracking, foot_uv, foot_gap) = tracking_of(represented, edge_point, uv, fraction)?;
4102 round_max = round_max.max(tracking);
4103 if tracking <= target {
4104 continue;
4105 }
4106 if fraction - parameters[index] <= span_floor || parameters[index + 1] - fraction <= span_floor {
4107 verify_blocked = true;
4108 continue;
4109 }
4110 // Two over-ask probes closer than the span floor in one
4111 // span: keep the worse, so no near-duplicate stations.
4112 if let Some(last) = candidates.last_mut() {
4113 if last.0 == index && fraction - last.1 <= span_floor {
4114 if tracking > last.4 {
4115 *last = (index, fraction, edge_point, uv, tracking, Some((foot_uv, foot_gap)));
4116 }
4117 continue;
4118 }
4119 }
4120 candidates.push((index, fraction, edge_point, uv, tracking, Some((foot_uv, foot_gap))));
4121 }
4122 }
4123 tracking_unmet = track_projection && round_max > target;
4124 // A round with more candidates than the cap has room for spends
4125 // that room on its WORST probes, kept in curve order, rather than
4126 // on the first spans in order: truncating in span order left the
4127 // late spans of an off-carrier ring at their base spacing and its
4128 // image 1.523e-6 off its feet; worst first reaches 5.84e-8 on the
4129 // same 513 samples. An even stride along the curve reached 2.87e-7
4130 // there but diluted the inserts a local miss needs and lost fits
4131 // over the corpus (a 1.3e-5 trim of `abc_00000011` fell back to its
4132 // 4.4e-4 size-ask fit), measured 2026-09-27.
4133 let room = cap.saturating_sub(parameters.len());
4134 if candidates.len() > room {
4135 truncated = true;
4136 let mut order: Vec<usize> = (0..candidates.len()).collect();
4137 order.sort_by(|&a, &b| candidates[b].4.total_cmp(&candidates[a].4).then(a.cmp(&b)));
4138 order.truncate(room);
4139 order.sort_unstable();
4140 candidates = order.into_iter().map(|index| candidates[index]).collect();
4141 }
4142 let mut inserts = Vec::with_capacity(candidates.len());
4143 for (index, fraction, edge_point, uv, _, foot) in candidates {
4144 // A tracking candidate is placed at the very foot it was read
4145 // against, so the node's image IS that foot; the legacy
4146 // inversion is unchanged.
4147 let (parameter, gap) = match foot {
4148 Some(placed) => placed,
4149 None => invert_near(surface, edge_point, [uv.x, uv.y], gaps[index].max(gaps[index + 1]))?,
4150 };
4151 inserts.push((index + 1, fraction, edge_point, parameter, gap));
4152 }
4153 if station_fit_tracing() {
4154 eprintln!(
4155 "STATION-FIT round: samples={} target={target:.3e} round_max={round_max:.3e} inserts={} truncated={truncated}{}",
4156 parameters.len(),
4157 inserts.len(),
4158 // Tracking mode's round_max is a TRACKING reading.
4159 if track_projection { " mode=tracking" } else { "" }
4160 );
4161 }
4162 if inserts.is_empty() {
4163 exit = if truncated {
4164 PcurveFitExit::SampleCeiling
4165 } else if skipped_narrow {
4166 // Every probed span is within the target; the narrow ones
4167 // were not probed, and the sweep below measures them.
4168 PcurveFitExit::SpanFloor
4169 } else if track_projection {
4170 // Every probe TRACKS within the ask, but round_max is a
4171 // tracking reading, not the deviation the report carries.
4172 // The exit is Stalled (no probe can be improved; the
4173 // residual measured below is the edge's own standoff),
4174 // and it becomes Converged below only if that measured
4175 // deviation itself meets the floor. A verification probe
4176 // over the ask that could not be inserted leaves the
4177 // tracking UNMET.
4178 tracking_met = !verify_blocked;
4179 PcurveFitExit::Stalled
4180 } else {
4181 converged_max = Some(round_max);
4182 PcurveFitExit::Converged
4183 };
4184 break;
4185 }
4186 for (index, fraction, edge_point, parameter, gap) in inserts.into_iter().rev() {
4187 parameters.insert(index, fraction);
4188 edge_points.insert(index, edge_point);
4189 raw.insert(index, parameter);
4190 gaps.insert(index, gap);
4191 off_surface = off_surface.max(gap);
4192 }
4193 // Inserts are independent global inversions too — re-run the repair
4194 // so a fold-flip introduced mid-refinement cannot poison the next
4195 // round's deviation interpolant.
4196 repair_branch_jumps(surface, &edge_points, &mut raw, tolerance)?;
4197 let rebuilt = build_interpolant_joined(surface, &raw, ¶meters, &joins)?;
4198 pcurve = rebuilt.0;
4199 excursion = rebuilt.1;
4200 distance = rebuilt.2;
4201 if truncated {
4202 exit = PcurveFitExit::SampleCeiling;
4203 break;
4204 }
4205 }
4206 let residual = match converged_max {
4207 Some(measured) => measured,
4208 None => {
4209 station_residual(surface, evaluate_edge, &pcurve, ¶meters, tolerance, &mut off_surface)?
4210 }
4211 };
4212 if station_fit_tracing() {
4213 eprintln!(
4214 "STATION-FIT pass raised={raised} exit={exit:?} samples={} residual={residual:.3e} off_surface={off_surface:.3e}",
4215 parameters.len()
4216 );
4217 if residual > tolerance + off_surface && (exit == PcurveFitExit::Converged || raised) {
4218 // Where the miss sits, and the uv track around it: the trace a
4219 // refusal needs to be attributed to a fold, a seam or the file.
4220 let (mut worst, mut at) = (0.0f64, 0usize);
4221 for (index, span) in parameters.windows(2).enumerate() {
4222 let fraction = 0.5 * (span[0] + span[1]);
4223 let uv = pcurve.evaluate(fraction)?;
4224 let deviation = surface.evaluate_extended(uv.x, uv.y)?.sub(evaluate_edge(fraction)?).length();
4225 if deviation > worst {
4226 worst = deviation;
4227 at = index;
4228 }
4229 }
4230 let ([u0, u1], [v0, v1]) = surface_domains(surface)?;
4231 let (closed_u, closed_v) = surface_closedness(surface)?;
4232 eprintln!(
4233 "STATION-FIT OFF BAR: worst {worst:.3e} between stations {at} and {}; surface degree {}x{} net {}x{} domain u[{u0},{u1}] v[{v0},{v1}] closed ({closed_u},{closed_v}) analytic {:?}",
4234 at + 1,
4235 surface.degree_u,
4236 surface.degree_v,
4237 surface.control_points.len(),
4238 surface.control_points[0].len(),
4239 surface.analytic().map(|_| "yes").unwrap_or("no")
4240 );
4241 {
4242 let fraction = 0.5 * (parameters[at] + parameters[at + 1]);
4243 let uv = pcurve.evaluate(fraction)?;
4244 let image = surface.evaluate_extended(uv.x, uv.y)?;
4245 let station = evaluate_edge(fraction)?;
4246 let foot = project_point_to_surface(surface, station)?;
4247 let seeded = project_point_to_surface_seeded(surface, station, raw[at][0], raw[at][1])?;
4248 eprintln!(
4249 " at the worst midpoint: deviation {:.4e}, station gap {:.4e} (seeded {:.4e}), image-to-foot {:.4e} (to seeded foot {:.4e}), foot uv ({:.7}, {:.7}) seeded ({:.7}, {:.7}) image uv ({:.7}, {:.7})",
4250 image.sub(station).length(), foot.distance, seeded.distance, image.sub(foot.point).length(), image.sub(seeded.point).length(), foot.u, foot.v, seeded.u, seeded.v, uv.x, uv.y
4251 );
4252 }
4253 let lo = at.saturating_sub(3);
4254 let hi = (at + 4).min(parameters.len() - 1);
4255 for index in lo..=hi {
4256 eprintln!(
4257 " station {index}: fraction {:.6} uv ({:.6}, {:.6}) point ({:.4}, {:.4}, {:.4})",
4258 parameters[index], raw[index][0], raw[index][1], edge_points[index].x, edge_points[index].y, edge_points[index].z
4259 );
4260 }
4261 }
4262 }
4263 // Legacy only: a SpanFloor exit whose measured DEVIATION is on the
4264 // bar is promoted. In tracking mode the deviation sits at the standoff
4265 // whatever the unprobed narrow spans' TRACKING is, so SpanFloor stays
4266 // SpanFloor (unread); a tracking-mode fit is Converged only when every
4267 // span was probed within the ask AND the measured deviation meets the
4268 // floor (the exit's documented meaning, never the standoff bar).
4269 if !track_projection && exit == PcurveFitExit::SpanFloor && residual <= tolerance + off_surface {
4270 exit = PcurveFitExit::Converged;
4271 }
4272 if tracking_met && residual <= tolerance {
4273 exit = PcurveFitExit::Converged;
4274 }
4275 if exit != PcurveFitExit::Converged
4276 && !raised
4277 && (residual > tolerance + off_surface || tracking_unmet)
4278 && cap < PCURVE_RAISED_SAMPLES
4279 {
4280 raised = true;
4281 cap = PCURVE_RAISED_SAMPLES;
4282 // The raised pass is bounded by SAMPLES, not rounds: a kink
4283 // converges O(h²), a round per halving, and the three rounds of
4284 // the first pass are what left the 2026-09-18 census's fits over
4285 // their ask with budget to spare (RoundBudget at 158–400 of 2000
4286 // samples, measured 2026-09-26). Forty rounds cover every halving
4287 // the cap admits; a fit that is not converging stalls out first.
4288 rounds = RAISED_ROUNDS;
4289 continue;
4290 }
4291 return Ok(PcurveFit {
4292 curve: pcurve,
4293 report: PcurveFitReport {
4294 residual,
4295 floor: tolerance,
4296 samples: parameters.len(),
4297 exit,
4298 off_surface,
4299 raised,
4300 clamped_excursion: excursion,
4301 clamped_distance: distance,
4302 },
4303 });
4304 }
4305}
4306
4307/// [`fit_pcurve_on_surface_stations`] for the callers that only need the
4308/// curve, accepted or refused on its bar (`accept_fit`).
4309pub fn build_pcurve_on_surface_stations(
4310 surface: &NurbsSurface,
4311 stations: &dyn Fn(f64) -> Result<Vec3, String>,
4312 breaks: &[f64],
4313 tolerance: f64,
4314 base_samples: usize,
4315 refinement_rounds: usize,
4316 parameter_cap: usize,
4317) -> Result<NurbsCurve, String> {
4318 accept_fit(fit_pcurve_on_surface_stations(
4319 surface,
4320 stations,
4321 breaks,
4322 tolerance,
4323 base_samples,
4324 refinement_rounds,
4325 parameter_cap,
4326 )?)
4327}
4328
4329/// The largest 3D distance between `pcurve`'s image and its stations at the
4330/// quarter points of every span of `parameters` — out of sample by
4331/// construction, since the interpolant is exact at its nodes. Unclamped
4332/// evaluations; a station read over the running bar is inverted so that
4333/// `off_surface` covers the standoff AT the stations the residual is taken at,
4334/// not only at the nodes (a curve lifted off its plane by a bump stands
4335/// further off between two nodes than at either).
4336fn station_residual(
4337 surface: &NurbsSurface,
4338 stations: &dyn Fn(f64) -> Result<Vec3, String>,
4339 pcurve: &NurbsCurve,
4340 parameters: &[f64],
4341 tolerance: f64,
4342 off_surface: &mut f64,
4343) -> Result<f64, String> {
4344 let mut worst = 0.0_f64;
4345 let mut read = |fraction: f64| -> Result<(), String> {
4346 let uv = pcurve.evaluate(fraction)?;
4347 let represented = surface.evaluate_extended(uv.x, uv.y)?;
4348 let station = stations(fraction)?;
4349 let deviation = represented.sub(station).length();
4350 worst = worst.max(deviation);
4351 if deviation > tolerance + *off_surface {
4352 *off_surface = off_surface.max(invert_checked(surface, station)?.1);
4353 }
4354 Ok(())
4355 };
4356 for span in parameters.windows(2) {
4357 for local in [0.25, 0.5, 0.75] {
4358 read(span[0] + (span[1] - span[0]) * local)?;
4359 }
4360 }
4361 Ok(worst)
4362}
4363
4364