From 8bb09f25d05a76bf639112ac9af5f5ecc4b20613 Mon Sep 17 00:00:00 2001 From: Kirill Frolov Date: Sat, 24 Oct 2026 04:07:25 +0300 Subject: [PATCH 1/8] claim: #521 Co-Authored-By: Codex From 9143a955f9b83410dd266a3caf6f0a62d6b3d829 Mon Sep 17 00:00:00 2001 From: Kirill Frolov Date: Sat, 24 Oct 2026 05:22:27 +0300 Subject: [PATCH 2/8] fix: propagate topology and healing evaluation failures (#521) Require successful curve reversal, tangent/angle evaluation, UV-corner/gap queries and surface derivatives before consuming their outputs. Preserve ClassifyLoops errors and retry incomplete healing stages. Seven regressions failed before their fixes. Record the unchanged C++ reference's 43 lost-angle statuses and four failed-stage marker differences explicitly; preserve all prior known records and comparison tolerances. Co-Authored-By: Codex --- crates/difftest/src/units/topology_vertex.rs | 6 + crates/solidcore/src/heal/face_props.rs | 119 +- crates/solidcore/src/topology/edge.rs | 48 +- crates/solidcore/src/topology/edgeuse/p1.rs | 113 +- crates/solidcore/src/topology/loopuse.rs | 97 +- fixtures/topology.vertex/constructed.json | 1209 +++++++++++++++++- plan/units/heal.face_props.toml | 4 +- plan/units/topology.edge.toml | 2 +- plan/units/topology.edgeuse_p1.toml | 2 +- plan/units/topology.loopuse.toml | 2 +- 10 files changed, 1491 insertions(+), 111 deletions(-) diff --git a/crates/difftest/src/units/topology_vertex.rs b/crates/difftest/src/units/topology_vertex.rs index c309753507..4d939f626b 100644 --- a/crates/difftest/src/units/topology_vertex.rs +++ b/crates/difftest/src/units/topology_vertex.rs @@ -910,6 +910,9 @@ fn id(o: Option, f: impl Fn(I) -> u32) -> i64 { } fn rust_err(e: &SolidError) -> Result { + if matches!(e, SolidError::InvalidInput { operation: "geom::vector3d::Vec3::angle_between", .. }) { + return Ok(Output::err("status_1007")); + } match Output::unported(e) { Some(o) => Ok(o), None => Err(harness(format!("rust: {e}"))), @@ -1475,6 +1478,9 @@ impl From for Fail { fn or_null(r: Result) -> Result, SolidError> { match r { Ok(v) => Ok(Some(v)), + // A failed angle is a geometry error, not an upstream null dereference + // (#521). Keep its status when the tangent predicates propagate it. + Err(e @ SolidError::InvalidInput { operation: "geom::vector3d::Vec3::angle_between", .. }) => Err(e), Err(SolidError::InvalidInput { .. }) => Ok(None), Err(e) => Err(e), } diff --git a/crates/solidcore/src/heal/face_props.rs b/crates/solidcore/src/heal/face_props.rs index 507d4dbe94..447da69c05 100644 --- a/crates/solidcore/src/heal/face_props.rs +++ b/crates/solidcore/src/heal/face_props.rs @@ -67,13 +67,15 @@ //! ([`crate::topology::face::p4::BrepSeamObjects`]; otherwise //! [`SolidError::InvalidInput`]). The `SmLoop` queries (`topology.loopuse`), //! `SmCurveClassification::ReSet` (`intersect.curve_class_p2`) and -//! `SmFace::ClassifyLoops` ([`crate::query::face::p4::classify_loops`], whose status -//! `SetProps_Stage3` ignores as upstream) are wired. +//! `SmFace::ClassifyLoops` ([`crate::query::face::p4::classify_loops`]) are wired. +//! `SetProps_Stage3` propagates classification failures (#521) before reading the +//! classification records. An error clears its in-progress stage marker, keeping +//! partial diagnostics without certifying the properties or skipping a retry. //! - **Query caches.** The mutable healer explicitly applies the vertex/face and //! edge/face gap caches produced by seam queries, even on errors. Read-only props //! queries still leave the B-rep unchanged; no geometry or topology is copied back. -use crate::error::{ignored, EntityRef, Result, SolidError}; +use crate::error::{EntityRef, Result, SolidError}; use crate::geom::tol::{calc_face_zone_tol_3d, xsect_tol_3d_from_zones}; use crate::geom::vector3d::{Point3, Vec3}; use crate::intersect::curve_class::p1::{ @@ -2081,6 +2083,7 @@ impl FaceProps { /// `SetProps_Stage3`: sets the loop values (after `Fix_SplitEdgesAtSeam`): the /// per-loop props, the closed loop count, and from `SmFace::ClassifyLoops` the /// containment, orientation and the loop problem counts. + /// A failed stage remains incomplete and can be retried. /// /// # Errors /// @@ -2088,7 +2091,7 @@ impl FaceProps { /// `SM_ERR_NOTYET_HEAL_FACE` ([`SM_ERR_NOTYET_HEAL_FACE`]) for a face /// `ClassifyLoops` cannot classify; the errors of [`FaceProps::set_props_stage2`] /// and [`LoopProps::set_props`]; [`SolidError::InvalidInput`] where upstream reads - /// outside an array; [`SolidError::Unported`] from `SmFace::ClassifyLoops`. + /// outside an array; the errors of `SmFace::ClassifyLoops`. pub fn set_props_stage3( &mut self, k: &Kernel, @@ -2112,6 +2115,23 @@ impl FaceProps { face_index: u64, exe_heal_op: HealerOpType, cache: &mut CurveOnClassifyCache, + ) -> Result<()> { + let result = self.set_props_stage3_inner(k, b, face, face_index, exe_heal_op, cache); + if result.is_err() && self.prop_stage == PropStage::Stage3A { + // Keep diagnostics, but an unfinished stage is not a cached success. + self.prop_stage = PropStage::Stage2; + } + result + } + + fn set_props_stage3_inner( + &mut self, + k: &Kernel, + b: &Brep, + face: FaceId, + face_index: u64, + exe_heal_op: HealerOpType, + cache: &mut CurveOnClassifyCache, ) -> Result<()> { // no work - already done or being done if self.prop_stage >= PropStage::Stage3A { @@ -2159,9 +2179,7 @@ impl FaceProps { ..ClassifyLoopsOut::default() }; let mut target_loops = closed_loops; - // (the status is only reported upstream; a handle type that cannot name the - // loop bisector's objects is a limitation of the caller, not a status) - let classify_status = match crate::query::face::p4::classify_loops_cached( + let classify_status = crate::query::face::p4::classify_loops_cached( k, b, m_face, @@ -2170,14 +2188,7 @@ impl FaceProps { &mut scratch, self, cache, - ) { - Err(e @ SolidError::InvalidInput { reason, .. }) - if reason == crate::query::face::p3::NO_CURVE_HANDLES => - { - return Err(e); - } - r => ignored(r)?, - }; + ); let ClassifyLoopsOut { classifiable, classify_loop_ios, @@ -2187,6 +2198,10 @@ impl FaceProps { looptree, } = classified; self.looptree = looptree; + self.not_yet_face_type = classifiable; + // Preserve the owned tree and problem classification on error, but do not + // consume output arrays that the failed classification did not fill. + classify_status?; // watch out for problem faces that defy ClassifyLoops if classifiable != HealNotYetFaceType::NoProbs { @@ -2216,7 +2231,6 @@ impl FaceProps { // guard against NULL m_pLooptreeRoot - can happen when ClassifyLoops cannot // process loops (a failed status is checked upstream by SE_MSG only) let Some(root) = self.looptree.as_ref().and_then(Looptree::root) else { - let _ = classify_status; self.not_yet_face_type = HealNotYetFaceType::NoLoopTreeRoot; return Err(not_yet_heal_face(OP_STAGE3)); }; @@ -2706,6 +2720,79 @@ mod tests { assert!(p.has_props(PropStage::Stage0, f)); } + #[test] + fn stage3_propagates_classification_failure() { + let k = Kernel::default(); + let (b, f) = plane_without_loops(); + let mut p: FaceProps = FaceProps::for_face(f, 0); + p.set_props_stage2(&k, &b, f, 0, HealerOpType::All).unwrap(); + let expected = crate::query::face::p4::classify_loops( + &k, + &b, + f, + &mut Vec::new(), + &mut ClassifyLoopsOut::default(), + &mut CurveClassification::new(&k), + &mut p, + ) + .unwrap_err(); + assert_eq!( + p.set_props_stage3(&k, &b, f, 0, HealerOpType::All), + Err(expected) + ); + assert!(!p.has_props_stage3(Some(f))); + } + + #[test] + fn stage3_failure_does_not_certify_cached_properties() { + let k = Kernel::default(); + let (mut b, f) = plane_without_loops(); + let mut p: FaceProps = FaceProps::for_face(f, 0); + p.set_props_stage2(&k, &b, f, 0, HealerOpType::All).unwrap(); + b.faceuse_mut(crate::topology::FaceuseId::from_index(0)) + .unwrap() + .loopuses + .push(crate::topology::LoopuseId::from_index(99)); + let first = p + .set_props_stage3(&k, &b, f, 0, HealerOpType::All) + .unwrap_err(); + assert_eq!( + p.set_props_stage3(&k, &b, f, 0, HealerOpType::All), + Err(first) + ); + assert_eq!(p.prop_stage, PropStage::Stage2); + } + + fn plane_without_loops() -> (Brep, FaceId) { + use crate::topology::{Face, Faceuse, FaceuseId, Shell}; + let mut b = Brep::new(); + let s = b.add_shell_record(Shell::default()).unwrap(); + let f = b + .add_face_record(Face { + surface: Some(crate::surface::surface::Surface::from( + crate::surface::plane::Plane::from_normal(Vec3::ZERO, Vec3::Z), + )), + faceuse: Some(FaceuseId::from_index(0)), + ..Face::new(s) + }) + .unwrap(); + b.shell_mut(s).unwrap().faces.push(f); + for (i, orientation) in [OrientType::Same, OrientType::Opposite] + .into_iter() + .enumerate() + { + b.add_faceuse_record(Faceuse { + shell: Some(s), + face: Some(f), + mate: Some(FaceuseId::from_index(1 - i as u32)), + orientation, + ..Faceuse::default() + }) + .unwrap(); + } + (b, f) + } + #[test] fn early_stage_statuses() { let k = Kernel::default(); diff --git a/crates/solidcore/src/topology/edge.rs b/crates/solidcore/src/topology/edge.rs index e08fd00be5..a8e9bea6c4 100644 --- a/crates/solidcore/src/topology/edge.rs +++ b/crates/solidcore/src/topology/edge.rs @@ -2116,14 +2116,14 @@ pub fn set_orientation(b: &mut Brep, e: EdgeId, orientation: OrientType) -> Resu Ok(()) } -/// `ReverseOrientation`: reverses the curve's parameterisation (the edge interval -/// becomes the reversed one; its status is ignored, a failure leaves the default -/// interval), flips every edge use's orientation and deletes their UV trim curves. +/// `ReverseOrientation`: reverses the curve's parameterisation, then installs the +/// reversed interval, flips every edge use's orientation and deletes its UV trim. +/// A failed curve reversal returns before changing the interval or edge uses. /// /// # Errors /// /// [`SolidError::InvalidInput`] without edge uses (upstream walks a null list) and -/// those of [`eu1::set_uv_trim_curve`]; [`SolidError::Unported`] from the curve. +/// those of the curve reversal and [`eu1::set_uv_trim_curve`]. pub fn reverse_orientation(k: &Kernel, b: &mut Brep, e: EdgeId) -> Result<()> { // locals let old_ivl = b.edge(e)?.interval; @@ -2131,10 +2131,7 @@ pub fn reverse_orientation(k: &Kernel, b: &mut Brep, e: EdgeId) -> Result<()> { // reverse Curve Parameterization if let Some(c) = b.edge_mut(e)?.curve.as_mut() { - match c.reverse_parameterization(k, &old_ivl) { - Ok(n) => new_ivl = n, - Err(err) => failed(err)?, - } + new_ivl = c.reverse_parameterization(k, &old_ivl)?; } b.edge_mut(e)?.interval = new_ivl; @@ -3173,6 +3170,41 @@ mod tests { } } + #[test] + fn failed_reversal_preserves_edge_interval_uses_and_trims() { + let k = Kernel::default(); + let (mut b, e, eus, _) = wire(); + let hermite = crate::curve::hermite_curve::HermiteCurve::new( + Vec3::ZERO, + Vec3::X, + Vec3::X, + Vec3::Y, + 3, + ); + let mut curve = C::from(hermite); + let old_ivl = b.edge(e).unwrap().interval; + let expected = curve.reverse_parameterization(&k, &old_ivl).unwrap_err(); + b.edge_mut(e).unwrap().curve = Some(curve); + for eu in eus { + b.edgeuse_mut(eu).unwrap().uv_trim_curve = Some(C::from(BSplineCurve::from_nurb( + NlCurve::new( + vec![ + CPoint::new(0.0, 0.0, 0.0, NOW), + CPoint::new(1.0, 0.0, 0.0, NOW), + ], + 1, + vec![0.0, 0.0, 1.0, 1.0], + ) + .unwrap(), + 3, + false, + ))); + } + let before = format!("{b:?}"); + assert_eq!(reverse_orientation(&k, &mut b, e), Err(expected)); + assert_eq!(format!("{b:?}"), before); + } + #[test] fn reverse_and_reset_primary() { let (mut b, e, eus, _) = wire(); diff --git a/crates/solidcore/src/topology/edgeuse/p1.rs b/crates/solidcore/src/topology/edgeuse/p1.rs index aefbbfeac7..6b122e09bd 100644 --- a/crates/solidcore/src/topology/edgeuse/p1.rs +++ b/crates/solidcore/src/topology/edgeuse/p1.rs @@ -76,10 +76,10 @@ //! cached gaps are returned by value. `CollectAdjacentFaceuses` appends to the //! caller's list, as upstream (entries already there are not added again). //! - **`sm_CalcDistAndDev`** reaches the solution's first object through a resolver -//! (upstream `SM_CAST_PTR(SmSurface, ...)`); the statuses it ignores are ignored -//! ([`SolidError::Unported`] still propagates), leaving the outputs of a failed -//! evaluation at their `SM_UNDEF_DOUBLE` defaults. `sm_AreSeamCurves`' unused -//! edge use argument is dropped. +//! (upstream `SM_CAST_PTR(SmSurface, ...)`). It and `GetStartPointUVLoopGap` +//! propagate evaluation failures (#521), instead of reading unwritten outputs; +//! an unsuccessful point drop returns `SM_ERR`. `sm_AreSeamCurves`' unused edge +//! use argument is dropped. //! - **Unported callees.** `SmEdgeuse::EvaluateBinormal` is wired //! (`topology.edgeuse_p3`); its deferred callees return [`SolidError::Unported`]. The gap computations are [`crate::heal::gap`]; //! `CalcMaxEdgeFaceGap`'s cross-section tolerance is the context one with no @@ -1104,12 +1104,6 @@ pub fn calc_edge_ccw_edge_gap( // ---- UV loop gap ------------------------------------------------------------------------ -/// A curve point at `t`, `SM_UNDEF_DOUBLE` where upstream ignores a failed -/// evaluation (only [`SolidError::Unported`] propagates). -fn point_or_undef(r: Result) -> Result { - Ok(crate::error::ignored(r)?.unwrap_or(Vec3::UNDEF)) -} - /// The edge interval of `eu`'s edge. fn edge_interval(b: &Brep, eu: EdgeuseId, what: &'static str) -> Result { let e = edge(b, eu)?.ok_or_else(|| null(what))?; @@ -1125,7 +1119,8 @@ fn edge_interval(b: &Brep, eu: EdgeuseId, what: &'static str) -> Result Result { // check state - only Loopuse Edgeuses have End_to_End gaps let r = b.edgeuse(eu)?; @@ -1168,36 +1163,37 @@ pub fn start_point_uv_loop_gap(k: &Kernel, b: &Brep, eu: EdgeuseId) -> Result( - k, - start_3d, - &domain, - &start_opts, - ))?; - // `sStart2d` stays default-constructed when the call fails - let start_2d = start_drop.map_or(Point2::UNDEF, |d| d.result_uv); + let start_drop = surface.drop_point::(k, start_3d, &domain, &start_opts)?; + if !start_drop.success { + return Err(SolidError::sm_err( + "topology::edgeuse::p1::start_point_uv_loop_gap", + )); + } + let start_2d = start_drop.result_uv; let cw_opts = DropPointOptions { guess_uv: Some(start_2d), operation: SolverOperation::Minimize, ..Default::default() }; - let cw_drop = - crate::error::ignored(surface.drop_point::(k, cw_end_3d, &domain, &cw_opts))?; - // upstream's DropPoint sets rResultUV to the guess before anything can fail - let cw_end_2d = cw_drop.map_or(start_2d, |d| d.result_uv); - return Ok(start_2d.distance(cw_end_2d)); + let cw_drop = surface.drop_point::(k, cw_end_3d, &domain, &cw_opts)?; + if !cw_drop.success { + return Err(SolidError::sm_err( + "topology::edgeuse::p1::start_point_uv_loop_gap", + )); + } + return Ok(start_2d.distance(cw_drop.result_uv)); }; - let start_uv = point_or_undef(tc.evaluate_point(k, start_param))?; - let cw_end_uv = point_or_undef(cc.evaluate_point(k, cw_end_param))?; + let start_uv = tc.evaluate_point(k, start_param)?; + let cw_end_uv = cc.evaluate_point(k, cw_end_param)?; // compute and return the EdgeEnd to EdgeEnd gap Ok(start_uv.distance(cw_end_uv)) @@ -1454,8 +1450,7 @@ pub fn faceuse_collect_adjacent( /// /// # Errors /// -/// [`SolidError::Unported`] from the surface evaluations (their other failures are -/// ignored, as upstream). +/// Those of the surface evaluations. Failed derivatives cannot supply a distance. pub fn calc_dist_and_dev<'s, O: SolutionObject>( k: &Kernel, pt: Point3, @@ -1485,9 +1480,7 @@ pub fn calc_dist_and_dev<'s, O: SolutionObject>( // Find distance (along surface normal) and deviation (in normal plane). let [u, v, ..] = sol.start.parameters; let uv = Point2::new(u, v); - let (s_pt, s_su, s_sv) = - crate::error::ignored(evaluate_1st_derivatives(k, srf, uv, Side::Left, Side::Left))? - .unwrap_or((Vec3::UNDEF, Vec3::UNDEF, Vec3::UNDEF)); + let (s_pt, s_su, s_sv) = evaluate_1st_derivatives(k, srf, uv, Side::Left, Side::Left)?; let mut norm = s_su.cross(s_sv); let mag = norm.length(); @@ -1962,6 +1955,38 @@ mod tests { assert!(uv_trim_curve_pointer(&b, a0).unwrap().is_some()); } + #[test] + fn loop_gap_refuses_failed_trim_and_edge_evaluations() { + use crate::nurbs::b_spline_curve::BSplineCurve; + let k = Kernel::default(); + let a0 = EdgeuseId::from_index(0); + for bad in [0, 6] { + let mut b = square(); + b.edgeuse_mut(a0).unwrap().uv_trim_curve = Some(uv_line([0.0, 0.0], [1.0, 0.0])); + b.edgeuse_mut(EdgeuseId::from_index(6)) + .unwrap() + .uv_trim_curve = Some(uv_line([0.0, 1.0], [0.0, 0.0])); + let empty = Curve::from(BSplineCurve::new(2)); + let expected = empty.evaluate_point(&k, 0.0).unwrap_err(); + b.edgeuse_mut(EdgeuseId::from_index(bad)) + .unwrap() + .uv_trim_curve = Some(empty); + assert_eq!(start_point_uv_loop_gap(&k, &b, a0), Err(expected)); + } + for bad in [0, 3] { + let mut b = square(); + for (edge, a, z) in [(0, Vec3::ZERO, Vec3::X), (3, Vec3::Y, Vec3::ZERO)] { + b.edge_mut(EdgeId::from_index(edge)).unwrap().curve = + Some(Curve::from(crate::curve::line::Line::from_points(a, z, 3))); + } + assert_eq!(start_point_uv_loop_gap(&k, &b, a0).unwrap(), 0.0); + let empty = Curve::from(BSplineCurve::new(3)); + let expected = empty.evaluate_point(&k, 0.0).unwrap_err(); + b.edge_mut(EdgeId::from_index(bad)).unwrap().curve = Some(empty); + assert_eq!(start_point_uv_loop_gap(&k, &b, a0), Err(expected)); + } + } + #[test] fn start_point_uv_loop_gap_uses_trim_curves() { let mut b = square(); @@ -2066,6 +2091,26 @@ mod tests { } } + #[test] + fn dist_and_dev_propagates_surface_evaluation_failure() { + let k = Kernel::default(); + let s = Surface::from(crate::nurbs::b_spline_surface::BSplineSurface::default()); + let mut sol = Solution::::new(); + sol.objects[0] = Some(H(ObjectKind::Surface)); + sol.start.solution_value = 0.5; + let expected = + evaluate_1st_derivatives(&k, &s, Point2::new(0.0, 0.0), Side::Left, Side::Left) + .unwrap_err(); + assert_eq!( + calc_dist_and_dev(&k, Vec3::Z, &sol, |_| Some(&s), true), + Err(expected) + ); + assert_eq!( + calc_dist_and_dev(&k, Vec3::Z, &sol, |_| Some(&s), false).unwrap(), + (0.5, None) + ); + } + #[test] fn dist_and_dev_on_a_plane() { let k = &Kernel::default(); diff --git a/crates/solidcore/src/topology/loopuse.rs b/crates/solidcore/src/topology/loopuse.rs index e16e76fd25..03735cc26a 100644 --- a/crates/solidcore/src/topology/loopuse.rs +++ b/crates/solidcore/src/topology/loopuse.rs @@ -82,6 +82,9 @@ //! - **Prepared queries.** The shared UV-corner walk can also borrow prepared topology //! without mutation. It reports [`SolidError::Unported`] only if a trim curve needs //! creating at the point where the mutable walk would create it. +//! - **UV-corner failures (#521).** Failed curve evaluations and point drops propagate; +//! an unsuccessful drop returns `SM_ERR`. A failed edge use cannot reuse the +//! preceding edge use's endpoints to classify the loop's corners. //! - The `std::unordered_set` overloads of `GetEdges` / `GetVertices`, //! `GetEdgeusesAndVertexuses`, `CalculateBoundingBox`, `GetTurningAngDeg`, //! `IsLoopuseSideInSurface`, `SmLoop::GetMaxSameDimGap3d`, `IsLamina`, `IsManifold`, @@ -94,7 +97,6 @@ use std::ops::Deref; use crate::curve::curve::Curve; use crate::error::{EntityRef, Result, SolidError}; use crate::geom::attribute::{AttributeStore, ObjectKey}; -use crate::geom::extent1d::Extent1d; use crate::geom::extent2d::{Extent2d, SS_NONE}; use crate::geom::object::{NotifyOperation, ObjectRef}; use crate::geom::tol; @@ -1275,25 +1277,17 @@ fn get_or_create_uv_trim_curve( .cloned()) } -/// `pS->DropPoint(point, domain, NULL, bSuccess, uv, dGap, bIsMulti)` (unit -/// `surface.surface_p5`): the UV point it writes to its out-param, or `None` only when -/// it leaves the out-param unwritten. Upstream ignores both the status and `bSuccess` -/// here, so a point written by a failed drop must still be returned. -/// -/// With no guess and no solution array, `DropPoint` writes `rResultUV` only when it -/// ends with `rbSuccess` set (its failed `GlobalPointSolve` returns before any write). -fn drop_point(k: &Kernel, s: &Surface, dom: &Extent2d, p: Point3) -> Result> { +/// Drops a corner onto the surface. Only a successful drop supplies a corner; +/// upstream's ignored failure can leave the previous edge use's point in place. +fn drop_point(k: &Kernel, s: &Surface, dom: &Extent2d, p: Point3) -> Result { let opts = crate::surface::surface::p5::DropPointOptions::default(); - let r = ignored(s.drop_point::(k, p, dom, &opts))?; - Ok(r.filter(|d| d.success).map(|d| d.result_uv)) -} - -/// Ignores a status upstream ignores ([`SolidError::Unported`] still propagates). -fn ignored(r: Result) -> Result> { - match r { - Ok(v) => Ok(Some(v)), - Err(e) if e.is_unported() => Err(e), - Err(_) => Ok(None), + let r = s.drop_point::(k, p, dom, &opts)?; + if r.success { + Ok(r.result_uv) + } else { + Err(SolidError::sm_err( + "topology::loopuse::uv_corner_boundaries", + )) } } @@ -1305,7 +1299,8 @@ fn ignored(r: Result) -> Result> { /// # Errors /// /// [`SolidError::Unported`] for a loop with edge uses (`GetOrCreateUVTrimCurve`); -/// those of [`edgeuses`]; [`SolidError::NotFound`] for a dangling id. +/// those of [`edgeuses`], curve evaluations and point drops; `SM_ERR` when a point +/// drop does not converge; [`SolidError::NotFound`] for a dangling id. pub fn uv_corner_boundaries( k: &Kernel, b: &mut Brep, @@ -1335,29 +1330,17 @@ pub(crate) fn uv_corner_boundaries_with_topology( return Ok(out); }; - // locals - let mut uv_pnt_beg = Point2::UNDEF; - let mut uv_pnt_end = Point2::UNDEF; - let mut uv_beg = Vec3::UNDEF; - let mut uv_end = Vec3::UNDEF; - // for every edgeuse - classify the end points for eu in edgeuses(b, lu, None, None)? { // get UVTrimCurve let trim = get_or_create_uv_trim_curve(k, b, eu)?; - let ivl: Extent1d; - if let Some(trim) = trim { + let (ivl, uv_pnt_beg, uv_pnt_end) = if let Some(trim) = trim { // end points - ivl = trim.natural_interval(); - if let Some(p) = ignored(trim.evaluate_point(k, ivl.evaluate(0.0)))? { - uv_beg = p; - } - if let Some(p) = ignored(trim.evaluate_point(k, ivl.evaluate(1.0)))? { - uv_end = p; - } - uv_pnt_beg = Point2::new(uv_beg.x, uv_beg.y); - uv_pnt_end = Point2::new(uv_end.x, uv_end.y); + let ivl = trim.natural_interval(); + let beg = trim.evaluate_point(k, ivl.evaluate(0.0))?; + let end = trim.evaluate_point(k, ivl.evaluate(1.0))?; + (ivl, Point2::new(beg.x, beg.y), Point2::new(end.x, end.y)) } else { // Use point drop for problem edgeuses let e = eu1::edge(b, eu)?.ok_or_else(|| null("GetUVCornerBoundaries: no edge"))?; @@ -1373,16 +1356,15 @@ pub(crate) fn uv_corner_boundaries_with_topology( .curve .as_ref() .ok_or_else(|| null("GetUVCornerBoundaries: no curve"))?; - ivl = edge.interval; - let beg = ignored(c.evaluate_point(k, ivl.min()))?.unwrap_or(Vec3::UNDEF); - let end = ignored(c.evaluate_point(k, ivl.max()))?.unwrap_or(Vec3::UNDEF); - if let Some(p) = drop_point(k, s, &dom, beg)? { - uv_pnt_beg = p; - } - if let Some(p) = drop_point(k, s, &dom, end)? { - uv_pnt_end = p; - } - } + let ivl = edge.interval; + let beg = c.evaluate_point(k, ivl.min())?; + let end = c.evaluate_point(k, ivl.max())?; + ( + ivl, + drop_point(k, s, &dom, beg)?, + drop_point(k, s, &dom, end)?, + ) + }; let edge = eu1::edge(b, eu)?; // classify the BegPoint @@ -1430,6 +1412,7 @@ pub fn calc_quad_num(origin: Point3, test: Point3) -> u32 { #[cfg(test)] mod tests { use super::*; + use crate::geom::extent1d::Extent1d; use crate::nurbs::b_spline_curve::BSplineCurve; use crate::nurbs::crv_basic::Curve as NlCurve; use crate::nurbs::math::{CPoint, NOW}; @@ -1749,6 +1732,26 @@ mod tests { ); } + #[test] + fn uv_corners_refuse_failed_trim_evaluation_after_valid_edge() { + let k = Kernel::default(); + let dom = Extent2d::from_bounds(0.0, 0.0, 1.0, 1.0); + for bad in [0, 2] { + let mut b = square(0.0); + for i in 0..4 { + b.edgeuse_mut(eu(2 * i)).unwrap().uv_trim_curve = + b.edge(EdgeId::from_index(i)).unwrap().curve.clone(); + } + let empty = Curve::from(BSplineCurve::new(2)); + let expected = empty.evaluate_point(&k, 0.0).unwrap_err(); + b.edgeuse_mut(eu(bad)).unwrap().uv_trim_curve = Some(empty); + assert_eq!( + uv_corner_boundaries(&k, &mut b, LoopId::from_index(0), &dom, 1e-6), + Err(expected), + ); + } + } + #[test] fn vertex_loop_and_corners() { let k = Kernel::default(); diff --git a/fixtures/topology.vertex/constructed.json b/fixtures/topology.vertex/constructed.json index 5e4e98c5a1..c7ce5bd2fa 100644 --- a/fixtures/topology.vertex/constructed.json +++ b/fixtures/topology.vertex/constructed.json @@ -102932,6 +102932,1213 @@ 0.125 ] } + }, + { + "name": "failed_tangent_constant0_rev0_v0_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 0, + 0, + 1, + 1 + ] + }, + "rev": false + }, + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ], + "rev": false + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 0 + } + }, + { + "name": "failed_tangent_constant0_rev0_v1_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 0, + 0, + 1, + 1 + ] + }, + "rev": false + }, + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ], + "rev": false + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 1 + } + }, + { + "name": "failed_tangent_constant0_rev0_v2_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 0, + 0, + 1, + 1 + ] + }, + "rev": false + }, + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ], + "rev": false + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 2 + } + }, + { + "name": "failed_tangent_constant0_rev0_tangent", + "upstream_ref": "source/SMLib/src/SmVertex.cpp / #521: AngleBetween fails on a zero derivative", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 0, + 0, + 1, + 1 + ] + }, + "rev": false + }, + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ], + "rev": false + } + ] + }, + "init_gaps": false, + "op": "tangent", + "v": 1, + "e1": 0, + "e2": 1, + "tol_deg": 0.5 + } + }, + { + "name": "failed_tangent_constant0_rev1_v0_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 0, + 0, + 1, + 1 + ] + }, + "rev": true + }, + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ], + "rev": true + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 0 + } + }, + { + "name": "failed_tangent_constant0_rev1_v1_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 0, + 0, + 1, + 1 + ] + }, + "rev": true + }, + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ], + "rev": true + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 1 + } + }, + { + "name": "failed_tangent_constant0_rev1_v2_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 0, + 0, + 1, + 1 + ] + }, + "rev": true + }, + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ], + "rev": true + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 2 + } + }, + { + "name": "failed_tangent_constant0_rev1_tangent", + "upstream_ref": "source/SMLib/src/SmVertex.cpp / #521: AngleBetween fails on a zero derivative", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 0, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 0, + 0, + 1, + 1 + ] + }, + "rev": true + }, + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ], + "rev": true + } + ] + }, + "init_gaps": false, + "op": "tangent", + "v": 1, + "e1": 0, + "e2": 1, + "tol_deg": 0.5 + } + }, + { + "name": "failed_tangent_constant1_rev0_v0_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ], + "rev": false + }, + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 1, + 1, + 2, + 2 + ] + }, + "rev": false + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 0 + } + }, + { + "name": "failed_tangent_constant1_rev0_v1_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ], + "rev": false + }, + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 1, + 1, + 2, + 2 + ] + }, + "rev": false + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 1 + } + }, + { + "name": "failed_tangent_constant1_rev0_v2_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ], + "rev": false + }, + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 1, + 1, + 2, + 2 + ] + }, + "rev": false + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 2 + } + }, + { + "name": "failed_tangent_constant1_rev0_tangent", + "upstream_ref": "source/SMLib/src/SmVertex.cpp / #521: AngleBetween fails on a zero derivative", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ], + "rev": false + }, + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 1, + 1, + 2, + 2 + ] + }, + "rev": false + } + ] + }, + "init_gaps": false, + "op": "tangent", + "v": 1, + "e1": 0, + "e2": 1, + "tol_deg": 0.5 + } + }, + { + "name": "failed_tangent_constant1_rev1_v0_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ], + "rev": true + }, + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 1, + 1, + 2, + 2 + ] + }, + "rev": true + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 0 + } + }, + { + "name": "failed_tangent_constant1_rev1_v1_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ], + "rev": true + }, + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 1, + 1, + 2, + 2 + ] + }, + "rev": true + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 1 + } + }, + { + "name": "failed_tangent_constant1_rev1_v2_topological", + "upstream_ref": "source/SMLib/src/SmVertex.cpp:716 / #521: zero derivative is not a removable-vertex certificate", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ], + "rev": true + }, + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 1, + 1, + 2, + 2 + ] + }, + "rev": true + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 2 + } + }, + { + "name": "failed_tangent_constant1_rev1_tangent", + "upstream_ref": "source/SMLib/src/SmVertex.cpp / #521: AngleBetween fails on a zero derivative", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ], + "rev": true + }, + { + "seg": "curve", + "curve": { + "dim": 3, + "cpts": [ + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ], + [ + 1, + 0, + 0, + -1.7976931348623157e+308 + ] + ], + "p": 1, + "u": [ + 1, + 1, + 2, + 2 + ] + }, + "rev": true + } + ] + }, + "init_gaps": false, + "op": "tangent", + "v": 1, + "e1": 0, + "e2": 1, + "tol_deg": 0.5 + } + }, + { + "name": "failed_tangent_control_v0_topological", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ] + }, + { + "seg": "line", + "a": [ + 1, + 0, + 0 + ], + "b": [ + 2, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ] + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 0 + } + }, + { + "name": "failed_tangent_control_v1_topological", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ] + }, + { + "seg": "line", + "a": [ + 1, + 0, + 0 + ], + "b": [ + 2, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ] + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 1 + } + }, + { + "name": "failed_tangent_control_v2_topological", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ] + }, + { + "seg": "line", + "a": [ + 1, + 0, + 0 + ], + "b": [ + 2, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ] + } + ] + }, + "init_gaps": false, + "op": "topological", + "v": 2 + } + }, + { + "name": "failed_tangent_control_tangent", + "inputs": { + "brep": { + "kind": "chain", + "closed": false, + "segs": [ + { + "seg": "line", + "a": [ + 0, + 0, + 0 + ], + "b": [ + 1, + 0, + 0 + ], + "smline": true, + "ivl": [ + 0, + 1 + ] + }, + { + "seg": "line", + "a": [ + 1, + 0, + 0 + ], + "b": [ + 2, + 0, + 0 + ], + "smline": true, + "ivl": [ + 1, + 2 + ] + } + ] + }, + "init_gaps": false, + "op": "tangent", + "v": 1, + "e1": 0, + "e2": 1, + "tol_deg": 0.5 + } } ] -} \ No newline at end of file +} diff --git a/plan/units/heal.face_props.toml b/plan/units/heal.face_props.toml index 20d91ca28e..f3b14bc76d 100644 --- a/plan/units/heal.face_props.toml +++ b/plan/units/heal.face_props.toml @@ -6,7 +6,7 @@ soft_depends_on = ["geom.extent2d", "geom.object", "intersect.curve_class_p2", " est_lines = 1410 layer = 46 tests = ["prog_test:my_test_trimming_tools", "SM_API_test:TestSmTrimSurfaceWith3dCurves", "prog_test:my_test_closed_sphere_properties", "prog_test:my_test_squeeze_edge_tracks_removal", "prog_test:my_test_missing_poles_refreshes_all_faces", "SM_API_test:TestSmHealBrep"] -status = "green" +status = "partial" claimed_by = "" claimed_at = "" -notes = "2026-10-25 (PR #863, snapshot surfaces keep their face owner, plan/findings.md 'snapshot surfaces keep their face owner'): gen0646, gen0790, gen1312 pass; GREEN heal.face_props cases=1528 passed=1528 failed=0 deferred=0 max_rel_err=0e0 with_values=1519 no_values=9 vacuous=0. 2026-10-24 (wire: trimmed surface cache history; the intersect::curve_class::p7 tolerance-growth deferral removed, plan/findings.md 'trimmed surface cache history: tolerance-growth deferral removed'), Linux: of 15 surface.surface_p2 deferrals 12 pass, 3 are known mismatches (gen0646, gen0790, gen1312: torus seam FindVertexSector). PARTIAL heal.face_props cases=1528 passed=1525 failed=0 deferred=0 max_rel_err=1e0 with_values=1516 no_values=9 vacuous=0 known_mismatch=3. PR #697 (2026-10-24, face surfaces keep their trimmed caches): 1528 cases, 1439 pass, 0 failed, 89 deferred (topology.face_p4 88, intersect.curve_class_p4 1); the 15 cache-history (tolerance growth) deferrals go: IntersectVertices' gap reads now run with the surface owner cleared, so the standalone trimmed cache upstream keeps is made there and reused (8 pass, the rest defer further on at FindVertexSector). PR #639 (2026-10-23): the runner builds the down face use side (SmBrepConstructor), which fixes the seam EDGE-vs-VERTEX snap; difftest 1528 cases, 1431 pass, 0 failed, 97 deferred (topology.face_p4 FindVertexSector 82, surface.surface_p2 trimmed surface cache history 15; was FindVertexSector 69 + EdgeCurveOnClassify 28). PARTIAL: difftest 1528 cases (28 fixture + 1500 generated: SetProps/Gaps/Stage2/Stage3 scripts, SmLoopProps::SetProps of every loop (op 8), seeded state, ReSet, HasProps, HasProblems, IsPoint3dOnPole, loop tree build + FindItem, on one-face B-reps with a plane or SmSurfOfRevolution surface (cylinder, cone with/without apex, sphere, torus; open or closed) owned by the face, a vertex loop or edge loops with holes, face tolerances up to 1e9; 9 fixture cases are FFI-guard regression cases, UB-131 and UB-128), 1314 pass bit-identical (max_rel_err 0), 0 failed, 214 deferred (topology.face_p4 210: SmFace::HasSeamProblem for closed faces in SetProps_Stage2; query.face_p4 4: SmFace::ClassifyLoops in SetProps_Stage3); --perturb 1314/1314 detected. Upstream defects UB-131 (SetProps_Gaps early return makes the Stage2/3 face re-run ineffective) and UB-128 (SetProps_Stage3 on a plane with a degenerate domain and edge loops segfaults in ClassifyLoops; the guard rejects a plane side below 1e-6 when Stage 3 can be reached); the guard rejects the crashing scripts. Snapshots compare the flat corner points too; no generated surface has a nonzero m_lBadFlatCorners (harness limitation, findings.md). Stage 3 is compared only through op 8 (direct Stage 3 cases defer at ClassifyLoops). Added Surface::get_singularities_of_face (face tolerance for the owned surface; other UNPORTED(topology.face_p2) tolerance stubs on surfaces unchanged). Wired: topology.loopuse (PR #102), SmSurface::GetSingularities (surface.surface_p3), SmFace::GetVertices (query.face_p2 co-port). Remaining UNPORTED stubs: topology.face_p4 HasSeamProblem, query.face_p4 ClassifyLoops, intersect.curve_class_p2 CurveClassification::ReSet (dead branch). No UNPORTED(heal.face_props) stubs existed on main. FaceProps is generic over a LoopObject handle; the loop tree is an arena (Looptree). CO-PORTED in heal.face_props (PR #122): SmMixedLinkType, SmTriedLinkType, SmHealerOpType (SmHealProbArray.h) -> heal::face_props::{MixedLinkType, TriedLinkType, HealerOpType} (owner heal.heal_data_p2 extends); SmContainmentType (SmTypes.h) -> heal::face_props::ContainmentType. 25 fns; largest: SmFaceProps::SetProps_Stage3, SmFaceProps::SetProps_Stage2, SmLoopProps::SetProps, SmFaceProps::ReSet; unit-SCC #22 (220 units); 100 upstream test entry points reach this unit PR #137 (intersect.curve_class_p4): the shim's face builder, seed and snapshot moved verbatim into harness/cpp/shim/sc_one_face_brep.h, shared with the intersect.curve_class_p4 shim; the runner's FaceIn, rust_brep, ffi_face, snapshot, seed and gen_face are pub(crate) for that runner, the ffi gained encode_face and pub seed_ok. Re-run: 1528 cases, 1314 pass, 214 deferred (query.face_p4 4, topology.face_p4 210), 0 failed, --perturb SENSITIVE 1314/1314. Merge of query.face_p3 (PR #132): query.face_p4 and query.face_p3 wired, the 4 ClassifyLoops deferrals reach further: 1528 cases, 1317 pass, 211 deferred (topology.face_p4 210, topology.face_p2 1), 0 failed. topology.face_p4: FaceProps::set_props_stage2_with takes the SetProps_Stage2 HasSeamProblem call as a callback (face::p4::has_seam_problem passes itself); set_props_stage2 keeps the stub for callers without classification objects." +notes = "[2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1528 native cases: 1427 match, 97 existing UNPORTED deferrals and 4 explicit WR-263 diagnostic stage-marker differences. Failed Stage3 preserves status 1040 and partial diagnostics while resetting its in-progress marker from 5 to 4 for retry; ClassifyLoops errors now propagate before output arrays are read.] 2026-10-25 (PR #863, snapshot surfaces keep their face owner, plan/findings.md 'snapshot surfaces keep their face owner'): gen0646, gen0790, gen1312 pass; GREEN heal.face_props cases=1528 passed=1528 failed=0 deferred=0 max_rel_err=0e0 with_values=1519 no_values=9 vacuous=0. 2026-10-24 (wire: trimmed surface cache history; the intersect::curve_class::p7 tolerance-growth deferral removed, plan/findings.md 'trimmed surface cache history: tolerance-growth deferral removed'), Linux: of 15 surface.surface_p2 deferrals 12 pass, 3 are known mismatches (gen0646, gen0790, gen1312: torus seam FindVertexSector). PARTIAL heal.face_props cases=1528 passed=1525 failed=0 deferred=0 max_rel_err=1e0 with_values=1516 no_values=9 vacuous=0 known_mismatch=3. PR #697 (2026-10-24, face surfaces keep their trimmed caches): 1528 cases, 1439 pass, 0 failed, 89 deferred (topology.face_p4 88, intersect.curve_class_p4 1); the 15 cache-history (tolerance growth) deferrals go: IntersectVertices' gap reads now run with the surface owner cleared, so the standalone trimmed cache upstream keeps is made there and reused (8 pass, the rest defer further on at FindVertexSector). PR #639 (2026-10-23): the runner builds the down face use side (SmBrepConstructor), which fixes the seam EDGE-vs-VERTEX snap; difftest 1528 cases, 1431 pass, 0 failed, 97 deferred (topology.face_p4 FindVertexSector 82, surface.surface_p2 trimmed surface cache history 15; was FindVertexSector 69 + EdgeCurveOnClassify 28). PARTIAL: difftest 1528 cases (28 fixture + 1500 generated: SetProps/Gaps/Stage2/Stage3 scripts, SmLoopProps::SetProps of every loop (op 8), seeded state, ReSet, HasProps, HasProblems, IsPoint3dOnPole, loop tree build + FindItem, on one-face B-reps with a plane or SmSurfOfRevolution surface (cylinder, cone with/without apex, sphere, torus; open or closed) owned by the face, a vertex loop or edge loops with holes, face tolerances up to 1e9; 9 fixture cases are FFI-guard regression cases, UB-131 and UB-128), 1314 pass bit-identical (max_rel_err 0), 0 failed, 214 deferred (topology.face_p4 210: SmFace::HasSeamProblem for closed faces in SetProps_Stage2; query.face_p4 4: SmFace::ClassifyLoops in SetProps_Stage3); --perturb 1314/1314 detected. Upstream defects UB-131 (SetProps_Gaps early return makes the Stage2/3 face re-run ineffective) and UB-128 (SetProps_Stage3 on a plane with a degenerate domain and edge loops segfaults in ClassifyLoops; the guard rejects a plane side below 1e-6 when Stage 3 can be reached); the guard rejects the crashing scripts. Snapshots compare the flat corner points too; no generated surface has a nonzero m_lBadFlatCorners (harness limitation, findings.md). Stage 3 is compared only through op 8 (direct Stage 3 cases defer at ClassifyLoops). Added Surface::get_singularities_of_face (face tolerance for the owned surface; other UNPORTED(topology.face_p2) tolerance stubs on surfaces unchanged). Wired: topology.loopuse (PR #102), SmSurface::GetSingularities (surface.surface_p3), SmFace::GetVertices (query.face_p2 co-port). Remaining UNPORTED stubs: topology.face_p4 HasSeamProblem, query.face_p4 ClassifyLoops, intersect.curve_class_p2 CurveClassification::ReSet (dead branch). No UNPORTED(heal.face_props) stubs existed on main. FaceProps is generic over a LoopObject handle; the loop tree is an arena (Looptree). CO-PORTED in heal.face_props (PR #122): SmMixedLinkType, SmTriedLinkType, SmHealerOpType (SmHealProbArray.h) -> heal::face_props::{MixedLinkType, TriedLinkType, HealerOpType} (owner heal.heal_data_p2 extends); SmContainmentType (SmTypes.h) -> heal::face_props::ContainmentType. 25 fns; largest: SmFaceProps::SetProps_Stage3, SmFaceProps::SetProps_Stage2, SmLoopProps::SetProps, SmFaceProps::ReSet; unit-SCC #22 (220 units); 100 upstream test entry points reach this unit PR #137 (intersect.curve_class_p4): the shim's face builder, seed and snapshot moved verbatim into harness/cpp/shim/sc_one_face_brep.h, shared with the intersect.curve_class_p4 shim; the runner's FaceIn, rust_brep, ffi_face, snapshot, seed and gen_face are pub(crate) for that runner, the ffi gained encode_face and pub seed_ok. Re-run: 1528 cases, 1314 pass, 214 deferred (query.face_p4 4, topology.face_p4 210), 0 failed, --perturb SENSITIVE 1314/1314. Merge of query.face_p3 (PR #132): query.face_p4 and query.face_p3 wired, the 4 ClassifyLoops deferrals reach further: 1528 cases, 1317 pass, 211 deferred (topology.face_p4 210, topology.face_p2 1), 0 failed. topology.face_p4: FaceProps::set_props_stage2_with takes the SetProps_Stage2 HasSeamProblem call as a callback (face::p4::has_seam_problem passes itself); set_props_stage2 keeps the stub for callers without classification objects." diff --git a/plan/units/topology.edge.toml b/plan/units/topology.edge.toml index 8ea15de914..ca2dd02d94 100644 --- a/plan/units/topology.edge.toml +++ b/plan/units/topology.edge.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test: status = "green" claimed_by = "" claimed_at = "" -notes = "Issue #294 private edge revolution adapter: reuse the shared exact record exporter and reader for all supported analytic surfaces (SmSurfOfRevolution, SmCone/SmCylinder, SmSphere, SmTorus, SmSurfOfExtrusion). Retains the 72 native revolution regressions plus new Cone/Sphere/Cylinder exact coverage and exact/malformed-record controls. Historical before PR #596 (final GREEN): difftest 1787 cases (1771 fixture: 236 on prog_test my_test_topology's wire network (its SetCurve / SetInterval / SetOrientation calls and every other op), 140 on wire edges of real SmLines (my_test_reversed_line_join's lines, forward and inside-out, sub-intervals, 1e-6 and 1e6 scales), 1395 on 9 upstream .smb B-reps read by SmBrep::ReadFromFile with and without RebuildUVTrimCurves; 16 generated, 2000 more checked once), 1715 pass, 72 deferred (topology.edgeuse_p3 62, topology.brep_p1 5, topology.brep_p2 5; topology.vertex wired after merging main), max_rel_err 0; --perturb SENSITIVE. Ops: the queries (edge use/vertex use/vertex/face/shell/region collections with attribute and connection filters, start/end/other vertex, lamina/manifold/spine/closed/wire, IsConnectedTo every kind), end tangent, trimmed NURBS copy, SetTolerance, SetOrientation, ReverseOrientation, OrientEdgeSequence, ResetPrimaryEdgeuse, AddOrientedEUPair (new edge uses), InsertManifoldEdgeEnd, cached gap maxima (gap values exported), Notify, SetCurve, both FindRadialSector, GetUVTrimCurveOfSurface, both IsSeam, IsTopological, IsTangentEdge, GetBlendEdgeuse, SmODEEdgeLengthIFEO::Evaluate, SetInterval. Edges with an ellipse curve or faces of SmBSplineSurface subclasses other than SmPlane are imported as their B-spline part: the C++ runner refuses ops evaluating them with inexact_geometry, and the Rust runner reports a harness deferral on topology.edge (exact analytic geometry import for edge evaluation); neither side evaluates the skipped op. Stubs: UNPORTED(topology.edgeuse_p3) SmEdgeuse::NormalizedEvaluate / EvaluateBinormal / GetOrCreateUVTrimCurve, UNPORTED(topology.brep_p2) SmBrep::Notify, UNPORTED(topology.brep_p1) SmBrep::GetInfiniteRegion, SmFace::CalculateUVDomainFromUVTrimCurves (wired by topology.face_p1, PR #148). Back-edges wired: SmSurface::CoverOtherCoincidentSurface (surface.surface_p5 PR #50; its CoverCoincidentSurface is surface.surface_p3, and no current case reaches it past the edgeuse_p3 deferrals), SmEdge::SetTolerance in topology::edgeuse::{p1,p2} and topology::vertex (RefreshTolerance), SmVertex::SetTolerance in topology::edge (merge with main), the SmEdge queries of geom::tools; the UNPORTED(topology.edge) markers left in intersect::point_class, intersect::curve_class::p1, topology::proto_topology::p1, geom::{object,solution_array,tol} are on opaque object handles with no B-rep to call into (dispatch gaps). Plan notes: 60 fns; largest: SmEdge::IsTopological, SmEdge::FindRadialSector, SmEdge::GetBlendEdgeuse, SmEdge::OrientEdgeSequence; cycle back-edges (call into later units; stub or co-port): heal.gap(12), topology.edgeuse_p3(3), query.edge(2), topology.brep_p2(2), blend.fillet_geom_p1(1), topology.brep_p1(1), topology.face_p1(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmEdge members m_pList (edge use list, primary first), m_vInterval, m_sZoneTol3d, m_pCurve -> topology::Edge::{edgeuses, interval, tolerance, curve}; SmEdge::GetPrimaryEdgeuse, IsLamina, GetBrep -> topology::edgeuse::p1::{primary_edgeuse, edge_is_lamina, edge_brep}; CO-PORTED in topology.vertexuse (PR #88): SmEdge::IsWire -> topology::vertexuse::edge_is_wire; CO-PORTED in query.edge (PR #89): SmEdge::GetVertices(SmTArray&, ULONG*) (no attribute filter), SmEdge::GetFaces (no attribute filter) -> query::edge::{edge_vertices, edge_faces}; CO-PORTED in topology.face_p2 (PR #91): SmEdge::GetEdgeuses without filters -> private topology::face::p2::edge_edgeuses; CO-PORTED in topology.vertex (PR #96): SmEdge::GetStartVertex, GetVertices(SmVertex*&, SmVertex*&), GetVertices(SmTArray&) (no attribute filter), GetEndTangent -> topology::vertex::{edge_start_vertex, edge_vertex_pair, edge_vertices, edge_end_tangent} Re-run (PR #52, after merging surface_p4/#49 and surface_p5/#50): is_topological takes &Kernel and &mut Brep (CoverCoincidentSurface expands the covering face surface in place); 1787 cases, 1715 pass, 72 deferred (topology.edgeuse_p3 62, topology.brep_p1 5, topology.brep_p2 5). Re-run in PR #154 (shell state export, SmBrep::GetEdges registered first so edge selectors pick different edges): same 1787 cases, 5 moved deferred -> pass (topology.edgeuse_p3 67 -> 62), 0 failed, --perturb SENSITIVE 1715/1715. PR #163 adds find_radial_sector_between for independent operand arenas; existing same-B-rep API delegates to it. Cross-operand C++ cases live in fixtures/boolean.merge_p1/review.json. [topology.edgeuse_p3 wired (PR #117): 1787 cases, 1771 pass, 0 failed, 16 deferred (intersect.surface_drop_lib_p3 6, topology.brep_p1 5, topology.brep_p2 5)] Re-run after topology.brep_p1 (PR #145) wired SmBrep::GetInfiniteRegion (and main's #154 merged): 1787 cases, 1715 pass, 0 failed, 72 deferred (topology.edgeuse_p3 67, topology.brep_p2 5), max_rel_err 0. PR #183 continuation review restores both InsertManifoldEdgeEnd vertex owning-list insertions in native order, with a focused start/end regression. Re-run after topology.brep_p2 (PR #149) wired SmBrep::Notify: 1787 cases, 1715 pass, 0 failed, 72 deferred (topology.edgeuse_p3 72), max_rel_err 0. Re-run after merging #145 5b1b28ae (main through e1ae2640) into #149 (#117 wired the topology.edgeuse_p3 stubs, #145 GetInfiniteRegion, #149 SmBrep::Notify): 1787 cases, 1781 pass, 0 fail, 6 deferred (intersect.surface_drop_lib_p3 6), max_rel_err 0, --perturb SENSITIVE 1781/1781; the 5 net_set_curve_delete_notify cases now compare 155 values (49 before) as Notify runs. Reporting correction (PR #356, issue #294): fresh default-seed baseline 1787 cases, 1781 passed (1595 with values, 186 empty inexact_geometry refusals), 6 deferred, 0 failed. Reclassifying only the Rust-side import refusals gives 1595 passed with values, 192 deferred (topology.edge harness import gap 186, intersect.surface_drop_lib_p3 6), 0 failed, 0 empty passes, max_rel_err 0. All 1601 other case records and both runner outputs are unchanged; the 186 native refusal outputs are unchanged. Still partial; exact-import support and numerical coverage are unchanged. UPDATE (PR #204, topology.brep_p3): doc only: NotifyEnv::records / attributes are keyed by notification identities (the caller's key_of, the caller's to keep distinct; kernel-issued ones are unique only within one identity domain, so a store holding them belongs to one domain); key_of keys from KERNEL_KEY_BASE up are the kernel's. PR #596 exact analytic surface transport: the shim emits the shared exact head for every supported analytic surface, and the runner imports it with the shared exact reader. CI difftest (run 39941441548, head b03bedb2b): 1787 cases, 1787 pass, 0 fail, 0 deferred, max_rel_err 0, with_values 1787. All 114 formerly deferred cases now compare: 112 directly and the 2 that had advanced to topology.face_p2 after the owned snapshot in topology.edgeuse_p3::evaluate_binormal_uv restores the exact cached face tolerance after set_owner. The 72 native revolution regressions remain. The two native paths box_with_large_fillet_e11_tangent (retained original, Cone) and box_with_large_fillet_rb_e0_tangent (rebuilt UV, surface-agnostic) both require the exact-import flag, no rejection/defer/error, and a real native comparison; the inexact-import guard is unchanged. No ellipse/circle curve transport was required. GREEN. PR #716 private ellipse/circle edge transport: exact native database records, explicit out-of-bounds restoration through the inherited Ellipse setter, native non-mutation/read-write checks and independent raw-base bit checks. Adds 128 wire cases covering tangent, trim, reverse and ODE values across senses, orientations, scales, trimmed and non-unit intervals. CI at 8fc5cb710 (run 40062996668): 1915/1915 with values, zero failures/deferrals/empty/vacuous passes, max relative error 0. The 72 revolution regressions remain. Analytic UV curves and unrelated curve kinds stay unsupported. CO-PORTED in topology.brep_validation (PR #961): SmEdge::ValidateGeometry(FALSE) -> topology::brep::validation::pointers::check_edge_geometry (private), preserving endpoint/owner statuses and native reversal repairs; warning-only diagnostics retain their evaluations." +notes = "[2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1787/1787 native cases match after #521 error propagation; failed Hermite reversal preserves the edge interval, edge-use orientations and UV trims.] Issue #294 private edge revolution adapter: reuse the shared exact record exporter and reader for all supported analytic surfaces (SmSurfOfRevolution, SmCone/SmCylinder, SmSphere, SmTorus, SmSurfOfExtrusion). Retains the 72 native revolution regressions plus new Cone/Sphere/Cylinder exact coverage and exact/malformed-record controls. Historical before PR #596 (final GREEN): difftest 1787 cases (1771 fixture: 236 on prog_test my_test_topology's wire network (its SetCurve / SetInterval / SetOrientation calls and every other op), 140 on wire edges of real SmLines (my_test_reversed_line_join's lines, forward and inside-out, sub-intervals, 1e-6 and 1e6 scales), 1395 on 9 upstream .smb B-reps read by SmBrep::ReadFromFile with and without RebuildUVTrimCurves; 16 generated, 2000 more checked once), 1715 pass, 72 deferred (topology.edgeuse_p3 62, topology.brep_p1 5, topology.brep_p2 5; topology.vertex wired after merging main), max_rel_err 0; --perturb SENSITIVE. Ops: the queries (edge use/vertex use/vertex/face/shell/region collections with attribute and connection filters, start/end/other vertex, lamina/manifold/spine/closed/wire, IsConnectedTo every kind), end tangent, trimmed NURBS copy, SetTolerance, SetOrientation, ReverseOrientation, OrientEdgeSequence, ResetPrimaryEdgeuse, AddOrientedEUPair (new edge uses), InsertManifoldEdgeEnd, cached gap maxima (gap values exported), Notify, SetCurve, both FindRadialSector, GetUVTrimCurveOfSurface, both IsSeam, IsTopological, IsTangentEdge, GetBlendEdgeuse, SmODEEdgeLengthIFEO::Evaluate, SetInterval. Edges with an ellipse curve or faces of SmBSplineSurface subclasses other than SmPlane are imported as their B-spline part: the C++ runner refuses ops evaluating them with inexact_geometry, and the Rust runner reports a harness deferral on topology.edge (exact analytic geometry import for edge evaluation); neither side evaluates the skipped op. Stubs: UNPORTED(topology.edgeuse_p3) SmEdgeuse::NormalizedEvaluate / EvaluateBinormal / GetOrCreateUVTrimCurve, UNPORTED(topology.brep_p2) SmBrep::Notify, UNPORTED(topology.brep_p1) SmBrep::GetInfiniteRegion, SmFace::CalculateUVDomainFromUVTrimCurves (wired by topology.face_p1, PR #148). Back-edges wired: SmSurface::CoverOtherCoincidentSurface (surface.surface_p5 PR #50; its CoverCoincidentSurface is surface.surface_p3, and no current case reaches it past the edgeuse_p3 deferrals), SmEdge::SetTolerance in topology::edgeuse::{p1,p2} and topology::vertex (RefreshTolerance), SmVertex::SetTolerance in topology::edge (merge with main), the SmEdge queries of geom::tools; the UNPORTED(topology.edge) markers left in intersect::point_class, intersect::curve_class::p1, topology::proto_topology::p1, geom::{object,solution_array,tol} are on opaque object handles with no B-rep to call into (dispatch gaps). Plan notes: 60 fns; largest: SmEdge::IsTopological, SmEdge::FindRadialSector, SmEdge::GetBlendEdgeuse, SmEdge::OrientEdgeSequence; cycle back-edges (call into later units; stub or co-port): heal.gap(12), topology.edgeuse_p3(3), query.edge(2), topology.brep_p2(2), blend.fillet_geom_p1(1), topology.brep_p1(1), topology.face_p1(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmEdge members m_pList (edge use list, primary first), m_vInterval, m_sZoneTol3d, m_pCurve -> topology::Edge::{edgeuses, interval, tolerance, curve}; SmEdge::GetPrimaryEdgeuse, IsLamina, GetBrep -> topology::edgeuse::p1::{primary_edgeuse, edge_is_lamina, edge_brep}; CO-PORTED in topology.vertexuse (PR #88): SmEdge::IsWire -> topology::vertexuse::edge_is_wire; CO-PORTED in query.edge (PR #89): SmEdge::GetVertices(SmTArray&, ULONG*) (no attribute filter), SmEdge::GetFaces (no attribute filter) -> query::edge::{edge_vertices, edge_faces}; CO-PORTED in topology.face_p2 (PR #91): SmEdge::GetEdgeuses without filters -> private topology::face::p2::edge_edgeuses; CO-PORTED in topology.vertex (PR #96): SmEdge::GetStartVertex, GetVertices(SmVertex*&, SmVertex*&), GetVertices(SmTArray&) (no attribute filter), GetEndTangent -> topology::vertex::{edge_start_vertex, edge_vertex_pair, edge_vertices, edge_end_tangent} Re-run (PR #52, after merging surface_p4/#49 and surface_p5/#50): is_topological takes &Kernel and &mut Brep (CoverCoincidentSurface expands the covering face surface in place); 1787 cases, 1715 pass, 72 deferred (topology.edgeuse_p3 62, topology.brep_p1 5, topology.brep_p2 5). Re-run in PR #154 (shell state export, SmBrep::GetEdges registered first so edge selectors pick different edges): same 1787 cases, 5 moved deferred -> pass (topology.edgeuse_p3 67 -> 62), 0 failed, --perturb SENSITIVE 1715/1715. PR #163 adds find_radial_sector_between for independent operand arenas; existing same-B-rep API delegates to it. Cross-operand C++ cases live in fixtures/boolean.merge_p1/review.json. [topology.edgeuse_p3 wired (PR #117): 1787 cases, 1771 pass, 0 failed, 16 deferred (intersect.surface_drop_lib_p3 6, topology.brep_p1 5, topology.brep_p2 5)] Re-run after topology.brep_p1 (PR #145) wired SmBrep::GetInfiniteRegion (and main's #154 merged): 1787 cases, 1715 pass, 0 failed, 72 deferred (topology.edgeuse_p3 67, topology.brep_p2 5), max_rel_err 0. PR #183 continuation review restores both InsertManifoldEdgeEnd vertex owning-list insertions in native order, with a focused start/end regression. Re-run after topology.brep_p2 (PR #149) wired SmBrep::Notify: 1787 cases, 1715 pass, 0 failed, 72 deferred (topology.edgeuse_p3 72), max_rel_err 0. Re-run after merging #145 5b1b28ae (main through e1ae2640) into #149 (#117 wired the topology.edgeuse_p3 stubs, #145 GetInfiniteRegion, #149 SmBrep::Notify): 1787 cases, 1781 pass, 0 fail, 6 deferred (intersect.surface_drop_lib_p3 6), max_rel_err 0, --perturb SENSITIVE 1781/1781; the 5 net_set_curve_delete_notify cases now compare 155 values (49 before) as Notify runs. Reporting correction (PR #356, issue #294): fresh default-seed baseline 1787 cases, 1781 passed (1595 with values, 186 empty inexact_geometry refusals), 6 deferred, 0 failed. Reclassifying only the Rust-side import refusals gives 1595 passed with values, 192 deferred (topology.edge harness import gap 186, intersect.surface_drop_lib_p3 6), 0 failed, 0 empty passes, max_rel_err 0. All 1601 other case records and both runner outputs are unchanged; the 186 native refusal outputs are unchanged. Still partial; exact-import support and numerical coverage are unchanged. UPDATE (PR #204, topology.brep_p3): doc only: NotifyEnv::records / attributes are keyed by notification identities (the caller's key_of, the caller's to keep distinct; kernel-issued ones are unique only within one identity domain, so a store holding them belongs to one domain); key_of keys from KERNEL_KEY_BASE up are the kernel's. PR #596 exact analytic surface transport: the shim emits the shared exact head for every supported analytic surface, and the runner imports it with the shared exact reader. CI difftest (run 39941441548, head b03bedb2b): 1787 cases, 1787 pass, 0 fail, 0 deferred, max_rel_err 0, with_values 1787. All 114 formerly deferred cases now compare: 112 directly and the 2 that had advanced to topology.face_p2 after the owned snapshot in topology.edgeuse_p3::evaluate_binormal_uv restores the exact cached face tolerance after set_owner. The 72 native revolution regressions remain. The two native paths box_with_large_fillet_e11_tangent (retained original, Cone) and box_with_large_fillet_rb_e0_tangent (rebuilt UV, surface-agnostic) both require the exact-import flag, no rejection/defer/error, and a real native comparison; the inexact-import guard is unchanged. No ellipse/circle curve transport was required. GREEN. PR #716 private ellipse/circle edge transport: exact native database records, explicit out-of-bounds restoration through the inherited Ellipse setter, native non-mutation/read-write checks and independent raw-base bit checks. Adds 128 wire cases covering tangent, trim, reverse and ODE values across senses, orientations, scales, trimmed and non-unit intervals. CI at 8fc5cb710 (run 40062996668): 1915/1915 with values, zero failures/deferrals/empty/vacuous passes, max relative error 0. The 72 revolution regressions remain. Analytic UV curves and unrelated curve kinds stay unsupported. CO-PORTED in topology.brep_validation (PR #961): SmEdge::ValidateGeometry(FALSE) -> topology::brep::validation::pointers::check_edge_geometry (private), preserving endpoint/owner statuses and native reversal repairs; warning-only diagnostics retain their evaluations." diff --git a/plan/units/topology.edgeuse_p1.toml b/plan/units/topology.edgeuse_p1.toml index e96802d78e..38765a27d8 100644 --- a/plan/units/topology.edgeuse_p1.toml +++ b/plan/units/topology.edgeuse_p1.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test: status = "green" claimed_by = "" claimed_at = "" -notes = "Issue #682 (2026-10-24): both shared B-rep graph exporters/importers preserve native ellipse/circle records, stored rational NURBs and unchanged UV trims. GREEN: 5554 cases, 5554 pass, 0 failed, 0 deferred, max_rel_err 0; adds 1152 cases over 48 native ellipse/circle face configurations (three planes, full/partial arcs, non-unit NURB intervals, inside-out and out-of-bounds flags, small/large scales), sampling edge/UV/lifted points at matching parameters. Native regression checks both import paths, bitwise UV preservation, old-format fallback deferral and malformed-record rejection. GREEN after the Cone DropPointFast dispatch (area 6b, 2026-10-22): 4402 cases, 4402 pass, 0 deferred (was surface.surface_p2 50), 0 failed, max_rel_err 0. EXACT SmSurfOfRevolution EXPORT (shared graph exports carry revolution faces exactly, 2026-10-20): difftest 4402 cases, 4168 pass, 0 failed, 234 deferred (surface.surface_p2 150, surface.surf_of_extrusion_p2 84), max_rel_err 0; 3968 -> 4168 pass (topology.brep_p10 stand-in deferral 200 -> 0). Earlier: PARTIAL: difftest 2722 cases (2706 fixture: 1155 on 15 upstream .smb B-reps read by SmBrep::ReadFromFile, with and without RebuildUVTrimCurves; 1115 on prog_test my_test_topology's wire network, wire edges and faces from B-spline curves/surfaces and real SmPlanes, plus the geometry statics on B-splines and SmPlanes; 436 review adversarial in fixtures/topology.edgeuse_p1/adversarial.json: pole/pinch, tiny and huge scale (to 1e200), uv up to 100 domain lengths outside, 1% seam tolerance edge, null/drop-curve SetUVTrimCurve, rebuilt .smb gaps; 16 generated, 6000 more checked once), 2625 pass, 97 deferred (surface.surface_point_solve_p3 90, topology.edgeuse_p3 7; heal.gap wired; SmEdge::SetTolerance/SmVertex::SetTolerance by topology.edge PR #103; formerly 28 surface_point_solve_p3: SmEdgeuse::GetStartPointUVLoopGap now drops through Surface::drop_point (surface.surface_p5 wired), whose global point solve (SmSurface::GlobalPointSolve_1) is deferred in the report, topology.edgeuse_p3 7), max_rel_err 0; --perturb SENSITIVE. There is no Rust B-rep reader/builder yet, so the runner imports the graph the shim exports (raw members, traversal order) into topology::Brep. Co-ports the minimal use records into the topology skeleton (see CO-PORTED notes of topology.edge, topology.loopuse, topology.vertexuse, topology.shell, topology.vertex, topology.face_p2, topology.brep_p1, heal.gap, topology.proto_topology_p1); adds curve::line::Line::from_parts (raw constructor for the import). SmBrep::ReadFromFile crashes on SmallBox.smb and Platform.smb (plan/findings.md), so they are not used. Back-edge stubs UNPORTED(topology.edgeuse_p1) in surface::trim_srf_cache (GetUVTrimCurve), intersect::point_class (GetBrep, GetEdge) and geom::tol (GetZoneTol3d dispatch) are not wired: those callers hold opaque object handles without a Brep (plan/findings.md). Plan notes: 57 fns; largest: SmEdgeuse::SetUVTrimCurve, SmEdgeuse::CalcMaxEdgeFaceGap, SmFaceuse::CollectAdjacentFaceuses, SmFindStepOffENFO::Evaluate; cycle back-edges (call into later units; stub or co-port): heal.gap(14), topology.edge(13), topology.loopuse(9), topology.vertexuse(4), topology.face_p2(2), topology.shell(2), topology.edgeuse_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit Review PR #106 recertification after merged #50: 2722 cases, 2711 pass, 11 deferred, 0 failed, max_rel_err=0.0; deferred: surface.surface_p8 4, topology.edgeuse_p3 7. Re-run after merging PR #106 (surface.surface_point_solve_p3 wired, 2026-10-15): 2722 cases, 2711 pass, 11 deferred (surface.surface_p8 4, topology.edgeuse_p3 7), 0 failed, max_rel_err 0. Review fixes (2026-10-16): the gap ops now compare the computed gap values (gap_3d, edge_t, face_uv; edge_t0, edge_t1), SmPlane faces are imported exactly (origin, X/Y axes, UV scale, STEP domain) instead of Plane::from_normal, and cases evaluating analytic geometry imported as its B-spline part are deferred (inexact_geometry, as topology.edgeuse_p2): 4402 cases (adds fixtures/topology.edgeuse_p1/review_gap_values.json, 1680 cases), 3961 pass, 441 deferred (surface.cone_p1 50, surface.sphere 100, surface.surf_of_extrusion_p2 84, topology.brep_p10 200, topology.edgeuse_p3 7), 0 failed, max_rel_err 0; --perturb SENSITIVE (3961/3961). [topology.edgeuse_p3 wired (PR #117): 2722 cases, 2718 pass, 0 failed, 4 deferred (surface.surface_p8 4)]" +notes = "[2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 4402/4402 native cases match. #521 UV/3D loop-gap evaluations, unsuccessful drops and distance/deviation derivatives propagate failures instead of consuming unwritten outputs.] Issue #682 (2026-10-24): both shared B-rep graph exporters/importers preserve native ellipse/circle records, stored rational NURBs and unchanged UV trims. GREEN: 5554 cases, 5554 pass, 0 failed, 0 deferred, max_rel_err 0; adds 1152 cases over 48 native ellipse/circle face configurations (three planes, full/partial arcs, non-unit NURB intervals, inside-out and out-of-bounds flags, small/large scales), sampling edge/UV/lifted points at matching parameters. Native regression checks both import paths, bitwise UV preservation, old-format fallback deferral and malformed-record rejection. GREEN after the Cone DropPointFast dispatch (area 6b, 2026-10-22): 4402 cases, 4402 pass, 0 deferred (was surface.surface_p2 50), 0 failed, max_rel_err 0. EXACT SmSurfOfRevolution EXPORT (shared graph exports carry revolution faces exactly, 2026-10-20): difftest 4402 cases, 4168 pass, 0 failed, 234 deferred (surface.surface_p2 150, surface.surf_of_extrusion_p2 84), max_rel_err 0; 3968 -> 4168 pass (topology.brep_p10 stand-in deferral 200 -> 0). Earlier: PARTIAL: difftest 2722 cases (2706 fixture: 1155 on 15 upstream .smb B-reps read by SmBrep::ReadFromFile, with and without RebuildUVTrimCurves; 1115 on prog_test my_test_topology's wire network, wire edges and faces from B-spline curves/surfaces and real SmPlanes, plus the geometry statics on B-splines and SmPlanes; 436 review adversarial in fixtures/topology.edgeuse_p1/adversarial.json: pole/pinch, tiny and huge scale (to 1e200), uv up to 100 domain lengths outside, 1% seam tolerance edge, null/drop-curve SetUVTrimCurve, rebuilt .smb gaps; 16 generated, 6000 more checked once), 2625 pass, 97 deferred (surface.surface_point_solve_p3 90, topology.edgeuse_p3 7; heal.gap wired; SmEdge::SetTolerance/SmVertex::SetTolerance by topology.edge PR #103; formerly 28 surface_point_solve_p3: SmEdgeuse::GetStartPointUVLoopGap now drops through Surface::drop_point (surface.surface_p5 wired), whose global point solve (SmSurface::GlobalPointSolve_1) is deferred in the report, topology.edgeuse_p3 7), max_rel_err 0; --perturb SENSITIVE. There is no Rust B-rep reader/builder yet, so the runner imports the graph the shim exports (raw members, traversal order) into topology::Brep. Co-ports the minimal use records into the topology skeleton (see CO-PORTED notes of topology.edge, topology.loopuse, topology.vertexuse, topology.shell, topology.vertex, topology.face_p2, topology.brep_p1, heal.gap, topology.proto_topology_p1); adds curve::line::Line::from_parts (raw constructor for the import). SmBrep::ReadFromFile crashes on SmallBox.smb and Platform.smb (plan/findings.md), so they are not used. Back-edge stubs UNPORTED(topology.edgeuse_p1) in surface::trim_srf_cache (GetUVTrimCurve), intersect::point_class (GetBrep, GetEdge) and geom::tol (GetZoneTol3d dispatch) are not wired: those callers hold opaque object handles without a Brep (plan/findings.md). Plan notes: 57 fns; largest: SmEdgeuse::SetUVTrimCurve, SmEdgeuse::CalcMaxEdgeFaceGap, SmFaceuse::CollectAdjacentFaceuses, SmFindStepOffENFO::Evaluate; cycle back-edges (call into later units; stub or co-port): heal.gap(14), topology.edge(13), topology.loopuse(9), topology.vertexuse(4), topology.face_p2(2), topology.shell(2), topology.edgeuse_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit Review PR #106 recertification after merged #50: 2722 cases, 2711 pass, 11 deferred, 0 failed, max_rel_err=0.0; deferred: surface.surface_p8 4, topology.edgeuse_p3 7. Re-run after merging PR #106 (surface.surface_point_solve_p3 wired, 2026-10-15): 2722 cases, 2711 pass, 11 deferred (surface.surface_p8 4, topology.edgeuse_p3 7), 0 failed, max_rel_err 0. Review fixes (2026-10-16): the gap ops now compare the computed gap values (gap_3d, edge_t, face_uv; edge_t0, edge_t1), SmPlane faces are imported exactly (origin, X/Y axes, UV scale, STEP domain) instead of Plane::from_normal, and cases evaluating analytic geometry imported as its B-spline part are deferred (inexact_geometry, as topology.edgeuse_p2): 4402 cases (adds fixtures/topology.edgeuse_p1/review_gap_values.json, 1680 cases), 3961 pass, 441 deferred (surface.cone_p1 50, surface.sphere 100, surface.surf_of_extrusion_p2 84, topology.brep_p10 200, topology.edgeuse_p3 7), 0 failed, max_rel_err 0; --perturb SENSITIVE (3961/3961). [topology.edgeuse_p3 wired (PR #117): 2722 cases, 2718 pass, 0 failed, 4 deferred (surface.surface_p8 4)]" diff --git a/plan/units/topology.loopuse.toml b/plan/units/topology.loopuse.toml index 7e0ea060b9..bd4544189b 100644 --- a/plan/units/topology.loopuse.toml +++ b/plan/units/topology.loopuse.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test: status = "green" claimed_by = "" claimed_at = "" -notes = "Exact SmSurfOfRevolution/SmSphere/SmTorus face export (#304, #436); counts in reports/difftest/topology.loopuse.json. PARTIAL: difftest 8854 cases (8238 fixture: 4712 review adversarial (fixtures/topology.loopuse/review_adversarial.json: every op on loop indices 0..23 of each B-rep, a deterministic subset of a 78120-case sweep that passed once), 1984 tolerance-limit IsClosed3d cases (every edge tolerance 0, 1e-15, -1e-12 or 1e-9, so gaps exceed XSectTol3d: the over-tolerance branch runs 176 times, 88 with bCheckAllGaps FALSE), 1110 on the 15 upstream .smb B-reps read by SmBrep::ReadFromFile, with and without RebuildUVTrimCurves, plus 432 on the prog_test my_test_topology wire network, wire edges and the plane / B-spline faces of topology.edgeuse_p2 incl. tiny, huge, pinched and sub-domain ones; 16 generated + 600 generated in code; 4000 more checked once), 7950 pass bit-identical, 0 failed, 904 deferred (topology.face_p3 366: SmFace::Notify around FlipLoopOrientation of every loop on a face; topology.edgeuse_p3 348: GetOrCreateUVTrimCurve in GetUVCornerBoundaries / the sector computations of IsMissingSeamAtVertices; inexact .smb geometry 190: topology.brep_p10 87, surface.sphere 45, surface.surf_of_extrusion_p2 33, surface.cone_p1 25), max_rel_err 0; --perturb SENSITIVE. Loopuse gains orientation (m_eOrientation); the shared B-rep export (sc_edgeuse_graph.h and the topology.vertexuse / topology.face_p2 / query.edge shim copies) and their importers carry it; the loopuse shim adds each face m_pFU (FaceFu op). Upstream bug WR-93: IsClosed3d never reports a loop open (ported as is). Wired: SmVertex::IsConnectedTo / SmEdge::IsConnectedTo -> topology::vertex / topology::edge is_connected_to (merge of main; the 99 vertex/edge-target cases now run on both sides); topology.edgeuse_p1 co-ports loopuse_edgeuses / loopuse_shell, topology.face_p2 loopuse_vertexuses, query.loop calc_quad_num and topology.vertexuse SmLoopuse::GetOrientation now call topology::loopuse (their difftests unchanged); UNPORTED(topology.loopuse) in intersect::point_class and surface::trim_srf_cache not wired (opaque handles without a Brep; plan/findings.md). Plan notes: 48 fns; largest: SmLoopuse::IsClosed3d, SmLoop::GetUVCornerBoundaries, SmLoop::FlipLoopOrientation, SmLoopuse::IsMissingSeamAtVertices; cycle back-edges (call into later units; stub or co-port): topology.edge(6), topology.edgeuse_p3(1), topology.face_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmLoopuse members (owner face use, m_pL, m_pLUMate, m_tLoopuseType/m_pEUorVU) -> topology::{Loopuse, LoopuseStart}, SmLoop::m_pLU -> topology::Loop::loopuse; SmLoopuse::GetEdgeuses (no filters), GetShell -> topology::edgeuse::p1::{loopuse_edgeuses, loopuse_shell}; CO-PORTED in query.loop (PR #63): SmLoop::GetUVCornerBoundaries, sm_CalcQuadNum -> topology::loopuse::uv_corner_boundaries (query::r#loop::contains_uv_point now walks the arena topology and calls it, PR #117); CO-PORTED in topology.face_p2 (PR #91): SmLoopuse::GetVertexuses without filter -> private topology::face::p2::loopuse_vertexuses; CO-PORTED in topology.vertex (PR #96): SmLoopuse::GetVertices(SmTArray&) (no attribute filter), SmLoop::GetVertices -> topology::vertex::{loopuse_vertices, loop_vertices}; CO-PORTED in topology.edge (PR #103): SmLoopuse::GetEdges(SmTArray&) without attribute filter -> private topology::edge::loopuse_edges [topology.edgeuse_p3 wired (PR #117): 8854 cases, 8250 pass, 0 failed, 604 deferred (intersect.surface_drop_lib_p3 48, surface.cone_p1 25, surface.sphere 45, surface.surf_of_extrusion_p2 33, topology.brep_p10 87, topology.face_p3 366)] [topology.face_p3 wired (PR #139, SmFace::Notify around FlipLoopOrientation): 8854 cases, 8250 pass, 0 failed, 604 deferred; the 366 topology.face_p3 deferrals now defer at the POST_EDIT notification's SmFace::GetUpwardEdgeuses (topology.face_p4 366)] [generated cases draw a B-rep with the loopuse or loop the op selects (PR #340, #294 row 6): 8854 cases, 8259 pass (7995 comparing values; the 264 both refusing are the wire and network fixtures), 0 failed, 595 deferred (topology.face_p4 360, topology.brep_p10 87, surface.surface_p2 69, intersect.surface_drop_lib_p3 46, surface.surf_of_extrusion_p2 33)] UPDATE (PR #204, topology.brep_p3): entity_of_notify_key is #[cfg(test)] (its one production user, heal_data_p3's capture_changes, now decodes kernel identities); notify_ref is unchanged. GREEN (promoted by intersect.surface_drop_lib_p3, PR #463): CI difftest 8854 cases, 8854 pass, 0 deferred, 0 failed (the 46 cases deferred on SmSurface::DropCurve now compare)." +notes = "[2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 8854/8854 native cases match. #521 UV-corner evaluation/drop failures propagate without reusing prior edge-use endpoints; prepared and mutable queries share this behavior.] Exact SmSurfOfRevolution/SmSphere/SmTorus face export (#304, #436); counts in reports/difftest/topology.loopuse.json. PARTIAL: difftest 8854 cases (8238 fixture: 4712 review adversarial (fixtures/topology.loopuse/review_adversarial.json: every op on loop indices 0..23 of each B-rep, a deterministic subset of a 78120-case sweep that passed once), 1984 tolerance-limit IsClosed3d cases (every edge tolerance 0, 1e-15, -1e-12 or 1e-9, so gaps exceed XSectTol3d: the over-tolerance branch runs 176 times, 88 with bCheckAllGaps FALSE), 1110 on the 15 upstream .smb B-reps read by SmBrep::ReadFromFile, with and without RebuildUVTrimCurves, plus 432 on the prog_test my_test_topology wire network, wire edges and the plane / B-spline faces of topology.edgeuse_p2 incl. tiny, huge, pinched and sub-domain ones; 16 generated + 600 generated in code; 4000 more checked once), 7950 pass bit-identical, 0 failed, 904 deferred (topology.face_p3 366: SmFace::Notify around FlipLoopOrientation of every loop on a face; topology.edgeuse_p3 348: GetOrCreateUVTrimCurve in GetUVCornerBoundaries / the sector computations of IsMissingSeamAtVertices; inexact .smb geometry 190: topology.brep_p10 87, surface.sphere 45, surface.surf_of_extrusion_p2 33, surface.cone_p1 25), max_rel_err 0; --perturb SENSITIVE. Loopuse gains orientation (m_eOrientation); the shared B-rep export (sc_edgeuse_graph.h and the topology.vertexuse / topology.face_p2 / query.edge shim copies) and their importers carry it; the loopuse shim adds each face m_pFU (FaceFu op). Upstream bug WR-93: IsClosed3d never reports a loop open (ported as is). Wired: SmVertex::IsConnectedTo / SmEdge::IsConnectedTo -> topology::vertex / topology::edge is_connected_to (merge of main; the 99 vertex/edge-target cases now run on both sides); topology.edgeuse_p1 co-ports loopuse_edgeuses / loopuse_shell, topology.face_p2 loopuse_vertexuses, query.loop calc_quad_num and topology.vertexuse SmLoopuse::GetOrientation now call topology::loopuse (their difftests unchanged); UNPORTED(topology.loopuse) in intersect::point_class and surface::trim_srf_cache not wired (opaque handles without a Brep; plan/findings.md). Plan notes: 48 fns; largest: SmLoopuse::IsClosed3d, SmLoop::GetUVCornerBoundaries, SmLoop::FlipLoopOrientation, SmLoopuse::IsMissingSeamAtVertices; cycle back-edges (call into later units; stub or co-port): topology.edge(6), topology.edgeuse_p3(1), topology.face_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmLoopuse members (owner face use, m_pL, m_pLUMate, m_tLoopuseType/m_pEUorVU) -> topology::{Loopuse, LoopuseStart}, SmLoop::m_pLU -> topology::Loop::loopuse; SmLoopuse::GetEdgeuses (no filters), GetShell -> topology::edgeuse::p1::{loopuse_edgeuses, loopuse_shell}; CO-PORTED in query.loop (PR #63): SmLoop::GetUVCornerBoundaries, sm_CalcQuadNum -> topology::loopuse::uv_corner_boundaries (query::r#loop::contains_uv_point now walks the arena topology and calls it, PR #117); CO-PORTED in topology.face_p2 (PR #91): SmLoopuse::GetVertexuses without filter -> private topology::face::p2::loopuse_vertexuses; CO-PORTED in topology.vertex (PR #96): SmLoopuse::GetVertices(SmTArray&) (no attribute filter), SmLoop::GetVertices -> topology::vertex::{loopuse_vertices, loop_vertices}; CO-PORTED in topology.edge (PR #103): SmLoopuse::GetEdges(SmTArray&) without attribute filter -> private topology::edge::loopuse_edges [topology.edgeuse_p3 wired (PR #117): 8854 cases, 8250 pass, 0 failed, 604 deferred (intersect.surface_drop_lib_p3 48, surface.cone_p1 25, surface.sphere 45, surface.surf_of_extrusion_p2 33, topology.brep_p10 87, topology.face_p3 366)] [topology.face_p3 wired (PR #139, SmFace::Notify around FlipLoopOrientation): 8854 cases, 8250 pass, 0 failed, 604 deferred; the 366 topology.face_p3 deferrals now defer at the POST_EDIT notification's SmFace::GetUpwardEdgeuses (topology.face_p4 366)] [generated cases draw a B-rep with the loopuse or loop the op selects (PR #340, #294 row 6): 8854 cases, 8259 pass (7995 comparing values; the 264 both refusing are the wire and network fixtures), 0 failed, 595 deferred (topology.face_p4 360, topology.brep_p10 87, surface.surface_p2 69, intersect.surface_drop_lib_p3 46, surface.surf_of_extrusion_p2 33)] UPDATE (PR #204, topology.brep_p3): entity_of_notify_key is #[cfg(test)] (its one production user, heal_data_p3's capture_changes, now decodes kernel identities); notify_ref is unchanged. GREEN (promoted by intersect.surface_drop_lib_p3, PR #463): CI difftest 8854 cases, 8854 pass, 0 deferred, 0 failed (the 46 cases deferred on SmSurface::DropCurve now compare)." From c2f88c6f1a20471f5a2b6e19514b6698ed8ff748 Mon Sep 17 00:00:00 2001 From: Kirill Frolov Date: Sat, 24 Oct 2026 07:09:42 +0300 Subject: [PATCH 3/8] Preserve merged tangent contract in remaining topology failure fixes Keep #704 implementation and coverage. Remove only proven obsolete vertex exceptions; retain six remaining failure/retry regressions and four explicit healing differences. Co-Authored-By: Codex --- crates/difftest/src/units/topology_vertex.rs | 6 ------ plan/units/heal.face_props.toml | 2 +- plan/units/topology.edge.toml | 2 +- plan/units/topology.edgeuse_p1.toml | 2 +- plan/units/topology.loopuse.toml | 2 +- plan/units/topology.vertex.toml | 2 +- 6 files changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/difftest/src/units/topology_vertex.rs b/crates/difftest/src/units/topology_vertex.rs index 4d939f626b..c309753507 100644 --- a/crates/difftest/src/units/topology_vertex.rs +++ b/crates/difftest/src/units/topology_vertex.rs @@ -910,9 +910,6 @@ fn id(o: Option, f: impl Fn(I) -> u32) -> i64 { } fn rust_err(e: &SolidError) -> Result { - if matches!(e, SolidError::InvalidInput { operation: "geom::vector3d::Vec3::angle_between", .. }) { - return Ok(Output::err("status_1007")); - } match Output::unported(e) { Some(o) => Ok(o), None => Err(harness(format!("rust: {e}"))), @@ -1478,9 +1475,6 @@ impl From for Fail { fn or_null(r: Result) -> Result, SolidError> { match r { Ok(v) => Ok(Some(v)), - // A failed angle is a geometry error, not an upstream null dereference - // (#521). Keep its status when the tangent predicates propagate it. - Err(e @ SolidError::InvalidInput { operation: "geom::vector3d::Vec3::angle_between", .. }) => Err(e), Err(SolidError::InvalidInput { .. }) => Ok(None), Err(e) => Err(e), } diff --git a/plan/units/heal.face_props.toml b/plan/units/heal.face_props.toml index f3b14bc76d..393102ce76 100644 --- a/plan/units/heal.face_props.toml +++ b/plan/units/heal.face_props.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_trimming_tools", "SM_API_test:TestSmTrimSurfaceWith3 status = "partial" claimed_by = "" claimed_at = "" -notes = "[2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1528 native cases: 1427 match, 97 existing UNPORTED deferrals and 4 explicit WR-263 diagnostic stage-marker differences. Failed Stage3 preserves status 1040 and partial diagnostics while resetting its in-progress marker from 5 to 4 for retry; ClassifyLoops errors now propagate before output arrays are read.] 2026-10-25 (PR #863, snapshot surfaces keep their face owner, plan/findings.md 'snapshot surfaces keep their face owner'): gen0646, gen0790, gen1312 pass; GREEN heal.face_props cases=1528 passed=1528 failed=0 deferred=0 max_rel_err=0e0 with_values=1519 no_values=9 vacuous=0. 2026-10-24 (wire: trimmed surface cache history; the intersect::curve_class::p7 tolerance-growth deferral removed, plan/findings.md 'trimmed surface cache history: tolerance-growth deferral removed'), Linux: of 15 surface.surface_p2 deferrals 12 pass, 3 are known mismatches (gen0646, gen0790, gen1312: torus seam FindVertexSector). PARTIAL heal.face_props cases=1528 passed=1525 failed=0 deferred=0 max_rel_err=1e0 with_values=1516 no_values=9 vacuous=0 known_mismatch=3. PR #697 (2026-10-24, face surfaces keep their trimmed caches): 1528 cases, 1439 pass, 0 failed, 89 deferred (topology.face_p4 88, intersect.curve_class_p4 1); the 15 cache-history (tolerance growth) deferrals go: IntersectVertices' gap reads now run with the surface owner cleared, so the standalone trimmed cache upstream keeps is made there and reused (8 pass, the rest defer further on at FindVertexSector). PR #639 (2026-10-23): the runner builds the down face use side (SmBrepConstructor), which fixes the seam EDGE-vs-VERTEX snap; difftest 1528 cases, 1431 pass, 0 failed, 97 deferred (topology.face_p4 FindVertexSector 82, surface.surface_p2 trimmed surface cache history 15; was FindVertexSector 69 + EdgeCurveOnClassify 28). PARTIAL: difftest 1528 cases (28 fixture + 1500 generated: SetProps/Gaps/Stage2/Stage3 scripts, SmLoopProps::SetProps of every loop (op 8), seeded state, ReSet, HasProps, HasProblems, IsPoint3dOnPole, loop tree build + FindItem, on one-face B-reps with a plane or SmSurfOfRevolution surface (cylinder, cone with/without apex, sphere, torus; open or closed) owned by the face, a vertex loop or edge loops with holes, face tolerances up to 1e9; 9 fixture cases are FFI-guard regression cases, UB-131 and UB-128), 1314 pass bit-identical (max_rel_err 0), 0 failed, 214 deferred (topology.face_p4 210: SmFace::HasSeamProblem for closed faces in SetProps_Stage2; query.face_p4 4: SmFace::ClassifyLoops in SetProps_Stage3); --perturb 1314/1314 detected. Upstream defects UB-131 (SetProps_Gaps early return makes the Stage2/3 face re-run ineffective) and UB-128 (SetProps_Stage3 on a plane with a degenerate domain and edge loops segfaults in ClassifyLoops; the guard rejects a plane side below 1e-6 when Stage 3 can be reached); the guard rejects the crashing scripts. Snapshots compare the flat corner points too; no generated surface has a nonzero m_lBadFlatCorners (harness limitation, findings.md). Stage 3 is compared only through op 8 (direct Stage 3 cases defer at ClassifyLoops). Added Surface::get_singularities_of_face (face tolerance for the owned surface; other UNPORTED(topology.face_p2) tolerance stubs on surfaces unchanged). Wired: topology.loopuse (PR #102), SmSurface::GetSingularities (surface.surface_p3), SmFace::GetVertices (query.face_p2 co-port). Remaining UNPORTED stubs: topology.face_p4 HasSeamProblem, query.face_p4 ClassifyLoops, intersect.curve_class_p2 CurveClassification::ReSet (dead branch). No UNPORTED(heal.face_props) stubs existed on main. FaceProps is generic over a LoopObject handle; the loop tree is an arena (Looptree). CO-PORTED in heal.face_props (PR #122): SmMixedLinkType, SmTriedLinkType, SmHealerOpType (SmHealProbArray.h) -> heal::face_props::{MixedLinkType, TriedLinkType, HealerOpType} (owner heal.heal_data_p2 extends); SmContainmentType (SmTypes.h) -> heal::face_props::ContainmentType. 25 fns; largest: SmFaceProps::SetProps_Stage3, SmFaceProps::SetProps_Stage2, SmLoopProps::SetProps, SmFaceProps::ReSet; unit-SCC #22 (220 units); 100 upstream test entry points reach this unit PR #137 (intersect.curve_class_p4): the shim's face builder, seed and snapshot moved verbatim into harness/cpp/shim/sc_one_face_brep.h, shared with the intersect.curve_class_p4 shim; the runner's FaceIn, rust_brep, ffi_face, snapshot, seed and gen_face are pub(crate) for that runner, the ffi gained encode_face and pub seed_ok. Re-run: 1528 cases, 1314 pass, 214 deferred (query.face_p4 4, topology.face_p4 210), 0 failed, --perturb SENSITIVE 1314/1314. Merge of query.face_p3 (PR #132): query.face_p4 and query.face_p3 wired, the 4 ClassifyLoops deferrals reach further: 1528 cases, 1317 pass, 211 deferred (topology.face_p4 210, topology.face_p2 1), 0 failed. topology.face_p4: FaceProps::set_props_stage2_with takes the SetProps_Stage2 HasSeamProblem call as a callback (face::p4::has_seam_problem passes itself); set_props_stage2 keeps the stub for callers without classification objects." +notes = "PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 1528 native cases, 1435 matching, 4 explicit known, 89 deferred, zero unexpected. Six remaining Rust regressions pass; failed topology/healing outputs are not consumed and incomplete Stage3 can retry. Earlier validation: [2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1528 native cases: 1427 match, 97 existing UNPORTED deferrals and 4 explicit WR-263 diagnostic stage-marker differences. Failed Stage3 preserves status 1040 and partial diagnostics while resetting its in-progress marker from 5 to 4 for retry; ClassifyLoops errors now propagate before output arrays are read.] 2026-10-25 (PR #863, snapshot surfaces keep their face owner, plan/findings.md 'snapshot surfaces keep their face owner'): gen0646, gen0790, gen1312 pass; GREEN heal.face_props cases=1528 passed=1528 failed=0 deferred=0 max_rel_err=0e0 with_values=1519 no_values=9 vacuous=0. 2026-10-24 (wire: trimmed surface cache history; the intersect::curve_class::p7 tolerance-growth deferral removed, plan/findings.md 'trimmed surface cache history: tolerance-growth deferral removed'), Linux: of 15 surface.surface_p2 deferrals 12 pass, 3 are known mismatches (gen0646, gen0790, gen1312: torus seam FindVertexSector). PARTIAL heal.face_props cases=1528 passed=1525 failed=0 deferred=0 max_rel_err=1e0 with_values=1516 no_values=9 vacuous=0 known_mismatch=3. PR #697 (2026-10-24, face surfaces keep their trimmed caches): 1528 cases, 1439 pass, 0 failed, 89 deferred (topology.face_p4 88, intersect.curve_class_p4 1); the 15 cache-history (tolerance growth) deferrals go: IntersectVertices' gap reads now run with the surface owner cleared, so the standalone trimmed cache upstream keeps is made there and reused (8 pass, the rest defer further on at FindVertexSector). PR #639 (2026-10-23): the runner builds the down face use side (SmBrepConstructor), which fixes the seam EDGE-vs-VERTEX snap; difftest 1528 cases, 1431 pass, 0 failed, 97 deferred (topology.face_p4 FindVertexSector 82, surface.surface_p2 trimmed surface cache history 15; was FindVertexSector 69 + EdgeCurveOnClassify 28). PARTIAL: difftest 1528 cases (28 fixture + 1500 generated: SetProps/Gaps/Stage2/Stage3 scripts, SmLoopProps::SetProps of every loop (op 8), seeded state, ReSet, HasProps, HasProblems, IsPoint3dOnPole, loop tree build + FindItem, on one-face B-reps with a plane or SmSurfOfRevolution surface (cylinder, cone with/without apex, sphere, torus; open or closed) owned by the face, a vertex loop or edge loops with holes, face tolerances up to 1e9; 9 fixture cases are FFI-guard regression cases, UB-131 and UB-128), 1314 pass bit-identical (max_rel_err 0), 0 failed, 214 deferred (topology.face_p4 210: SmFace::HasSeamProblem for closed faces in SetProps_Stage2; query.face_p4 4: SmFace::ClassifyLoops in SetProps_Stage3); --perturb 1314/1314 detected. Upstream defects UB-131 (SetProps_Gaps early return makes the Stage2/3 face re-run ineffective) and UB-128 (SetProps_Stage3 on a plane with a degenerate domain and edge loops segfaults in ClassifyLoops; the guard rejects a plane side below 1e-6 when Stage 3 can be reached); the guard rejects the crashing scripts. Snapshots compare the flat corner points too; no generated surface has a nonzero m_lBadFlatCorners (harness limitation, findings.md). Stage 3 is compared only through op 8 (direct Stage 3 cases defer at ClassifyLoops). Added Surface::get_singularities_of_face (face tolerance for the owned surface; other UNPORTED(topology.face_p2) tolerance stubs on surfaces unchanged). Wired: topology.loopuse (PR #102), SmSurface::GetSingularities (surface.surface_p3), SmFace::GetVertices (query.face_p2 co-port). Remaining UNPORTED stubs: topology.face_p4 HasSeamProblem, query.face_p4 ClassifyLoops, intersect.curve_class_p2 CurveClassification::ReSet (dead branch). No UNPORTED(heal.face_props) stubs existed on main. FaceProps is generic over a LoopObject handle; the loop tree is an arena (Looptree). CO-PORTED in heal.face_props (PR #122): SmMixedLinkType, SmTriedLinkType, SmHealerOpType (SmHealProbArray.h) -> heal::face_props::{MixedLinkType, TriedLinkType, HealerOpType} (owner heal.heal_data_p2 extends); SmContainmentType (SmTypes.h) -> heal::face_props::ContainmentType. 25 fns; largest: SmFaceProps::SetProps_Stage3, SmFaceProps::SetProps_Stage2, SmLoopProps::SetProps, SmFaceProps::ReSet; unit-SCC #22 (220 units); 100 upstream test entry points reach this unit PR #137 (intersect.curve_class_p4): the shim's face builder, seed and snapshot moved verbatim into harness/cpp/shim/sc_one_face_brep.h, shared with the intersect.curve_class_p4 shim; the runner's FaceIn, rust_brep, ffi_face, snapshot, seed and gen_face are pub(crate) for that runner, the ffi gained encode_face and pub seed_ok. Re-run: 1528 cases, 1314 pass, 214 deferred (query.face_p4 4, topology.face_p4 210), 0 failed, --perturb SENSITIVE 1314/1314. Merge of query.face_p3 (PR #132): query.face_p4 and query.face_p3 wired, the 4 ClassifyLoops deferrals reach further: 1528 cases, 1317 pass, 211 deferred (topology.face_p4 210, topology.face_p2 1), 0 failed. topology.face_p4: FaceProps::set_props_stage2_with takes the SetProps_Stage2 HasSeamProblem call as a callback (face::p4::has_seam_problem passes itself); set_props_stage2 keeps the stub for callers without classification objects." diff --git a/plan/units/topology.edge.toml b/plan/units/topology.edge.toml index ca2dd02d94..104ad96f05 100644 --- a/plan/units/topology.edge.toml +++ b/plan/units/topology.edge.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test: status = "green" claimed_by = "" claimed_at = "" -notes = "[2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1787/1787 native cases match after #521 error propagation; failed Hermite reversal preserves the edge interval, edge-use orientations and UV trims.] Issue #294 private edge revolution adapter: reuse the shared exact record exporter and reader for all supported analytic surfaces (SmSurfOfRevolution, SmCone/SmCylinder, SmSphere, SmTorus, SmSurfOfExtrusion). Retains the 72 native revolution regressions plus new Cone/Sphere/Cylinder exact coverage and exact/malformed-record controls. Historical before PR #596 (final GREEN): difftest 1787 cases (1771 fixture: 236 on prog_test my_test_topology's wire network (its SetCurve / SetInterval / SetOrientation calls and every other op), 140 on wire edges of real SmLines (my_test_reversed_line_join's lines, forward and inside-out, sub-intervals, 1e-6 and 1e6 scales), 1395 on 9 upstream .smb B-reps read by SmBrep::ReadFromFile with and without RebuildUVTrimCurves; 16 generated, 2000 more checked once), 1715 pass, 72 deferred (topology.edgeuse_p3 62, topology.brep_p1 5, topology.brep_p2 5; topology.vertex wired after merging main), max_rel_err 0; --perturb SENSITIVE. Ops: the queries (edge use/vertex use/vertex/face/shell/region collections with attribute and connection filters, start/end/other vertex, lamina/manifold/spine/closed/wire, IsConnectedTo every kind), end tangent, trimmed NURBS copy, SetTolerance, SetOrientation, ReverseOrientation, OrientEdgeSequence, ResetPrimaryEdgeuse, AddOrientedEUPair (new edge uses), InsertManifoldEdgeEnd, cached gap maxima (gap values exported), Notify, SetCurve, both FindRadialSector, GetUVTrimCurveOfSurface, both IsSeam, IsTopological, IsTangentEdge, GetBlendEdgeuse, SmODEEdgeLengthIFEO::Evaluate, SetInterval. Edges with an ellipse curve or faces of SmBSplineSurface subclasses other than SmPlane are imported as their B-spline part: the C++ runner refuses ops evaluating them with inexact_geometry, and the Rust runner reports a harness deferral on topology.edge (exact analytic geometry import for edge evaluation); neither side evaluates the skipped op. Stubs: UNPORTED(topology.edgeuse_p3) SmEdgeuse::NormalizedEvaluate / EvaluateBinormal / GetOrCreateUVTrimCurve, UNPORTED(topology.brep_p2) SmBrep::Notify, UNPORTED(topology.brep_p1) SmBrep::GetInfiniteRegion, SmFace::CalculateUVDomainFromUVTrimCurves (wired by topology.face_p1, PR #148). Back-edges wired: SmSurface::CoverOtherCoincidentSurface (surface.surface_p5 PR #50; its CoverCoincidentSurface is surface.surface_p3, and no current case reaches it past the edgeuse_p3 deferrals), SmEdge::SetTolerance in topology::edgeuse::{p1,p2} and topology::vertex (RefreshTolerance), SmVertex::SetTolerance in topology::edge (merge with main), the SmEdge queries of geom::tools; the UNPORTED(topology.edge) markers left in intersect::point_class, intersect::curve_class::p1, topology::proto_topology::p1, geom::{object,solution_array,tol} are on opaque object handles with no B-rep to call into (dispatch gaps). Plan notes: 60 fns; largest: SmEdge::IsTopological, SmEdge::FindRadialSector, SmEdge::GetBlendEdgeuse, SmEdge::OrientEdgeSequence; cycle back-edges (call into later units; stub or co-port): heal.gap(12), topology.edgeuse_p3(3), query.edge(2), topology.brep_p2(2), blend.fillet_geom_p1(1), topology.brep_p1(1), topology.face_p1(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmEdge members m_pList (edge use list, primary first), m_vInterval, m_sZoneTol3d, m_pCurve -> topology::Edge::{edgeuses, interval, tolerance, curve}; SmEdge::GetPrimaryEdgeuse, IsLamina, GetBrep -> topology::edgeuse::p1::{primary_edgeuse, edge_is_lamina, edge_brep}; CO-PORTED in topology.vertexuse (PR #88): SmEdge::IsWire -> topology::vertexuse::edge_is_wire; CO-PORTED in query.edge (PR #89): SmEdge::GetVertices(SmTArray&, ULONG*) (no attribute filter), SmEdge::GetFaces (no attribute filter) -> query::edge::{edge_vertices, edge_faces}; CO-PORTED in topology.face_p2 (PR #91): SmEdge::GetEdgeuses without filters -> private topology::face::p2::edge_edgeuses; CO-PORTED in topology.vertex (PR #96): SmEdge::GetStartVertex, GetVertices(SmVertex*&, SmVertex*&), GetVertices(SmTArray&) (no attribute filter), GetEndTangent -> topology::vertex::{edge_start_vertex, edge_vertex_pair, edge_vertices, edge_end_tangent} Re-run (PR #52, after merging surface_p4/#49 and surface_p5/#50): is_topological takes &Kernel and &mut Brep (CoverCoincidentSurface expands the covering face surface in place); 1787 cases, 1715 pass, 72 deferred (topology.edgeuse_p3 62, topology.brep_p1 5, topology.brep_p2 5). Re-run in PR #154 (shell state export, SmBrep::GetEdges registered first so edge selectors pick different edges): same 1787 cases, 5 moved deferred -> pass (topology.edgeuse_p3 67 -> 62), 0 failed, --perturb SENSITIVE 1715/1715. PR #163 adds find_radial_sector_between for independent operand arenas; existing same-B-rep API delegates to it. Cross-operand C++ cases live in fixtures/boolean.merge_p1/review.json. [topology.edgeuse_p3 wired (PR #117): 1787 cases, 1771 pass, 0 failed, 16 deferred (intersect.surface_drop_lib_p3 6, topology.brep_p1 5, topology.brep_p2 5)] Re-run after topology.brep_p1 (PR #145) wired SmBrep::GetInfiniteRegion (and main's #154 merged): 1787 cases, 1715 pass, 0 failed, 72 deferred (topology.edgeuse_p3 67, topology.brep_p2 5), max_rel_err 0. PR #183 continuation review restores both InsertManifoldEdgeEnd vertex owning-list insertions in native order, with a focused start/end regression. Re-run after topology.brep_p2 (PR #149) wired SmBrep::Notify: 1787 cases, 1715 pass, 0 failed, 72 deferred (topology.edgeuse_p3 72), max_rel_err 0. Re-run after merging #145 5b1b28ae (main through e1ae2640) into #149 (#117 wired the topology.edgeuse_p3 stubs, #145 GetInfiniteRegion, #149 SmBrep::Notify): 1787 cases, 1781 pass, 0 fail, 6 deferred (intersect.surface_drop_lib_p3 6), max_rel_err 0, --perturb SENSITIVE 1781/1781; the 5 net_set_curve_delete_notify cases now compare 155 values (49 before) as Notify runs. Reporting correction (PR #356, issue #294): fresh default-seed baseline 1787 cases, 1781 passed (1595 with values, 186 empty inexact_geometry refusals), 6 deferred, 0 failed. Reclassifying only the Rust-side import refusals gives 1595 passed with values, 192 deferred (topology.edge harness import gap 186, intersect.surface_drop_lib_p3 6), 0 failed, 0 empty passes, max_rel_err 0. All 1601 other case records and both runner outputs are unchanged; the 186 native refusal outputs are unchanged. Still partial; exact-import support and numerical coverage are unchanged. UPDATE (PR #204, topology.brep_p3): doc only: NotifyEnv::records / attributes are keyed by notification identities (the caller's key_of, the caller's to keep distinct; kernel-issued ones are unique only within one identity domain, so a store holding them belongs to one domain); key_of keys from KERNEL_KEY_BASE up are the kernel's. PR #596 exact analytic surface transport: the shim emits the shared exact head for every supported analytic surface, and the runner imports it with the shared exact reader. CI difftest (run 39941441548, head b03bedb2b): 1787 cases, 1787 pass, 0 fail, 0 deferred, max_rel_err 0, with_values 1787. All 114 formerly deferred cases now compare: 112 directly and the 2 that had advanced to topology.face_p2 after the owned snapshot in topology.edgeuse_p3::evaluate_binormal_uv restores the exact cached face tolerance after set_owner. The 72 native revolution regressions remain. The two native paths box_with_large_fillet_e11_tangent (retained original, Cone) and box_with_large_fillet_rb_e0_tangent (rebuilt UV, surface-agnostic) both require the exact-import flag, no rejection/defer/error, and a real native comparison; the inexact-import guard is unchanged. No ellipse/circle curve transport was required. GREEN. PR #716 private ellipse/circle edge transport: exact native database records, explicit out-of-bounds restoration through the inherited Ellipse setter, native non-mutation/read-write checks and independent raw-base bit checks. Adds 128 wire cases covering tangent, trim, reverse and ODE values across senses, orientations, scales, trimmed and non-unit intervals. CI at 8fc5cb710 (run 40062996668): 1915/1915 with values, zero failures/deferrals/empty/vacuous passes, max relative error 0. The 72 revolution regressions remain. Analytic UV curves and unrelated curve kinds stay unsupported. CO-PORTED in topology.brep_validation (PR #961): SmEdge::ValidateGeometry(FALSE) -> topology::brep::validation::pointers::check_edge_geometry (private), preserving endpoint/owner statuses and native reversal repairs; warning-only diagnostics retain their evaluations." +notes = "PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 1787 native cases, 1787 matching, 0 explicit known, 0 deferred, zero unexpected. Six remaining Rust regressions pass; failed topology/healing outputs are not consumed and incomplete Stage3 can retry. Earlier validation: [2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1787/1787 native cases match after #521 error propagation; failed Hermite reversal preserves the edge interval, edge-use orientations and UV trims.] Issue #294 private edge revolution adapter: reuse the shared exact record exporter and reader for all supported analytic surfaces (SmSurfOfRevolution, SmCone/SmCylinder, SmSphere, SmTorus, SmSurfOfExtrusion). Retains the 72 native revolution regressions plus new Cone/Sphere/Cylinder exact coverage and exact/malformed-record controls. Historical before PR #596 (final GREEN): difftest 1787 cases (1771 fixture: 236 on prog_test my_test_topology's wire network (its SetCurve / SetInterval / SetOrientation calls and every other op), 140 on wire edges of real SmLines (my_test_reversed_line_join's lines, forward and inside-out, sub-intervals, 1e-6 and 1e6 scales), 1395 on 9 upstream .smb B-reps read by SmBrep::ReadFromFile with and without RebuildUVTrimCurves; 16 generated, 2000 more checked once), 1715 pass, 72 deferred (topology.edgeuse_p3 62, topology.brep_p1 5, topology.brep_p2 5; topology.vertex wired after merging main), max_rel_err 0; --perturb SENSITIVE. Ops: the queries (edge use/vertex use/vertex/face/shell/region collections with attribute and connection filters, start/end/other vertex, lamina/manifold/spine/closed/wire, IsConnectedTo every kind), end tangent, trimmed NURBS copy, SetTolerance, SetOrientation, ReverseOrientation, OrientEdgeSequence, ResetPrimaryEdgeuse, AddOrientedEUPair (new edge uses), InsertManifoldEdgeEnd, cached gap maxima (gap values exported), Notify, SetCurve, both FindRadialSector, GetUVTrimCurveOfSurface, both IsSeam, IsTopological, IsTangentEdge, GetBlendEdgeuse, SmODEEdgeLengthIFEO::Evaluate, SetInterval. Edges with an ellipse curve or faces of SmBSplineSurface subclasses other than SmPlane are imported as their B-spline part: the C++ runner refuses ops evaluating them with inexact_geometry, and the Rust runner reports a harness deferral on topology.edge (exact analytic geometry import for edge evaluation); neither side evaluates the skipped op. Stubs: UNPORTED(topology.edgeuse_p3) SmEdgeuse::NormalizedEvaluate / EvaluateBinormal / GetOrCreateUVTrimCurve, UNPORTED(topology.brep_p2) SmBrep::Notify, UNPORTED(topology.brep_p1) SmBrep::GetInfiniteRegion, SmFace::CalculateUVDomainFromUVTrimCurves (wired by topology.face_p1, PR #148). Back-edges wired: SmSurface::CoverOtherCoincidentSurface (surface.surface_p5 PR #50; its CoverCoincidentSurface is surface.surface_p3, and no current case reaches it past the edgeuse_p3 deferrals), SmEdge::SetTolerance in topology::edgeuse::{p1,p2} and topology::vertex (RefreshTolerance), SmVertex::SetTolerance in topology::edge (merge with main), the SmEdge queries of geom::tools; the UNPORTED(topology.edge) markers left in intersect::point_class, intersect::curve_class::p1, topology::proto_topology::p1, geom::{object,solution_array,tol} are on opaque object handles with no B-rep to call into (dispatch gaps). Plan notes: 60 fns; largest: SmEdge::IsTopological, SmEdge::FindRadialSector, SmEdge::GetBlendEdgeuse, SmEdge::OrientEdgeSequence; cycle back-edges (call into later units; stub or co-port): heal.gap(12), topology.edgeuse_p3(3), query.edge(2), topology.brep_p2(2), blend.fillet_geom_p1(1), topology.brep_p1(1), topology.face_p1(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmEdge members m_pList (edge use list, primary first), m_vInterval, m_sZoneTol3d, m_pCurve -> topology::Edge::{edgeuses, interval, tolerance, curve}; SmEdge::GetPrimaryEdgeuse, IsLamina, GetBrep -> topology::edgeuse::p1::{primary_edgeuse, edge_is_lamina, edge_brep}; CO-PORTED in topology.vertexuse (PR #88): SmEdge::IsWire -> topology::vertexuse::edge_is_wire; CO-PORTED in query.edge (PR #89): SmEdge::GetVertices(SmTArray&, ULONG*) (no attribute filter), SmEdge::GetFaces (no attribute filter) -> query::edge::{edge_vertices, edge_faces}; CO-PORTED in topology.face_p2 (PR #91): SmEdge::GetEdgeuses without filters -> private topology::face::p2::edge_edgeuses; CO-PORTED in topology.vertex (PR #96): SmEdge::GetStartVertex, GetVertices(SmVertex*&, SmVertex*&), GetVertices(SmTArray&) (no attribute filter), GetEndTangent -> topology::vertex::{edge_start_vertex, edge_vertex_pair, edge_vertices, edge_end_tangent} Re-run (PR #52, after merging surface_p4/#49 and surface_p5/#50): is_topological takes &Kernel and &mut Brep (CoverCoincidentSurface expands the covering face surface in place); 1787 cases, 1715 pass, 72 deferred (topology.edgeuse_p3 62, topology.brep_p1 5, topology.brep_p2 5). Re-run in PR #154 (shell state export, SmBrep::GetEdges registered first so edge selectors pick different edges): same 1787 cases, 5 moved deferred -> pass (topology.edgeuse_p3 67 -> 62), 0 failed, --perturb SENSITIVE 1715/1715. PR #163 adds find_radial_sector_between for independent operand arenas; existing same-B-rep API delegates to it. Cross-operand C++ cases live in fixtures/boolean.merge_p1/review.json. [topology.edgeuse_p3 wired (PR #117): 1787 cases, 1771 pass, 0 failed, 16 deferred (intersect.surface_drop_lib_p3 6, topology.brep_p1 5, topology.brep_p2 5)] Re-run after topology.brep_p1 (PR #145) wired SmBrep::GetInfiniteRegion (and main's #154 merged): 1787 cases, 1715 pass, 0 failed, 72 deferred (topology.edgeuse_p3 67, topology.brep_p2 5), max_rel_err 0. PR #183 continuation review restores both InsertManifoldEdgeEnd vertex owning-list insertions in native order, with a focused start/end regression. Re-run after topology.brep_p2 (PR #149) wired SmBrep::Notify: 1787 cases, 1715 pass, 0 failed, 72 deferred (topology.edgeuse_p3 72), max_rel_err 0. Re-run after merging #145 5b1b28ae (main through e1ae2640) into #149 (#117 wired the topology.edgeuse_p3 stubs, #145 GetInfiniteRegion, #149 SmBrep::Notify): 1787 cases, 1781 pass, 0 fail, 6 deferred (intersect.surface_drop_lib_p3 6), max_rel_err 0, --perturb SENSITIVE 1781/1781; the 5 net_set_curve_delete_notify cases now compare 155 values (49 before) as Notify runs. Reporting correction (PR #356, issue #294): fresh default-seed baseline 1787 cases, 1781 passed (1595 with values, 186 empty inexact_geometry refusals), 6 deferred, 0 failed. Reclassifying only the Rust-side import refusals gives 1595 passed with values, 192 deferred (topology.edge harness import gap 186, intersect.surface_drop_lib_p3 6), 0 failed, 0 empty passes, max_rel_err 0. All 1601 other case records and both runner outputs are unchanged; the 186 native refusal outputs are unchanged. Still partial; exact-import support and numerical coverage are unchanged. UPDATE (PR #204, topology.brep_p3): doc only: NotifyEnv::records / attributes are keyed by notification identities (the caller's key_of, the caller's to keep distinct; kernel-issued ones are unique only within one identity domain, so a store holding them belongs to one domain); key_of keys from KERNEL_KEY_BASE up are the kernel's. PR #596 exact analytic surface transport: the shim emits the shared exact head for every supported analytic surface, and the runner imports it with the shared exact reader. CI difftest (run 39941441548, head b03bedb2b): 1787 cases, 1787 pass, 0 fail, 0 deferred, max_rel_err 0, with_values 1787. All 114 formerly deferred cases now compare: 112 directly and the 2 that had advanced to topology.face_p2 after the owned snapshot in topology.edgeuse_p3::evaluate_binormal_uv restores the exact cached face tolerance after set_owner. The 72 native revolution regressions remain. The two native paths box_with_large_fillet_e11_tangent (retained original, Cone) and box_with_large_fillet_rb_e0_tangent (rebuilt UV, surface-agnostic) both require the exact-import flag, no rejection/defer/error, and a real native comparison; the inexact-import guard is unchanged. No ellipse/circle curve transport was required. GREEN. PR #716 private ellipse/circle edge transport: exact native database records, explicit out-of-bounds restoration through the inherited Ellipse setter, native non-mutation/read-write checks and independent raw-base bit checks. Adds 128 wire cases covering tangent, trim, reverse and ODE values across senses, orientations, scales, trimmed and non-unit intervals. CI at 8fc5cb710 (run 40062996668): 1915/1915 with values, zero failures/deferrals/empty/vacuous passes, max relative error 0. The 72 revolution regressions remain. Analytic UV curves and unrelated curve kinds stay unsupported. CO-PORTED in topology.brep_validation (PR #961): SmEdge::ValidateGeometry(FALSE) -> topology::brep::validation::pointers::check_edge_geometry (private), preserving endpoint/owner statuses and native reversal repairs; warning-only diagnostics retain their evaluations." diff --git a/plan/units/topology.edgeuse_p1.toml b/plan/units/topology.edgeuse_p1.toml index 38765a27d8..05dd01fb0d 100644 --- a/plan/units/topology.edgeuse_p1.toml +++ b/plan/units/topology.edgeuse_p1.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test: status = "green" claimed_by = "" claimed_at = "" -notes = "[2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 4402/4402 native cases match. #521 UV/3D loop-gap evaluations, unsuccessful drops and distance/deviation derivatives propagate failures instead of consuming unwritten outputs.] Issue #682 (2026-10-24): both shared B-rep graph exporters/importers preserve native ellipse/circle records, stored rational NURBs and unchanged UV trims. GREEN: 5554 cases, 5554 pass, 0 failed, 0 deferred, max_rel_err 0; adds 1152 cases over 48 native ellipse/circle face configurations (three planes, full/partial arcs, non-unit NURB intervals, inside-out and out-of-bounds flags, small/large scales), sampling edge/UV/lifted points at matching parameters. Native regression checks both import paths, bitwise UV preservation, old-format fallback deferral and malformed-record rejection. GREEN after the Cone DropPointFast dispatch (area 6b, 2026-10-22): 4402 cases, 4402 pass, 0 deferred (was surface.surface_p2 50), 0 failed, max_rel_err 0. EXACT SmSurfOfRevolution EXPORT (shared graph exports carry revolution faces exactly, 2026-10-20): difftest 4402 cases, 4168 pass, 0 failed, 234 deferred (surface.surface_p2 150, surface.surf_of_extrusion_p2 84), max_rel_err 0; 3968 -> 4168 pass (topology.brep_p10 stand-in deferral 200 -> 0). Earlier: PARTIAL: difftest 2722 cases (2706 fixture: 1155 on 15 upstream .smb B-reps read by SmBrep::ReadFromFile, with and without RebuildUVTrimCurves; 1115 on prog_test my_test_topology's wire network, wire edges and faces from B-spline curves/surfaces and real SmPlanes, plus the geometry statics on B-splines and SmPlanes; 436 review adversarial in fixtures/topology.edgeuse_p1/adversarial.json: pole/pinch, tiny and huge scale (to 1e200), uv up to 100 domain lengths outside, 1% seam tolerance edge, null/drop-curve SetUVTrimCurve, rebuilt .smb gaps; 16 generated, 6000 more checked once), 2625 pass, 97 deferred (surface.surface_point_solve_p3 90, topology.edgeuse_p3 7; heal.gap wired; SmEdge::SetTolerance/SmVertex::SetTolerance by topology.edge PR #103; formerly 28 surface_point_solve_p3: SmEdgeuse::GetStartPointUVLoopGap now drops through Surface::drop_point (surface.surface_p5 wired), whose global point solve (SmSurface::GlobalPointSolve_1) is deferred in the report, topology.edgeuse_p3 7), max_rel_err 0; --perturb SENSITIVE. There is no Rust B-rep reader/builder yet, so the runner imports the graph the shim exports (raw members, traversal order) into topology::Brep. Co-ports the minimal use records into the topology skeleton (see CO-PORTED notes of topology.edge, topology.loopuse, topology.vertexuse, topology.shell, topology.vertex, topology.face_p2, topology.brep_p1, heal.gap, topology.proto_topology_p1); adds curve::line::Line::from_parts (raw constructor for the import). SmBrep::ReadFromFile crashes on SmallBox.smb and Platform.smb (plan/findings.md), so they are not used. Back-edge stubs UNPORTED(topology.edgeuse_p1) in surface::trim_srf_cache (GetUVTrimCurve), intersect::point_class (GetBrep, GetEdge) and geom::tol (GetZoneTol3d dispatch) are not wired: those callers hold opaque object handles without a Brep (plan/findings.md). Plan notes: 57 fns; largest: SmEdgeuse::SetUVTrimCurve, SmEdgeuse::CalcMaxEdgeFaceGap, SmFaceuse::CollectAdjacentFaceuses, SmFindStepOffENFO::Evaluate; cycle back-edges (call into later units; stub or co-port): heal.gap(14), topology.edge(13), topology.loopuse(9), topology.vertexuse(4), topology.face_p2(2), topology.shell(2), topology.edgeuse_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit Review PR #106 recertification after merged #50: 2722 cases, 2711 pass, 11 deferred, 0 failed, max_rel_err=0.0; deferred: surface.surface_p8 4, topology.edgeuse_p3 7. Re-run after merging PR #106 (surface.surface_point_solve_p3 wired, 2026-10-15): 2722 cases, 2711 pass, 11 deferred (surface.surface_p8 4, topology.edgeuse_p3 7), 0 failed, max_rel_err 0. Review fixes (2026-10-16): the gap ops now compare the computed gap values (gap_3d, edge_t, face_uv; edge_t0, edge_t1), SmPlane faces are imported exactly (origin, X/Y axes, UV scale, STEP domain) instead of Plane::from_normal, and cases evaluating analytic geometry imported as its B-spline part are deferred (inexact_geometry, as topology.edgeuse_p2): 4402 cases (adds fixtures/topology.edgeuse_p1/review_gap_values.json, 1680 cases), 3961 pass, 441 deferred (surface.cone_p1 50, surface.sphere 100, surface.surf_of_extrusion_p2 84, topology.brep_p10 200, topology.edgeuse_p3 7), 0 failed, max_rel_err 0; --perturb SENSITIVE (3961/3961). [topology.edgeuse_p3 wired (PR #117): 2722 cases, 2718 pass, 0 failed, 4 deferred (surface.surface_p8 4)]" +notes = "PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 4402 native cases, 4402 matching, 0 explicit known, 0 deferred, zero unexpected. Six remaining Rust regressions pass; failed topology/healing outputs are not consumed and incomplete Stage3 can retry. Earlier validation: [2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 4402/4402 native cases match. #521 UV/3D loop-gap evaluations, unsuccessful drops and distance/deviation derivatives propagate failures instead of consuming unwritten outputs.] Issue #682 (2026-10-24): both shared B-rep graph exporters/importers preserve native ellipse/circle records, stored rational NURBs and unchanged UV trims. GREEN: 5554 cases, 5554 pass, 0 failed, 0 deferred, max_rel_err 0; adds 1152 cases over 48 native ellipse/circle face configurations (three planes, full/partial arcs, non-unit NURB intervals, inside-out and out-of-bounds flags, small/large scales), sampling edge/UV/lifted points at matching parameters. Native regression checks both import paths, bitwise UV preservation, old-format fallback deferral and malformed-record rejection. GREEN after the Cone DropPointFast dispatch (area 6b, 2026-10-22): 4402 cases, 4402 pass, 0 deferred (was surface.surface_p2 50), 0 failed, max_rel_err 0. EXACT SmSurfOfRevolution EXPORT (shared graph exports carry revolution faces exactly, 2026-10-20): difftest 4402 cases, 4168 pass, 0 failed, 234 deferred (surface.surface_p2 150, surface.surf_of_extrusion_p2 84), max_rel_err 0; 3968 -> 4168 pass (topology.brep_p10 stand-in deferral 200 -> 0). Earlier: PARTIAL: difftest 2722 cases (2706 fixture: 1155 on 15 upstream .smb B-reps read by SmBrep::ReadFromFile, with and without RebuildUVTrimCurves; 1115 on prog_test my_test_topology's wire network, wire edges and faces from B-spline curves/surfaces and real SmPlanes, plus the geometry statics on B-splines and SmPlanes; 436 review adversarial in fixtures/topology.edgeuse_p1/adversarial.json: pole/pinch, tiny and huge scale (to 1e200), uv up to 100 domain lengths outside, 1% seam tolerance edge, null/drop-curve SetUVTrimCurve, rebuilt .smb gaps; 16 generated, 6000 more checked once), 2625 pass, 97 deferred (surface.surface_point_solve_p3 90, topology.edgeuse_p3 7; heal.gap wired; SmEdge::SetTolerance/SmVertex::SetTolerance by topology.edge PR #103; formerly 28 surface_point_solve_p3: SmEdgeuse::GetStartPointUVLoopGap now drops through Surface::drop_point (surface.surface_p5 wired), whose global point solve (SmSurface::GlobalPointSolve_1) is deferred in the report, topology.edgeuse_p3 7), max_rel_err 0; --perturb SENSITIVE. There is no Rust B-rep reader/builder yet, so the runner imports the graph the shim exports (raw members, traversal order) into topology::Brep. Co-ports the minimal use records into the topology skeleton (see CO-PORTED notes of topology.edge, topology.loopuse, topology.vertexuse, topology.shell, topology.vertex, topology.face_p2, topology.brep_p1, heal.gap, topology.proto_topology_p1); adds curve::line::Line::from_parts (raw constructor for the import). SmBrep::ReadFromFile crashes on SmallBox.smb and Platform.smb (plan/findings.md), so they are not used. Back-edge stubs UNPORTED(topology.edgeuse_p1) in surface::trim_srf_cache (GetUVTrimCurve), intersect::point_class (GetBrep, GetEdge) and geom::tol (GetZoneTol3d dispatch) are not wired: those callers hold opaque object handles without a Brep (plan/findings.md). Plan notes: 57 fns; largest: SmEdgeuse::SetUVTrimCurve, SmEdgeuse::CalcMaxEdgeFaceGap, SmFaceuse::CollectAdjacentFaceuses, SmFindStepOffENFO::Evaluate; cycle back-edges (call into later units; stub or co-port): heal.gap(14), topology.edge(13), topology.loopuse(9), topology.vertexuse(4), topology.face_p2(2), topology.shell(2), topology.edgeuse_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit Review PR #106 recertification after merged #50: 2722 cases, 2711 pass, 11 deferred, 0 failed, max_rel_err=0.0; deferred: surface.surface_p8 4, topology.edgeuse_p3 7. Re-run after merging PR #106 (surface.surface_point_solve_p3 wired, 2026-10-15): 2722 cases, 2711 pass, 11 deferred (surface.surface_p8 4, topology.edgeuse_p3 7), 0 failed, max_rel_err 0. Review fixes (2026-10-16): the gap ops now compare the computed gap values (gap_3d, edge_t, face_uv; edge_t0, edge_t1), SmPlane faces are imported exactly (origin, X/Y axes, UV scale, STEP domain) instead of Plane::from_normal, and cases evaluating analytic geometry imported as its B-spline part are deferred (inexact_geometry, as topology.edgeuse_p2): 4402 cases (adds fixtures/topology.edgeuse_p1/review_gap_values.json, 1680 cases), 3961 pass, 441 deferred (surface.cone_p1 50, surface.sphere 100, surface.surf_of_extrusion_p2 84, topology.brep_p10 200, topology.edgeuse_p3 7), 0 failed, max_rel_err 0; --perturb SENSITIVE (3961/3961). [topology.edgeuse_p3 wired (PR #117): 2722 cases, 2718 pass, 0 failed, 4 deferred (surface.surface_p8 4)]" diff --git a/plan/units/topology.loopuse.toml b/plan/units/topology.loopuse.toml index bd4544189b..91b3b38410 100644 --- a/plan/units/topology.loopuse.toml +++ b/plan/units/topology.loopuse.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test: status = "green" claimed_by = "" claimed_at = "" -notes = "[2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 8854/8854 native cases match. #521 UV-corner evaluation/drop failures propagate without reusing prior edge-use endpoints; prepared and mutable queries share this behavior.] Exact SmSurfOfRevolution/SmSphere/SmTorus face export (#304, #436); counts in reports/difftest/topology.loopuse.json. PARTIAL: difftest 8854 cases (8238 fixture: 4712 review adversarial (fixtures/topology.loopuse/review_adversarial.json: every op on loop indices 0..23 of each B-rep, a deterministic subset of a 78120-case sweep that passed once), 1984 tolerance-limit IsClosed3d cases (every edge tolerance 0, 1e-15, -1e-12 or 1e-9, so gaps exceed XSectTol3d: the over-tolerance branch runs 176 times, 88 with bCheckAllGaps FALSE), 1110 on the 15 upstream .smb B-reps read by SmBrep::ReadFromFile, with and without RebuildUVTrimCurves, plus 432 on the prog_test my_test_topology wire network, wire edges and the plane / B-spline faces of topology.edgeuse_p2 incl. tiny, huge, pinched and sub-domain ones; 16 generated + 600 generated in code; 4000 more checked once), 7950 pass bit-identical, 0 failed, 904 deferred (topology.face_p3 366: SmFace::Notify around FlipLoopOrientation of every loop on a face; topology.edgeuse_p3 348: GetOrCreateUVTrimCurve in GetUVCornerBoundaries / the sector computations of IsMissingSeamAtVertices; inexact .smb geometry 190: topology.brep_p10 87, surface.sphere 45, surface.surf_of_extrusion_p2 33, surface.cone_p1 25), max_rel_err 0; --perturb SENSITIVE. Loopuse gains orientation (m_eOrientation); the shared B-rep export (sc_edgeuse_graph.h and the topology.vertexuse / topology.face_p2 / query.edge shim copies) and their importers carry it; the loopuse shim adds each face m_pFU (FaceFu op). Upstream bug WR-93: IsClosed3d never reports a loop open (ported as is). Wired: SmVertex::IsConnectedTo / SmEdge::IsConnectedTo -> topology::vertex / topology::edge is_connected_to (merge of main; the 99 vertex/edge-target cases now run on both sides); topology.edgeuse_p1 co-ports loopuse_edgeuses / loopuse_shell, topology.face_p2 loopuse_vertexuses, query.loop calc_quad_num and topology.vertexuse SmLoopuse::GetOrientation now call topology::loopuse (their difftests unchanged); UNPORTED(topology.loopuse) in intersect::point_class and surface::trim_srf_cache not wired (opaque handles without a Brep; plan/findings.md). Plan notes: 48 fns; largest: SmLoopuse::IsClosed3d, SmLoop::GetUVCornerBoundaries, SmLoop::FlipLoopOrientation, SmLoopuse::IsMissingSeamAtVertices; cycle back-edges (call into later units; stub or co-port): topology.edge(6), topology.edgeuse_p3(1), topology.face_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmLoopuse members (owner face use, m_pL, m_pLUMate, m_tLoopuseType/m_pEUorVU) -> topology::{Loopuse, LoopuseStart}, SmLoop::m_pLU -> topology::Loop::loopuse; SmLoopuse::GetEdgeuses (no filters), GetShell -> topology::edgeuse::p1::{loopuse_edgeuses, loopuse_shell}; CO-PORTED in query.loop (PR #63): SmLoop::GetUVCornerBoundaries, sm_CalcQuadNum -> topology::loopuse::uv_corner_boundaries (query::r#loop::contains_uv_point now walks the arena topology and calls it, PR #117); CO-PORTED in topology.face_p2 (PR #91): SmLoopuse::GetVertexuses without filter -> private topology::face::p2::loopuse_vertexuses; CO-PORTED in topology.vertex (PR #96): SmLoopuse::GetVertices(SmTArray&) (no attribute filter), SmLoop::GetVertices -> topology::vertex::{loopuse_vertices, loop_vertices}; CO-PORTED in topology.edge (PR #103): SmLoopuse::GetEdges(SmTArray&) without attribute filter -> private topology::edge::loopuse_edges [topology.edgeuse_p3 wired (PR #117): 8854 cases, 8250 pass, 0 failed, 604 deferred (intersect.surface_drop_lib_p3 48, surface.cone_p1 25, surface.sphere 45, surface.surf_of_extrusion_p2 33, topology.brep_p10 87, topology.face_p3 366)] [topology.face_p3 wired (PR #139, SmFace::Notify around FlipLoopOrientation): 8854 cases, 8250 pass, 0 failed, 604 deferred; the 366 topology.face_p3 deferrals now defer at the POST_EDIT notification's SmFace::GetUpwardEdgeuses (topology.face_p4 366)] [generated cases draw a B-rep with the loopuse or loop the op selects (PR #340, #294 row 6): 8854 cases, 8259 pass (7995 comparing values; the 264 both refusing are the wire and network fixtures), 0 failed, 595 deferred (topology.face_p4 360, topology.brep_p10 87, surface.surface_p2 69, intersect.surface_drop_lib_p3 46, surface.surf_of_extrusion_p2 33)] UPDATE (PR #204, topology.brep_p3): entity_of_notify_key is #[cfg(test)] (its one production user, heal_data_p3's capture_changes, now decodes kernel identities); notify_ref is unchanged. GREEN (promoted by intersect.surface_drop_lib_p3, PR #463): CI difftest 8854 cases, 8854 pass, 0 deferred, 0 failed (the 46 cases deferred on SmSurface::DropCurve now compare)." +notes = "PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 8854 native cases, 8854 matching, 0 explicit known, 0 deferred, zero unexpected. Six remaining Rust regressions pass; failed topology/healing outputs are not consumed and incomplete Stage3 can retry. Earlier validation: [2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 8854/8854 native cases match. #521 UV-corner evaluation/drop failures propagate without reusing prior edge-use endpoints; prepared and mutable queries share this behavior.] Exact SmSurfOfRevolution/SmSphere/SmTorus face export (#304, #436); counts in reports/difftest/topology.loopuse.json. PARTIAL: difftest 8854 cases (8238 fixture: 4712 review adversarial (fixtures/topology.loopuse/review_adversarial.json: every op on loop indices 0..23 of each B-rep, a deterministic subset of a 78120-case sweep that passed once), 1984 tolerance-limit IsClosed3d cases (every edge tolerance 0, 1e-15, -1e-12 or 1e-9, so gaps exceed XSectTol3d: the over-tolerance branch runs 176 times, 88 with bCheckAllGaps FALSE), 1110 on the 15 upstream .smb B-reps read by SmBrep::ReadFromFile, with and without RebuildUVTrimCurves, plus 432 on the prog_test my_test_topology wire network, wire edges and the plane / B-spline faces of topology.edgeuse_p2 incl. tiny, huge, pinched and sub-domain ones; 16 generated + 600 generated in code; 4000 more checked once), 7950 pass bit-identical, 0 failed, 904 deferred (topology.face_p3 366: SmFace::Notify around FlipLoopOrientation of every loop on a face; topology.edgeuse_p3 348: GetOrCreateUVTrimCurve in GetUVCornerBoundaries / the sector computations of IsMissingSeamAtVertices; inexact .smb geometry 190: topology.brep_p10 87, surface.sphere 45, surface.surf_of_extrusion_p2 33, surface.cone_p1 25), max_rel_err 0; --perturb SENSITIVE. Loopuse gains orientation (m_eOrientation); the shared B-rep export (sc_edgeuse_graph.h and the topology.vertexuse / topology.face_p2 / query.edge shim copies) and their importers carry it; the loopuse shim adds each face m_pFU (FaceFu op). Upstream bug WR-93: IsClosed3d never reports a loop open (ported as is). Wired: SmVertex::IsConnectedTo / SmEdge::IsConnectedTo -> topology::vertex / topology::edge is_connected_to (merge of main; the 99 vertex/edge-target cases now run on both sides); topology.edgeuse_p1 co-ports loopuse_edgeuses / loopuse_shell, topology.face_p2 loopuse_vertexuses, query.loop calc_quad_num and topology.vertexuse SmLoopuse::GetOrientation now call topology::loopuse (their difftests unchanged); UNPORTED(topology.loopuse) in intersect::point_class and surface::trim_srf_cache not wired (opaque handles without a Brep; plan/findings.md). Plan notes: 48 fns; largest: SmLoopuse::IsClosed3d, SmLoop::GetUVCornerBoundaries, SmLoop::FlipLoopOrientation, SmLoopuse::IsMissingSeamAtVertices; cycle back-edges (call into later units; stub or co-port): topology.edge(6), topology.edgeuse_p3(1), topology.face_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmLoopuse members (owner face use, m_pL, m_pLUMate, m_tLoopuseType/m_pEUorVU) -> topology::{Loopuse, LoopuseStart}, SmLoop::m_pLU -> topology::Loop::loopuse; SmLoopuse::GetEdgeuses (no filters), GetShell -> topology::edgeuse::p1::{loopuse_edgeuses, loopuse_shell}; CO-PORTED in query.loop (PR #63): SmLoop::GetUVCornerBoundaries, sm_CalcQuadNum -> topology::loopuse::uv_corner_boundaries (query::r#loop::contains_uv_point now walks the arena topology and calls it, PR #117); CO-PORTED in topology.face_p2 (PR #91): SmLoopuse::GetVertexuses without filter -> private topology::face::p2::loopuse_vertexuses; CO-PORTED in topology.vertex (PR #96): SmLoopuse::GetVertices(SmTArray&) (no attribute filter), SmLoop::GetVertices -> topology::vertex::{loopuse_vertices, loop_vertices}; CO-PORTED in topology.edge (PR #103): SmLoopuse::GetEdges(SmTArray&) without attribute filter -> private topology::edge::loopuse_edges [topology.edgeuse_p3 wired (PR #117): 8854 cases, 8250 pass, 0 failed, 604 deferred (intersect.surface_drop_lib_p3 48, surface.cone_p1 25, surface.sphere 45, surface.surf_of_extrusion_p2 33, topology.brep_p10 87, topology.face_p3 366)] [topology.face_p3 wired (PR #139, SmFace::Notify around FlipLoopOrientation): 8854 cases, 8250 pass, 0 failed, 604 deferred; the 366 topology.face_p3 deferrals now defer at the POST_EDIT notification's SmFace::GetUpwardEdgeuses (topology.face_p4 366)] [generated cases draw a B-rep with the loopuse or loop the op selects (PR #340, #294 row 6): 8854 cases, 8259 pass (7995 comparing values; the 264 both refusing are the wire and network fixtures), 0 failed, 595 deferred (topology.face_p4 360, topology.brep_p10 87, surface.surface_p2 69, intersect.surface_drop_lib_p3 46, surface.surf_of_extrusion_p2 33)] UPDATE (PR #204, topology.brep_p3): entity_of_notify_key is #[cfg(test)] (its one production user, heal_data_p3's capture_changes, now decodes kernel identities); notify_ref is unchanged. GREEN (promoted by intersect.surface_drop_lib_p3, PR #463): CI difftest 8854 cases, 8854 pass, 0 deferred, 0 failed (the 46 cases deferred on SmSurface::DropCurve now compare)." diff --git a/plan/units/topology.vertex.toml b/plan/units/topology.vertex.toml index c2fc93016f..08d502ede8 100644 --- a/plan/units/topology.vertex.toml +++ b/plan/units/topology.vertex.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test: status = "green" claimed_by = "" claimed_at = "" -notes = "PARTIAL: difftest 5508 cases (5492 fixture: 1408 review (rational degree-1 / quadratic / arc edges meeting lines and arcs at a vertex, B-spline-line and reversed SmLine turns at 0.49..180 degrees, 1e-12..1e12 scales, 2- and 3-edge closed chains, tangent / gap / SetTolerance limits); 2160 on 15 upstream .smb B-reps read by SmBrep::ReadFromFile, some with RebuildUVTrimCurves; 1665 constructed: prog_test my_test_topology's wire network, my_test_edge_geometry_repairs' wire line (SetTolerance/SetPoint), chains of wire edges from real SmLines (re-parameterised), degree-1 B-spline lines and B-spline curves turning by 0 to 180 degrees around the 0.5 degree continuity limit (IsTopologicalVertex / AreEdgesTangent), closed squares, a closed seam curve, wire edges and plane / B-spline / pinched faces (full and sub-domain); 259 adversarial: 1e-6..1e6 scales, zero-length and tiny segments, a point B-spline, tolerance limits incl. SM_USE_DEFAULT and negatives, gap tolerance limits, every notify operation, null selectors, far edges; 16 generated), 4651 pass, 857 deferred (topology.edgeuse_p3 399, surface.surface_p3 382, topology.face_p3 76; SmEdge::SetTolerance wired by topology.edge PR #103; after merging main: SmFace::GetUpwardFaceuse wired to topology::face::p2::upward_faceuse, the shim exports each face's upward face use, and heal.gap gaps are real), max_rel_err 0; --perturb SENSITIVE. Runner imports the shim's exported graph (with each vertex's vertex use list in upstream order, cached gap values, edge curve identity) into topology::Brep. topology::Vertex gains vertexuses (m_pList) and poly_vertex; SM_TOLERANT_WATCH is off in the reference build (no m_bExact: IsExact is TRUE, SetExact no-op). Upstream bugs WR-91 (GetVertexuseOfFace ignores its face), WR-92 (IsTopologicalVertex SER/cusps), UB-105 (GetMaxUpDimGap3d NULL array), UB-106 (uninitialised tangents). Wired UNPORTED(topology.vertex) in geom::tools (geom.tools: 60 deferred now pass); generic-handle stubs in intersect::point_class, geom::solution_array, geom::object, geom::tol, surface::trim_srf_cache, topology::proto_topology::p1::proto and query::poly stay (opaque handles, plan/findings.md); the co-ported vertex use scans in topology::vertexuse now call topology::vertex. Plan notes: 38 fns; largest: SmVertex::IsTopologicalVertex, SmVertex::Notify, SmVertex::IsConnectedTo, SmVertex::GetMaxUpDimGap3d; cycle back-edges (call into later units; stub or co-port): topology.edge(12), heal.gap(4), topology.loopuse(2), blend.fillet_geom_p1(1), topology.face_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmVertex::m_sZoneTol3d -> topology::Vertex::tolerance; CO-PORTED in topology.vertexuse (PR #88): SmVertex::GetVertexuses / GetVertexusesOfEdge / GetVertexusesOfFace (replaced by topology::vertex in PR #96); CO-PORTED in topology.face_p2 (PR #91): SmVertex::GetEdges without face filter (uses found by scanning the B-rep's vertex uses) -> private topology::face::p2::vertex_edges Re-run (PR #52, after merging surface_p4/#49 and surface_p5/#50): 5508 cases, 4984 pass, 524 deferred (topology.edgeuse_p3 448, topology.face_p3 76). [topology.edgeuse_p3 wired (PR #117): 5508 cases, 4896 pass, 0 failed, 612 deferred (surface.cone_p1 104, surface.sphere 104, surface.surf_of_extrusion_p2 104, topology.brep_p10 224, topology.face_p3 76)] [PR #155: the inexact-import deferrals are labelled topology.brep_p10 SmBrep::MakeTopologyFromData, not topology.brep_p7 SmBrep::ReadFromFile] [topology.face_p3 wired (PR #139, SmFace::SetTolerance in RefreshTolerance): 5508 cases, 4972 pass, 0 failed, 536 deferred (surface.cone_p1 104, surface.sphere 104, surface.surf_of_extrusion_p2 104, topology.brep_p10 224)] [2026-10-23 GREEN (PR #605): the shim exports each analytic face surface exactly (sc_analytic_export.h record head) and the runner reads it with util::read_exact_surface, so cone/sphere/revolution/extrusion faces are real SurfaceKinds and the solving ops compare values: PASS topology.vertex cases=5508 passed=5508 failed=0 deferred=0 max_rel_err=0e0 with_values=5506 no_values=2 vacuous=0] [#521: a failed GetEndTangent in AreEdgesTangent / IsTopologicalVertex leaves the SmVector3d default (SM_UNDEF_DOUBLE), not an uninitialised tangent: the port reads Vec3::UNDEF (angle 0, TRUE) where it returned InvalidInput, and the shim refuses only a null curve (WR-256, UB-106 corrected); 70 cases in end_tangents.json (zero end tangents, 1e-30 end spans): PASS topology.vertex cases=5578 passed=5578 failed=0 deferred=0 max_rel_err=0e0 with_values=5576 no_values=2 vacuous=0]" +notes = "PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 5598 native cases, 5598 matching, 0 explicit known, 0 deferred, zero unexpected. Main #704 vertex behavior/test and its 70 fixtures preserved exactly; our 20 constant-angle/control fixtures are matching parity coverage. All 43 former vertex exceptions were proven matching and removed, then the full 5598-case unit reran strictly green. Earlier validation: PARTIAL: difftest 5508 cases (5492 fixture: 1408 review (rational degree-1 / quadratic / arc edges meeting lines and arcs at a vertex, B-spline-line and reversed SmLine turns at 0.49..180 degrees, 1e-12..1e12 scales, 2- and 3-edge closed chains, tangent / gap / SetTolerance limits); 2160 on 15 upstream .smb B-reps read by SmBrep::ReadFromFile, some with RebuildUVTrimCurves; 1665 constructed: prog_test my_test_topology's wire network, my_test_edge_geometry_repairs' wire line (SetTolerance/SetPoint), chains of wire edges from real SmLines (re-parameterised), degree-1 B-spline lines and B-spline curves turning by 0 to 180 degrees around the 0.5 degree continuity limit (IsTopologicalVertex / AreEdgesTangent), closed squares, a closed seam curve, wire edges and plane / B-spline / pinched faces (full and sub-domain); 259 adversarial: 1e-6..1e6 scales, zero-length and tiny segments, a point B-spline, tolerance limits incl. SM_USE_DEFAULT and negatives, gap tolerance limits, every notify operation, null selectors, far edges; 16 generated), 4651 pass, 857 deferred (topology.edgeuse_p3 399, surface.surface_p3 382, topology.face_p3 76; SmEdge::SetTolerance wired by topology.edge PR #103; after merging main: SmFace::GetUpwardFaceuse wired to topology::face::p2::upward_faceuse, the shim exports each face's upward face use, and heal.gap gaps are real), max_rel_err 0; --perturb SENSITIVE. Runner imports the shim's exported graph (with each vertex's vertex use list in upstream order, cached gap values, edge curve identity) into topology::Brep. topology::Vertex gains vertexuses (m_pList) and poly_vertex; SM_TOLERANT_WATCH is off in the reference build (no m_bExact: IsExact is TRUE, SetExact no-op). Upstream bugs WR-91 (GetVertexuseOfFace ignores its face), WR-92 (IsTopologicalVertex SER/cusps), UB-105 (GetMaxUpDimGap3d NULL array), UB-106 (uninitialised tangents). Wired UNPORTED(topology.vertex) in geom::tools (geom.tools: 60 deferred now pass); generic-handle stubs in intersect::point_class, geom::solution_array, geom::object, geom::tol, surface::trim_srf_cache, topology::proto_topology::p1::proto and query::poly stay (opaque handles, plan/findings.md); the co-ported vertex use scans in topology::vertexuse now call topology::vertex. Plan notes: 38 fns; largest: SmVertex::IsTopologicalVertex, SmVertex::Notify, SmVertex::IsConnectedTo, SmVertex::GetMaxUpDimGap3d; cycle back-edges (call into later units; stub or co-port): topology.edge(12), heal.gap(4), topology.loopuse(2), blend.fillet_geom_p1(1), topology.face_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmVertex::m_sZoneTol3d -> topology::Vertex::tolerance; CO-PORTED in topology.vertexuse (PR #88): SmVertex::GetVertexuses / GetVertexusesOfEdge / GetVertexusesOfFace (replaced by topology::vertex in PR #96); CO-PORTED in topology.face_p2 (PR #91): SmVertex::GetEdges without face filter (uses found by scanning the B-rep's vertex uses) -> private topology::face::p2::vertex_edges Re-run (PR #52, after merging surface_p4/#49 and surface_p5/#50): 5508 cases, 4984 pass, 524 deferred (topology.edgeuse_p3 448, topology.face_p3 76). [topology.edgeuse_p3 wired (PR #117): 5508 cases, 4896 pass, 0 failed, 612 deferred (surface.cone_p1 104, surface.sphere 104, surface.surf_of_extrusion_p2 104, topology.brep_p10 224, topology.face_p3 76)] [PR #155: the inexact-import deferrals are labelled topology.brep_p10 SmBrep::MakeTopologyFromData, not topology.brep_p7 SmBrep::ReadFromFile] [topology.face_p3 wired (PR #139, SmFace::SetTolerance in RefreshTolerance): 5508 cases, 4972 pass, 0 failed, 536 deferred (surface.cone_p1 104, surface.sphere 104, surface.surf_of_extrusion_p2 104, topology.brep_p10 224)] [2026-10-23 GREEN (PR #605): the shim exports each analytic face surface exactly (sc_analytic_export.h record head) and the runner reads it with util::read_exact_surface, so cone/sphere/revolution/extrusion faces are real SurfaceKinds and the solving ops compare values: PASS topology.vertex cases=5508 passed=5508 failed=0 deferred=0 max_rel_err=0e0 with_values=5506 no_values=2 vacuous=0] [#521: a failed GetEndTangent in AreEdgesTangent / IsTopologicalVertex leaves the SmVector3d default (SM_UNDEF_DOUBLE), not an uninitialised tangent: the port reads Vec3::UNDEF (angle 0, TRUE) where it returned InvalidInput, and the shim refuses only a null curve (WR-256, UB-106 corrected); 70 cases in end_tangents.json (zero end tangents, 1e-30 end spans): PASS topology.vertex cases=5578 passed=5578 failed=0 deferred=0 max_rel_err=0e0 with_values=5576 no_values=2 vacuous=0]" From d5504db361ee706b9cbe96d390c80f78ada289cb Mon Sep 17 00:00:00 2001 From: Kirill Frolov Date: Sun, 25 Oct 2026 06:00:15 +0300 Subject: [PATCH 4/8] test: cover failed healing retries through shared cache Exercise both public and shared-cache Stage3 entry points after the cache refactor, retaining error propagation and retry state. Co-Authored-By: Codex --- crates/solidcore/src/heal/face_props.rs | 37 ++++++++++++++----------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/solidcore/src/heal/face_props.rs b/crates/solidcore/src/heal/face_props.rs index 447da69c05..8d9559a9f7 100644 --- a/crates/solidcore/src/heal/face_props.rs +++ b/crates/solidcore/src/heal/face_props.rs @@ -2745,22 +2745,27 @@ mod tests { #[test] fn stage3_failure_does_not_certify_cached_properties() { - let k = Kernel::default(); - let (mut b, f) = plane_without_loops(); - let mut p: FaceProps = FaceProps::for_face(f, 0); - p.set_props_stage2(&k, &b, f, 0, HealerOpType::All).unwrap(); - b.faceuse_mut(crate::topology::FaceuseId::from_index(0)) - .unwrap() - .loopuses - .push(crate::topology::LoopuseId::from_index(99)); - let first = p - .set_props_stage3(&k, &b, f, 0, HealerOpType::All) - .unwrap_err(); - assert_eq!( - p.set_props_stage3(&k, &b, f, 0, HealerOpType::All), - Err(first) - ); - assert_eq!(p.prop_stage, PropStage::Stage2); + for cached in [false, true] { + let k = Kernel::default(); + let (mut b, f) = plane_without_loops(); + let mut p: FaceProps = FaceProps::for_face(f, 0); + p.set_props_stage2(&k, &b, f, 0, HealerOpType::All).unwrap(); + b.faceuse_mut(crate::topology::FaceuseId::from_index(0)) + .unwrap() + .loopuses + .push(crate::topology::LoopuseId::from_index(99)); + let mut cache = CurveOnClassifyCache::default(); + let mut run = |props: &mut FaceProps| { + if cached { + props.set_props_stage3_cached(&k, &b, f, 0, HealerOpType::All, &mut cache) + } else { + props.set_props_stage3(&k, &b, f, 0, HealerOpType::All) + } + }; + let first = run(&mut p).unwrap_err(); + assert_eq!(run(&mut p), Err(first)); + assert_eq!(p.prop_stage, PropStage::Stage2); + } } fn plane_without_loops() -> (Brep, FaceId) { From e049faa7392a97b0317784602ea6dd94c83e0e41 Mon Sep 17 00:00:00 2001 From: Kirill Frolov Date: Sun, 25 Oct 2026 06:05:41 +0300 Subject: [PATCH 5/8] docs: record healing markers reached after cache integration Verify four newly reachable incomplete-stage differences individually against native outputs and current main. Keep healing partial, preserve all prior records, and document the identical query.face_p4/gen0360 baseline failure without suppressing it. Co-Authored-By: Codex --- plan/units/heal.face_props.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plan/units/heal.face_props.toml b/plan/units/heal.face_props.toml index 393102ce76..07fafba4af 100644 --- a/plan/units/heal.face_props.toml +++ b/plan/units/heal.face_props.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_trimming_tools", "SM_API_test:TestSmTrimSurfaceWith3 status = "partial" claimed_by = "" claimed_at = "" -notes = "PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 1528 native cases, 1435 matching, 4 explicit known, 89 deferred, zero unexpected. Six remaining Rust regressions pass; failed topology/healing outputs are not consumed and incomplete Stage3 can retry. Earlier validation: [2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1528 native cases: 1427 match, 97 existing UNPORTED deferrals and 4 explicit WR-263 diagnostic stage-marker differences. Failed Stage3 preserves status 1040 and partial diagnostics while resetting its in-progress marker from 5 to 4 for retry; ClassifyLoops errors now propagate before output arrays are read.] 2026-10-25 (PR #863, snapshot surfaces keep their face owner, plan/findings.md 'snapshot surfaces keep their face owner'): gen0646, gen0790, gen1312 pass; GREEN heal.face_props cases=1528 passed=1528 failed=0 deferred=0 max_rel_err=0e0 with_values=1519 no_values=9 vacuous=0. 2026-10-24 (wire: trimmed surface cache history; the intersect::curve_class::p7 tolerance-growth deferral removed, plan/findings.md 'trimmed surface cache history: tolerance-growth deferral removed'), Linux: of 15 surface.surface_p2 deferrals 12 pass, 3 are known mismatches (gen0646, gen0790, gen1312: torus seam FindVertexSector). PARTIAL heal.face_props cases=1528 passed=1525 failed=0 deferred=0 max_rel_err=1e0 with_values=1516 no_values=9 vacuous=0 known_mismatch=3. PR #697 (2026-10-24, face surfaces keep their trimmed caches): 1528 cases, 1439 pass, 0 failed, 89 deferred (topology.face_p4 88, intersect.curve_class_p4 1); the 15 cache-history (tolerance growth) deferrals go: IntersectVertices' gap reads now run with the surface owner cleared, so the standalone trimmed cache upstream keeps is made there and reused (8 pass, the rest defer further on at FindVertexSector). PR #639 (2026-10-23): the runner builds the down face use side (SmBrepConstructor), which fixes the seam EDGE-vs-VERTEX snap; difftest 1528 cases, 1431 pass, 0 failed, 97 deferred (topology.face_p4 FindVertexSector 82, surface.surface_p2 trimmed surface cache history 15; was FindVertexSector 69 + EdgeCurveOnClassify 28). PARTIAL: difftest 1528 cases (28 fixture + 1500 generated: SetProps/Gaps/Stage2/Stage3 scripts, SmLoopProps::SetProps of every loop (op 8), seeded state, ReSet, HasProps, HasProblems, IsPoint3dOnPole, loop tree build + FindItem, on one-face B-reps with a plane or SmSurfOfRevolution surface (cylinder, cone with/without apex, sphere, torus; open or closed) owned by the face, a vertex loop or edge loops with holes, face tolerances up to 1e9; 9 fixture cases are FFI-guard regression cases, UB-131 and UB-128), 1314 pass bit-identical (max_rel_err 0), 0 failed, 214 deferred (topology.face_p4 210: SmFace::HasSeamProblem for closed faces in SetProps_Stage2; query.face_p4 4: SmFace::ClassifyLoops in SetProps_Stage3); --perturb 1314/1314 detected. Upstream defects UB-131 (SetProps_Gaps early return makes the Stage2/3 face re-run ineffective) and UB-128 (SetProps_Stage3 on a plane with a degenerate domain and edge loops segfaults in ClassifyLoops; the guard rejects a plane side below 1e-6 when Stage 3 can be reached); the guard rejects the crashing scripts. Snapshots compare the flat corner points too; no generated surface has a nonzero m_lBadFlatCorners (harness limitation, findings.md). Stage 3 is compared only through op 8 (direct Stage 3 cases defer at ClassifyLoops). Added Surface::get_singularities_of_face (face tolerance for the owned surface; other UNPORTED(topology.face_p2) tolerance stubs on surfaces unchanged). Wired: topology.loopuse (PR #102), SmSurface::GetSingularities (surface.surface_p3), SmFace::GetVertices (query.face_p2 co-port). Remaining UNPORTED stubs: topology.face_p4 HasSeamProblem, query.face_p4 ClassifyLoops, intersect.curve_class_p2 CurveClassification::ReSet (dead branch). No UNPORTED(heal.face_props) stubs existed on main. FaceProps is generic over a LoopObject handle; the loop tree is an arena (Looptree). CO-PORTED in heal.face_props (PR #122): SmMixedLinkType, SmTriedLinkType, SmHealerOpType (SmHealProbArray.h) -> heal::face_props::{MixedLinkType, TriedLinkType, HealerOpType} (owner heal.heal_data_p2 extends); SmContainmentType (SmTypes.h) -> heal::face_props::ContainmentType. 25 fns; largest: SmFaceProps::SetProps_Stage3, SmFaceProps::SetProps_Stage2, SmLoopProps::SetProps, SmFaceProps::ReSet; unit-SCC #22 (220 units); 100 upstream test entry points reach this unit PR #137 (intersect.curve_class_p4): the shim's face builder, seed and snapshot moved verbatim into harness/cpp/shim/sc_one_face_brep.h, shared with the intersect.curve_class_p4 shim; the runner's FaceIn, rust_brep, ffi_face, snapshot, seed and gen_face are pub(crate) for that runner, the ffi gained encode_face and pub seed_ok. Re-run: 1528 cases, 1314 pass, 214 deferred (query.face_p4 4, topology.face_p4 210), 0 failed, --perturb SENSITIVE 1314/1314. Merge of query.face_p3 (PR #132): query.face_p4 and query.face_p3 wired, the 4 ClassifyLoops deferrals reach further: 1528 cases, 1317 pass, 211 deferred (topology.face_p4 210, topology.face_p2 1), 0 failed. topology.face_p4: FaceProps::set_props_stage2_with takes the SetProps_Stage2 HasSeamProblem call as a callback (face::p4::has_seam_problem passes itself); set_props_stage2 keeps the stub for callers without classification objects." +notes = "PR #700 cache-wrapper rebase on e0266d5af (2026-10-25): 1528 native cases, 1505 matching, 8 explicit WR-263 stage-marker differences, 15 surface.surface_p2 deferrals, zero unexpected. Raw outputs prove each of the four newly reachable cases differs only in incomplete markers; all statuses and other outputs agree. Cached and public Stage3 failure retries are regression-tested. Earlier validation: PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 1528 native cases, 1435 matching, 4 explicit known, 89 deferred, zero unexpected. Six remaining Rust regressions pass; failed topology/healing outputs are not consumed and incomplete Stage3 can retry. Earlier validation: [2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1528 native cases: 1427 match, 97 existing UNPORTED deferrals and 4 explicit WR-263 diagnostic stage-marker differences. Failed Stage3 preserves status 1040 and partial diagnostics while resetting its in-progress marker from 5 to 4 for retry; ClassifyLoops errors now propagate before output arrays are read.] 2026-10-25 (PR #863, snapshot surfaces keep their face owner, plan/findings.md 'snapshot surfaces keep their face owner'): gen0646, gen0790, gen1312 pass; GREEN heal.face_props cases=1528 passed=1528 failed=0 deferred=0 max_rel_err=0e0 with_values=1519 no_values=9 vacuous=0. 2026-10-24 (wire: trimmed surface cache history; the intersect::curve_class::p7 tolerance-growth deferral removed, plan/findings.md 'trimmed surface cache history: tolerance-growth deferral removed'), Linux: of 15 surface.surface_p2 deferrals 12 pass, 3 are known mismatches (gen0646, gen0790, gen1312: torus seam FindVertexSector). PARTIAL heal.face_props cases=1528 passed=1525 failed=0 deferred=0 max_rel_err=1e0 with_values=1516 no_values=9 vacuous=0 known_mismatch=3. PR #697 (2026-10-24, face surfaces keep their trimmed caches): 1528 cases, 1439 pass, 0 failed, 89 deferred (topology.face_p4 88, intersect.curve_class_p4 1); the 15 cache-history (tolerance growth) deferrals go: IntersectVertices' gap reads now run with the surface owner cleared, so the standalone trimmed cache upstream keeps is made there and reused (8 pass, the rest defer further on at FindVertexSector). PR #639 (2026-10-23): the runner builds the down face use side (SmBrepConstructor), which fixes the seam EDGE-vs-VERTEX snap; difftest 1528 cases, 1431 pass, 0 failed, 97 deferred (topology.face_p4 FindVertexSector 82, surface.surface_p2 trimmed surface cache history 15; was FindVertexSector 69 + EdgeCurveOnClassify 28). PARTIAL: difftest 1528 cases (28 fixture + 1500 generated: SetProps/Gaps/Stage2/Stage3 scripts, SmLoopProps::SetProps of every loop (op 8), seeded state, ReSet, HasProps, HasProblems, IsPoint3dOnPole, loop tree build + FindItem, on one-face B-reps with a plane or SmSurfOfRevolution surface (cylinder, cone with/without apex, sphere, torus; open or closed) owned by the face, a vertex loop or edge loops with holes, face tolerances up to 1e9; 9 fixture cases are FFI-guard regression cases, UB-131 and UB-128), 1314 pass bit-identical (max_rel_err 0), 0 failed, 214 deferred (topology.face_p4 210: SmFace::HasSeamProblem for closed faces in SetProps_Stage2; query.face_p4 4: SmFace::ClassifyLoops in SetProps_Stage3); --perturb 1314/1314 detected. Upstream defects UB-131 (SetProps_Gaps early return makes the Stage2/3 face re-run ineffective) and UB-128 (SetProps_Stage3 on a plane with a degenerate domain and edge loops segfaults in ClassifyLoops; the guard rejects a plane side below 1e-6 when Stage 3 can be reached); the guard rejects the crashing scripts. Snapshots compare the flat corner points too; no generated surface has a nonzero m_lBadFlatCorners (harness limitation, findings.md). Stage 3 is compared only through op 8 (direct Stage 3 cases defer at ClassifyLoops). Added Surface::get_singularities_of_face (face tolerance for the owned surface; other UNPORTED(topology.face_p2) tolerance stubs on surfaces unchanged). Wired: topology.loopuse (PR #102), SmSurface::GetSingularities (surface.surface_p3), SmFace::GetVertices (query.face_p2 co-port). Remaining UNPORTED stubs: topology.face_p4 HasSeamProblem, query.face_p4 ClassifyLoops, intersect.curve_class_p2 CurveClassification::ReSet (dead branch). No UNPORTED(heal.face_props) stubs existed on main. FaceProps is generic over a LoopObject handle; the loop tree is an arena (Looptree). CO-PORTED in heal.face_props (PR #122): SmMixedLinkType, SmTriedLinkType, SmHealerOpType (SmHealProbArray.h) -> heal::face_props::{MixedLinkType, TriedLinkType, HealerOpType} (owner heal.heal_data_p2 extends); SmContainmentType (SmTypes.h) -> heal::face_props::ContainmentType. 25 fns; largest: SmFaceProps::SetProps_Stage3, SmFaceProps::SetProps_Stage2, SmLoopProps::SetProps, SmFaceProps::ReSet; unit-SCC #22 (220 units); 100 upstream test entry points reach this unit PR #137 (intersect.curve_class_p4): the shim's face builder, seed and snapshot moved verbatim into harness/cpp/shim/sc_one_face_brep.h, shared with the intersect.curve_class_p4 shim; the runner's FaceIn, rust_brep, ffi_face, snapshot, seed and gen_face are pub(crate) for that runner, the ffi gained encode_face and pub seed_ok. Re-run: 1528 cases, 1314 pass, 214 deferred (query.face_p4 4, topology.face_p4 210), 0 failed, --perturb SENSITIVE 1314/1314. Merge of query.face_p3 (PR #132): query.face_p4 and query.face_p3 wired, the 4 ClassifyLoops deferrals reach further: 1528 cases, 1317 pass, 211 deferred (topology.face_p4 210, topology.face_p2 1), 0 failed. topology.face_p4: FaceProps::set_props_stage2_with takes the SetProps_Stage2 HasSeamProblem call as a callback (face::p4::has_seam_problem passes itself); set_props_stage2 keeps the stub for callers without classification objects." From 024a700412a132bee6942df8d6cc8a4b4cbf1ac4 Mon Sep 17 00:00:00 2001 From: Kirill Frolov Date: Sun, 25 Oct 2026 15:55:33 +0300 Subject: [PATCH 6/8] docs: record healing validation after cache-owner rebase Retain current main coverage notes and the intentional partial retry contract. Confirm all eight known differences remain exclusively stage markers; all 1528 native cases now run without deferrals or unexpected failures. Co-Authored-By: Codex --- plan/units/heal.face_props.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plan/units/heal.face_props.toml b/plan/units/heal.face_props.toml index 07fafba4af..3b48bcc7ca 100644 --- a/plan/units/heal.face_props.toml +++ b/plan/units/heal.face_props.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_trimming_tools", "SM_API_test:TestSmTrimSurfaceWith3 status = "partial" claimed_by = "" claimed_at = "" -notes = "PR #700 cache-wrapper rebase on e0266d5af (2026-10-25): 1528 native cases, 1505 matching, 8 explicit WR-263 stage-marker differences, 15 surface.surface_p2 deferrals, zero unexpected. Raw outputs prove each of the four newly reachable cases differs only in incomplete markers; all statuses and other outputs agree. Cached and public Stage3 failure retries are regression-tested. Earlier validation: PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 1528 native cases, 1435 matching, 4 explicit known, 89 deferred, zero unexpected. Six remaining Rust regressions pass; failed topology/healing outputs are not consumed and incomplete Stage3 can retry. Earlier validation: [2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1528 native cases: 1427 match, 97 existing UNPORTED deferrals and 4 explicit WR-263 diagnostic stage-marker differences. Failed Stage3 preserves status 1040 and partial diagnostics while resetting its in-progress marker from 5 to 4 for retry; ClassifyLoops errors now propagate before output arrays are read.] 2026-10-25 (PR #863, snapshot surfaces keep their face owner, plan/findings.md 'snapshot surfaces keep their face owner'): gen0646, gen0790, gen1312 pass; GREEN heal.face_props cases=1528 passed=1528 failed=0 deferred=0 max_rel_err=0e0 with_values=1519 no_values=9 vacuous=0. 2026-10-24 (wire: trimmed surface cache history; the intersect::curve_class::p7 tolerance-growth deferral removed, plan/findings.md 'trimmed surface cache history: tolerance-growth deferral removed'), Linux: of 15 surface.surface_p2 deferrals 12 pass, 3 are known mismatches (gen0646, gen0790, gen1312: torus seam FindVertexSector). PARTIAL heal.face_props cases=1528 passed=1525 failed=0 deferred=0 max_rel_err=1e0 with_values=1516 no_values=9 vacuous=0 known_mismatch=3. PR #697 (2026-10-24, face surfaces keep their trimmed caches): 1528 cases, 1439 pass, 0 failed, 89 deferred (topology.face_p4 88, intersect.curve_class_p4 1); the 15 cache-history (tolerance growth) deferrals go: IntersectVertices' gap reads now run with the surface owner cleared, so the standalone trimmed cache upstream keeps is made there and reused (8 pass, the rest defer further on at FindVertexSector). PR #639 (2026-10-23): the runner builds the down face use side (SmBrepConstructor), which fixes the seam EDGE-vs-VERTEX snap; difftest 1528 cases, 1431 pass, 0 failed, 97 deferred (topology.face_p4 FindVertexSector 82, surface.surface_p2 trimmed surface cache history 15; was FindVertexSector 69 + EdgeCurveOnClassify 28). PARTIAL: difftest 1528 cases (28 fixture + 1500 generated: SetProps/Gaps/Stage2/Stage3 scripts, SmLoopProps::SetProps of every loop (op 8), seeded state, ReSet, HasProps, HasProblems, IsPoint3dOnPole, loop tree build + FindItem, on one-face B-reps with a plane or SmSurfOfRevolution surface (cylinder, cone with/without apex, sphere, torus; open or closed) owned by the face, a vertex loop or edge loops with holes, face tolerances up to 1e9; 9 fixture cases are FFI-guard regression cases, UB-131 and UB-128), 1314 pass bit-identical (max_rel_err 0), 0 failed, 214 deferred (topology.face_p4 210: SmFace::HasSeamProblem for closed faces in SetProps_Stage2; query.face_p4 4: SmFace::ClassifyLoops in SetProps_Stage3); --perturb 1314/1314 detected. Upstream defects UB-131 (SetProps_Gaps early return makes the Stage2/3 face re-run ineffective) and UB-128 (SetProps_Stage3 on a plane with a degenerate domain and edge loops segfaults in ClassifyLoops; the guard rejects a plane side below 1e-6 when Stage 3 can be reached); the guard rejects the crashing scripts. Snapshots compare the flat corner points too; no generated surface has a nonzero m_lBadFlatCorners (harness limitation, findings.md). Stage 3 is compared only through op 8 (direct Stage 3 cases defer at ClassifyLoops). Added Surface::get_singularities_of_face (face tolerance for the owned surface; other UNPORTED(topology.face_p2) tolerance stubs on surfaces unchanged). Wired: topology.loopuse (PR #102), SmSurface::GetSingularities (surface.surface_p3), SmFace::GetVertices (query.face_p2 co-port). Remaining UNPORTED stubs: topology.face_p4 HasSeamProblem, query.face_p4 ClassifyLoops, intersect.curve_class_p2 CurveClassification::ReSet (dead branch). No UNPORTED(heal.face_props) stubs existed on main. FaceProps is generic over a LoopObject handle; the loop tree is an arena (Looptree). CO-PORTED in heal.face_props (PR #122): SmMixedLinkType, SmTriedLinkType, SmHealerOpType (SmHealProbArray.h) -> heal::face_props::{MixedLinkType, TriedLinkType, HealerOpType} (owner heal.heal_data_p2 extends); SmContainmentType (SmTypes.h) -> heal::face_props::ContainmentType. 25 fns; largest: SmFaceProps::SetProps_Stage3, SmFaceProps::SetProps_Stage2, SmLoopProps::SetProps, SmFaceProps::ReSet; unit-SCC #22 (220 units); 100 upstream test entry points reach this unit PR #137 (intersect.curve_class_p4): the shim's face builder, seed and snapshot moved verbatim into harness/cpp/shim/sc_one_face_brep.h, shared with the intersect.curve_class_p4 shim; the runner's FaceIn, rust_brep, ffi_face, snapshot, seed and gen_face are pub(crate) for that runner, the ffi gained encode_face and pub seed_ok. Re-run: 1528 cases, 1314 pass, 214 deferred (query.face_p4 4, topology.face_p4 210), 0 failed, --perturb SENSITIVE 1314/1314. Merge of query.face_p3 (PR #132): query.face_p4 and query.face_p3 wired, the 4 ClassifyLoops deferrals reach further: 1528 cases, 1317 pass, 211 deferred (topology.face_p4 210, topology.face_p2 1), 0 failed. topology.face_p4: FaceProps::set_props_stage2_with takes the SetProps_Stage2 HasSeamProblem call as a callback (face::p4::has_seam_problem passes itself); set_props_stage2 keeps the stub for callers without classification objects." +notes = "PR #700 rebase on 86f600878 (2026-10-25): fresh full native run has 1528 cases, 1520 matching, 8 existing individually verified WR-263 incomplete-stage marker differences, zero deferred and zero unexpected failures. All recorded mismatches remain exclusively C++ marker 5 versus Rust marker 4; main's cache-owner fixes eliminate the 15 earlier deferrals. Preserve main's green validation notes below, but this PR remains partial because its retry contract intentionally differs on those eight cases. Earlier validation: PR #700 cache-wrapper rebase on e0266d5af (2026-10-25): 1528 native cases, 1505 matching, 8 explicit WR-263 stage-marker differences, 15 surface.surface_p2 deferrals, zero unexpected. Raw outputs prove each of the four newly reachable cases differs only in incomplete markers; all statuses and other outputs agree. Cached and public Stage3 failure retries are regression-tested. Earlier validation: PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 1528 native cases, 1435 matching, 4 explicit known, 89 deferred, zero unexpected. Six remaining Rust regressions pass; failed topology/healing outputs are not consumed and incomplete Stage3 can retry. Earlier validation: [2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1528 native cases: 1427 match, 97 existing UNPORTED deferrals and 4 explicit WR-263 diagnostic stage-marker differences. Failed Stage3 preserves status 1040 and partial diagnostics while resetting its in-progress marker from 5 to 4 for retry; ClassifyLoops errors now propagate before output arrays are read.] 2026-10-25 (PR #863, snapshot surfaces keep their face owner, plan/findings.md 'snapshot surfaces keep their face owner'): gen0646, gen0790, gen1312 pass; GREEN heal.face_props cases=1528 passed=1528 failed=0 deferred=0 max_rel_err=0e0 with_values=1519 no_values=9 vacuous=0. 2026-10-24 (wire: trimmed surface cache history; the intersect::curve_class::p7 tolerance-growth deferral removed, plan/findings.md 'trimmed surface cache history: tolerance-growth deferral removed'), Linux: of 15 surface.surface_p2 deferrals 12 pass, 3 are known mismatches (gen0646, gen0790, gen1312: torus seam FindVertexSector). PARTIAL heal.face_props cases=1528 passed=1525 failed=0 deferred=0 max_rel_err=1e0 with_values=1516 no_values=9 vacuous=0 known_mismatch=3. PR #697 (2026-10-24, face surfaces keep their trimmed caches): 1528 cases, 1439 pass, 0 failed, 89 deferred (topology.face_p4 88, intersect.curve_class_p4 1); the 15 cache-history (tolerance growth) deferrals go: IntersectVertices' gap reads now run with the surface owner cleared, so the standalone trimmed cache upstream keeps is made there and reused (8 pass, the rest defer further on at FindVertexSector). PR #639 (2026-10-23): the runner builds the down face use side (SmBrepConstructor), which fixes the seam EDGE-vs-VERTEX snap; difftest 1528 cases, 1431 pass, 0 failed, 97 deferred (topology.face_p4 FindVertexSector 82, surface.surface_p2 trimmed surface cache history 15; was FindVertexSector 69 + EdgeCurveOnClassify 28). PARTIAL: difftest 1528 cases (28 fixture + 1500 generated: SetProps/Gaps/Stage2/Stage3 scripts, SmLoopProps::SetProps of every loop (op 8), seeded state, ReSet, HasProps, HasProblems, IsPoint3dOnPole, loop tree build + FindItem, on one-face B-reps with a plane or SmSurfOfRevolution surface (cylinder, cone with/without apex, sphere, torus; open or closed) owned by the face, a vertex loop or edge loops with holes, face tolerances up to 1e9; 9 fixture cases are FFI-guard regression cases, UB-131 and UB-128), 1314 pass bit-identical (max_rel_err 0), 0 failed, 214 deferred (topology.face_p4 210: SmFace::HasSeamProblem for closed faces in SetProps_Stage2; query.face_p4 4: SmFace::ClassifyLoops in SetProps_Stage3); --perturb 1314/1314 detected. Upstream defects UB-131 (SetProps_Gaps early return makes the Stage2/3 face re-run ineffective) and UB-128 (SetProps_Stage3 on a plane with a degenerate domain and edge loops segfaults in ClassifyLoops; the guard rejects a plane side below 1e-6 when Stage 3 can be reached); the guard rejects the crashing scripts. Snapshots compare the flat corner points too; no generated surface has a nonzero m_lBadFlatCorners (harness limitation, findings.md). Stage 3 is compared only through op 8 (direct Stage 3 cases defer at ClassifyLoops). Added Surface::get_singularities_of_face (face tolerance for the owned surface; other UNPORTED(topology.face_p2) tolerance stubs on surfaces unchanged). Wired: topology.loopuse (PR #102), SmSurface::GetSingularities (surface.surface_p3), SmFace::GetVertices (query.face_p2 co-port). Remaining UNPORTED stubs: topology.face_p4 HasSeamProblem, query.face_p4 ClassifyLoops, intersect.curve_class_p2 CurveClassification::ReSet (dead branch). No UNPORTED(heal.face_props) stubs existed on main. FaceProps is generic over a LoopObject handle; the loop tree is an arena (Looptree). CO-PORTED in heal.face_props (PR #122): SmMixedLinkType, SmTriedLinkType, SmHealerOpType (SmHealProbArray.h) -> heal::face_props::{MixedLinkType, TriedLinkType, HealerOpType} (owner heal.heal_data_p2 extends); SmContainmentType (SmTypes.h) -> heal::face_props::ContainmentType. 25 fns; largest: SmFaceProps::SetProps_Stage3, SmFaceProps::SetProps_Stage2, SmLoopProps::SetProps, SmFaceProps::ReSet; unit-SCC #22 (220 units); 100 upstream test entry points reach this unit PR #137 (intersect.curve_class_p4): the shim's face builder, seed and snapshot moved verbatim into harness/cpp/shim/sc_one_face_brep.h, shared with the intersect.curve_class_p4 shim; the runner's FaceIn, rust_brep, ffi_face, snapshot, seed and gen_face are pub(crate) for that runner, the ffi gained encode_face and pub seed_ok. Re-run: 1528 cases, 1314 pass, 214 deferred (query.face_p4 4, topology.face_p4 210), 0 failed, --perturb SENSITIVE 1314/1314. Merge of query.face_p3 (PR #132): query.face_p4 and query.face_p3 wired, the 4 ClassifyLoops deferrals reach further: 1528 cases, 1317 pass, 211 deferred (topology.face_p4 210, topology.face_p2 1), 0 failed. topology.face_p4: FaceProps::set_props_stage2_with takes the SetProps_Stage2 HasSeamProblem call as a callback (face::p4::has_seam_problem passes itself); set_props_stage2 keeps the stub for callers without classification objects." From d51488e42d0ac47c827666be15d434b73ee082e3 Mon Sep 17 00:00:00 2001 From: Kirill Frolov Date: Sun, 25 Oct 2026 22:15:57 +0300 Subject: [PATCH 7/8] docs: migrate topology failure records to per-entry layout Keep all PR findings, seven upstream reports and eight known healing cases in the new per-record directories. Preserve merged archive records, update references, and renumber the PR reports to WR-260 through WR-266 to avoid existing WR-257 and WR-259. Kernel code and fixture inputs are unchanged. Co-Authored-By: Codex --- .../known_mismatches/heal.face_props.toml | 41 ++++++ ...n-status--addendum-2026-10-24-after-704.md | 45 +++++++ ...status--addendum-2026-10-25-cache-owner.md | 22 ++++ ...atus--addendum-2026-10-25-cache-wrapper.md | 49 +++++++ ...general--521-topology-evaluation-status.md | 120 ++++++++++++++++++ plan/units/heal.face_props.toml | 2 +- .../WR-260--edge-reversal-failed-status.md | 10 ++ .../WR-261--vertex-failed-angle-predicates.md | 14 ++ .../WR-262--loop-failed-uv-corners.md | 9 ++ .../WR-263--edgeuse-failed-loop-gap.md | 9 ++ ...-264--edgeuse-failed-distance-deviation.md | 9 ++ .../WR-265--stage3-failed-classification.md | 9 ++ .../WR-266--stage3-failed-retry-marker.md | 13 ++ 13 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 crates/difftest/known_mismatches/heal.face_props.toml create mode 100644 plan/findings/general--521-topology-evaluation-status--addendum-2026-10-24-after-704.md create mode 100644 plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-owner.md create mode 100644 plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-wrapper.md create mode 100644 plan/findings/general--521-topology-evaluation-status.md create mode 100644 plan/upstream-issues/WR-260--edge-reversal-failed-status.md create mode 100644 plan/upstream-issues/WR-261--vertex-failed-angle-predicates.md create mode 100644 plan/upstream-issues/WR-262--loop-failed-uv-corners.md create mode 100644 plan/upstream-issues/WR-263--edgeuse-failed-loop-gap.md create mode 100644 plan/upstream-issues/WR-264--edgeuse-failed-distance-deviation.md create mode 100644 plan/upstream-issues/WR-265--stage3-failed-classification.md create mode 100644 plan/upstream-issues/WR-266--stage3-failed-retry-marker.md diff --git a/crates/difftest/known_mismatches/heal.face_props.toml b/crates/difftest/known_mismatches/heal.face_props.toml new file mode 100644 index 0000000000..caf5e99a2a --- /dev/null +++ b/crates/difftest/known_mismatches/heal.face_props.toml @@ -0,0 +1,41 @@ +# PR #700: eight individually verified incomplete Stage3 marker differences. + +[[mismatch]] +unit = "heal.face_props" +case = "gen0345" +why = "WR-266 / #521: failed Stage3 retains in-progress marker 5 in C++; Rust retains the same 1040 error but restores incomplete stage 4 for retry; other compared outputs agree (plan/findings/general--521-topology-evaluation-status.md: Failed topology evaluations and incomplete healing stages)" + +[[mismatch]] +unit = "heal.face_props" +case = "gen0821" +why = "WR-266 / #521: failed Stage3 retains in-progress marker 5 in C++; Rust retains the same 1040 error but restores incomplete stage 4 for retry; other compared outputs agree (plan/findings/general--521-topology-evaluation-status.md: Failed topology evaluations and incomplete healing stages)" + +[[mismatch]] +unit = "heal.face_props" +case = "gen0957" +why = "WR-266 / #521: failed Stage3 retains in-progress marker 5 in C++; Rust retains the same 1040 error but restores incomplete stage 4 for retry; other compared outputs agree (plan/findings/general--521-topology-evaluation-status.md: Failed topology evaluations and incomplete healing stages)" + +[[mismatch]] +unit = "heal.face_props" +case = "stage3" +why = "WR-266 / #521: failed Stage3 retains in-progress marker 5 in C++; Rust retains the same 1040 error but restores incomplete stage 4 for retry; other compared outputs agree (plan/findings/general--521-topology-evaluation-status.md: Failed topology evaluations and incomplete healing stages)" + +[[mismatch]] +unit = "heal.face_props" +case = "gen0241" +why = "WR-266 / #521, newly reachable after the cache-wrapper rebase on e0266d5af: values.v[84] is C++ 5 versus Rust 4 after the same 1040 failure; all other outputs agree. Raw outputs checked against both current main and the PR (plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-wrapper.md)." + +[[mismatch]] +unit = "heal.face_props" +case = "gen0662" +why = "WR-266 / #521, newly reachable after the cache-wrapper rebase on e0266d5af: values.v[1], [84] and [167] are C++ 5 versus Rust 4; the initial 1040 failure and subsequent 1000 statuses are identical, and all other outputs agree. Raw outputs checked against both current main and the PR (plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-wrapper.md)." + +[[mismatch]] +unit = "heal.face_props" +case = "gen1105" +why = "WR-266 / #521, newly reachable after the cache-wrapper rebase on e0266d5af: values.v[84] is C++ 5 versus Rust 4 after the same 1040 failure; all other outputs agree. Raw outputs checked against both current main and the PR (plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-wrapper.md)." + +[[mismatch]] +unit = "heal.face_props" +case = "gen1377" +why = "WR-266 / #521, newly reachable after the cache-wrapper rebase on e0266d5af: values.v[167], [250] and [333] are C++ 5 versus Rust 4; the initial 1040 failure and subsequent successful query statuses are identical, and all other outputs agree. Raw outputs checked against both current main and the PR (plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-wrapper.md)." diff --git a/plan/findings/general--521-topology-evaluation-status--addendum-2026-10-24-after-704.md b/plan/findings/general--521-topology-evaluation-status--addendum-2026-10-24-after-704.md new file mode 100644 index 0000000000..c12bfb1206 --- /dev/null +++ b/plan/findings/general--521-topology-evaluation-status--addendum-2026-10-24-after-704.md @@ -0,0 +1,45 @@ +# general: #521 follow-up after merged #704: current scope of PR #700 (2026-10-24) +Updates: plan/findings/general--521-topology-evaluation-status.md + +The earlier vertex error-policy draft and its 43 WR-261 native differences above +are historical validation of that draft, not the current branch contract. Main +#704's complete vertex implementation, regression, 70 end-tangent fixtures, shim, +FFI and query-loop documentation are preserved byte for byte. The vertex error +propagation change and its one regression were removed, as was the runner's +angle-error normalization. All 43 formerly listed vertex cases now compare equal +under the merged contract: the strict harness first rejected all 43 as stale +passes, then they alone were removed and the full 5598-case vertex unit reran +green. The 20 constant-segment fixtures remain matching parity coverage; they +exercise zero tangents/angle failures, not failed GetEndTangent evaluation. + +Six remaining regressions cover edge reversal, loop UV corner evaluation, edge-use +loop gaps and deviation, and healing classification/retry state. Their four kernel +modules and the 20 fixture inputs are byte-identical to the original tested +implementation. The four WR-266 healing differences remain explicit and partial: +status 1040 is unchanged, and only the incomplete marker changes from 5 to 4 +(including gen0345's later snapshot). This was rechecked from raw native outputs. +The earlier C++ WR-261 problem record remains valid; its altered Rust predicate +policy is outside this follow-up. + +Current validation on `0cdc106a4a941c31be2f48402084dca3a72832f0`: fmt, default/native +workspace Clippy with warnings denied, 2094 kernel unit tests, wasm, 48 runner +tests, and five integration passes (one existing partial-sphere/torus case ignored) +under the optimized difftest profile used by CI all pass. Fresh native 13-unit +validation has 37684 cases: 37163 matching, six explicit known differences (four +WR-266 healing markers, two existing main Rust/harness MoveSeam limitations), +462 UNPORTED deferrals and 53 native guard rejections (37 UB-137 plus 16 existing +input guards), zero unexpected failures. Twelve units come from the initial +matrix; vertex was rerun strictly after removing the proven stale entries. No +production source or fixture changed between those runs. The current main cache +wiring is retained; old 6ec validation figures above retain their original base. +No canonical CI pass or full macOS --green is claimed. + + +Rejection-category clarification for the current PR #700 matrix: the 53 native +rejections comprise 37 UB-137 crash traps, one HANG-39 input guard and +15 missing-entity cases. Earlier references to the latter 16 as “input guards” +were imprecise. No parity comparison ran for those rejected cases; none is counted +as a matching result. The combined sixteen-fix matrix separately has 994 UNPORTED +deferrals plus six `harness:offset-result-adapter` deferrals, and 95 rejections +(37 crash traps, 25 other input guards, 18 construction failures, 15 missing +entities). The recorded totals and zero-unexpected-failure results are unchanged. diff --git a/plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-owner.md b/plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-owner.md new file mode 100644 index 0000000000..ccdb2e06d7 --- /dev/null +++ b/plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-owner.md @@ -0,0 +1,22 @@ +# general: PR #700 after the cache-owner rebase (2026-10-25) +Updates: plan/findings/general--521-topology-evaluation-status.md + +Rebased on main `86f600878928a11af49e6a8c7b15cdcfc4f46044`; the conflict +was in `plan/units/heal.face_props.toml`. Both main's newer green validation notes +and this PR's historical evidence are retained. The four kernel modules and +vertex fixture corpus are byte-identical to the preceding PR head. Main's +cache-owner changes eliminate the 15 previous healing deferrals. + +Fresh full native `heal.face_props` on source head +`87c6dbfe947173e2de195c8536090dd8051dfc18`: **1,528 cases, 1,520 matching, +eight existing known differences, zero deferred, zero unexpected failures**. +Every recorded mismatch in those eight cases was checked: all are exclusively +`values.v` stage markers, C++ 5 versus Rust 4, with no additional mismatches hidden +by a record. No case, exception, guard or comparison rule changed. This PR's +healing unit stays partial for that deliberate retry-state difference, even though +current main's unmodified unit is green. + +Formatting, default workspace/all-target Clippy with warnings denied, all 2,139 +kernel unit tests and wasm pass. Other native figures and the previously observed +query.face_p4/gen0360 baseline failure retain their earlier validation bases; they +were not re-certified by this targeted healing run. Canonical CI remains pending. diff --git a/plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-wrapper.md b/plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-wrapper.md new file mode 100644 index 0000000000..3b5283a610 --- /dev/null +++ b/plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-wrapper.md @@ -0,0 +1,49 @@ +# general: PR #700 cache-wrapper rebase (2026-10-25) +Updates: plan/findings/general--521-topology-evaluation-status.md + +Rebased on main `e0266d5af928b70bf6967afc442cbc7f8eb53796` after the anticipated +`FaceProps::set_props_stage3_cached` conflict appeared. The public wrapper still +creates a query cache; `set_props_with_seam` and `classify_loops_cached` keep the +caller's cache. Failure rollback belongs in the cached entry point, so both routes +reset incomplete Stage3A to Stage2 and preserve classification errors. The existing +retry regression now exercises both routes. All 2,133 kernel unit tests, formatting, +default workspace/all-target Clippy with warnings denied, and wasm pass. + +Current main makes four more WR-266 healing-marker differences reachable. A local +raw-output probe ran each exact input against the unchanged C++ reference, this PR, +and current main. Main matches C++; this PR differs only at the listed stage markers. +Statuses, other values, integer fields and topology are identical. These are +individual known mismatches, never parity passes; `heal.face_props` stays partial. + +| Case | Seed | Differing offsets in `values.v` (C++ 5, Rust 4) | Statuses immediately before those markers | +|---|---|---|---| +| gen0241 | 16562362309779923184 | 84 | 1040 | +| gen0662 | 16562362309779923605 | 1, 84, 167 | 1040, 1000, 1000 | +| gen1105 | 16562362309779924048 | 84 | 1040 | +| gen1377 | 16562362309779924320 | 167, 250, 333 | 1040, 0, 0 | + +The later snapshots retain the earlier incomplete marker even when a subsequent +query succeeds; a successful query does not certify Stage3. All four original +WR-266 entries and all 40 current-main entries remain unchanged. No tolerance, +floor, native guard, shim or fixture was changed. + +A separate `query.face_p4/gen0360` comparison (seed 4634847627957580463) fails +identically on this PR and on exact current main: `ints.classify_loop_ios[8]` is +C++ 1 versus Rust 2, `[10]` is 1 versus 0, `[11]` is 1 versus 5, and +`ints.looptree.len` is 17 versus 9. Raw C++ outputs and raw Rust outputs are each +identical across the two revisions. This pre-existing classification failure was +not suppressed or added to the exception list. Reproduce with +`difftest --unit query.face_p4 --filter gen0360`. The full query unit has 500 cases: +492 matching, two pre-existing known mismatches, five deferrals and this one +unexpected baseline failure. Canonical CI and a clean full native matrix are not +claimed. + +The broader native run completed topology.edge (1,915/1,915), topology.vertex +(5,598/5,598) and topology.loopuse (8,854/8,854) with zero failures or deferrals, then +was stopped to investigate the targeted healing/query failures. Remaining units +from that attempted matrix are not represented as completed validation. + +Fresh complete heal.face_props rerun after recording those four individually proven +differences: 1,528 cases, 1,505 matching, eight explicit known stage-marker +differences, 15 existing surface.surface_p2 deferrals, zero unexpected failures. +The eight remain genuine mismatches; the unit remains partial. diff --git a/plan/findings/general--521-topology-evaluation-status.md b/plan/findings/general--521-topology-evaluation-status.md new file mode 100644 index 0000000000..71b5a3e792 --- /dev/null +++ b/plan/findings/general--521-topology-evaluation-status.md @@ -0,0 +1,120 @@ +# general: Failed topology evaluations and incomplete healing stages (#521, PR #700) (2026-10-24) + +Migrated PR #700 record. Later addenda describe the final scope; older draft +claims and validation figures below retain their original bases. Its upstream +issue IDs WR-257 through WR-263 are renumbered WR-260 through WR-266 to avoid +existing main records; the underlying evidence is unchanged. + +Related addenda: [scope after #704](general--521-topology-evaluation-status--addendum-2026-10-24-after-704.md), [cache-wrapper integration](general--521-topology-evaluation-status--addendum-2026-10-25-cache-wrapper.md), [cache-owner validation](general--521-topology-evaluation-status--addendum-2026-10-25-cache-owner.md). + +A Hermite wire edge is a small defined failure: `SmHermiteCurve` inherits the base +`ReverseParameterization`, which returns `SM_ERR`. `SmEdge::ReverseOrientation` +drops that status, installs the unset output interval and flips the edge uses. +The Rust port followed it. The fixed Rust call propagates the curve error before +changing the edge interval, orientations or UV trims. The curve method's own +mutation behavior remains its implementation's responsibility. + +The other implemented paths named by #521 now require successful evaluations: + +- `vertex::are_edges_tangent` and `is_topological_vertex` propagate tangent and + `AngleBetween` errors. A zero/too-small tangent no longer supplies a zero-angle + certificate. Healthy parallel edges, cusps and the angle thresholds keep their + previous behavior. +- `loopuse::uv_corner_boundaries` evaluates fresh endpoints for each edge use and + propagates trim/3D evaluation and fallback point-drop errors. An unsuccessful + drop returns `SM_ERR`. Trim acquisition already propagated its error. +- `edgeuse::p1::start_point_uv_loop_gap` propagates both curve evaluations and both + drops, including unsuccessful drops; `calc_dist_and_dev` propagates failed first + derivatives. A distance-only request still uses the stored solution value. +- `FaceProps::set_props_stage3` restores the owned classification tree and problem + type, then propagates classification errors before reading its output arrays. + An error restores the stage marker to Stage2 while keeping partial diagnostics. + A subsequent call retries instead of returning success from a stale Stage3A. + +`query::loop::contains_uv_point` already propagates evaluation failures and uses +Unknown safely for failed trim acquisition. `HasDogLegNearPole` has no Rust +implementation to audit yet. The change covers the implemented paths above. + +### Reproduction and reference evidence + +Seven regressions in the existing test modules failed before these changes: +failed Hermite reversal, original tangent/angle error propagation, failed trim +corner evaluation (at the first and a later edge use), UV/3D gap evaluation, +failed surface derivatives, failed loop classification, and an incomplete healing +stage retried after a dangling loopuse. The first five failed together; the two +healing regressions were separately reproduced before their production change. + +A standalone probe against the unchanged pinned C++ libraries on macOS arm64 +constructed the Hermite from points `(0,0,0)`, `(1,0,0)` and directions `(1,0,0)`, +`(0,1,0)`, then called `CreateWireEdgeFromCurve` over `[0,1]` and reversed the edge: + +```text +Hermite reverse status=1001 output=[1e+20,-1e+20] +wire status=1000 +edge before=[0,1] after=[1e+20,-1e+20] orientation=1 -> 2 +tangent length=0 angle status=1007 value=0 +tangent length=1e-12 angle status=1007 value=0 +tangent length=1 angle status=1000 value=0 +``` + +Twenty new cases in the existing `topology.vertex/constructed.json` cover constant +B-splines at either end of a chain, both orientations and endpoint selectors, plus +healthy line controls. The preserved pre-fix binary matched C++ in all 20; the fixed +kernel matches 14 and propagates the failed angle in six. Additional existing cases +exercise point curves and `1e-12` tangents. Raw C++/Rust outputs were inspected for +all 43 angle differences and four healing differences, plus the 14 new controls. +The temporary diagnostic example was removed. + +| Angle case group | Cases | C++ TRUE | C++ FALSE | Rust result | +|---|---:|---:|---:|---| +| New constant-tangent cases | 6 | 6 | 0 | `status_1007` | +| Existing `point_curve_*` cases | 7 | 7 | 0 | `status_1007` | +| Existing `rev_tiny_scale*` cases | 30 | 16 | 14 | `status_1007` | + +Those 43 cases concern lost failure statuses. Fourteen C++ predicates already +return FALSE; their difference is the missing error status. The difftest runner +preserves this specific angle error as `status_1007` instead of translating it to +the null-dereference sentinel or a harness error. Every case still runs both sides. +The 43 WR-261 records keep `topology.vertex` partial. + +Four existing healing cases (`stage3`, `gen0345`, `gen0821`, `gen0957`) retain status +1040 on both sides. Their only compared differences are the incomplete stage marker: +C++ 5 (Stage3A), Rust 4 (Stage2), including a subsequent diagnostic snapshot in +`gen0345`. The four WR-266 records keep these diagnostic differences explicit. +The unsafe empty-NURB native operations used by some Rust-only regressions were +not executed against C++; their failed-output paths were checked in source. + +### Validation on `6ecdaaa21467a52461f1d39bc3e4c0d9a745988b` plus this change + +Fmt, default/native workspace Clippy with warnings denied, wasm, 2086 kernel unit +tests, five integration tests and 48 runner tests pass. The existing unsupported +partial-sphere/torus integration case remains ignored. The native matrix covered +13 units; vertex and face_props were then rerun after recording their observed +C++ differences and marking vertex partial. The other eleven passing units' code, +fixtures and per-unit known records did not change. + +| Unit | Cases | Matching | Known | Deferred | Rejected | +|---|---:|---:|---:|---:|---:| +| topology.edge | 1787 | 1787 | 0 | 0 | 0 | +| topology.vertex | 5528 | 5485 | 43 | 0 | 0 | +| topology.loopuse | 8854 | 8854 | 0 | 0 | 0 | +| topology.edgeuse_p1 | 4402 | 4402 | 0 | 0 | 0 | +| topology.edgeuse_p3 | 4789 | 4789 | 0 | 0 | 0 | +| topology.face_p1 | 2315 | 2276 | 2 | 37 | 0 | +| query.loop | 3040 | 3040 | 0 | 0 | 0 | +| query.face_p3 | 1199 | 1087 | 0 | 112 | 0 | +| query.face_p4 | 500 | 395 | 0 | 105 | 0 | +| boolean.merge_p1 | 248 | 243 | 0 | 5 | 0 | +| topology.brep_p1 | 2627 | 2461 | 0 | 150 | 16 | +| topology.brep_p5 | 797 | 795 | 0 | 2 | 0 | +| heal.face_props | 1528 | 1427 | 4 | 97 | 0 | + +Total: 37,614 cases, 37,041 matching (including 646 both-error comparisons), +49 known differences, 508 deferrals (471 UNPORTED plus 37 native UB-137 crash guards), +16 native input-guard rejections and +zero unexpected failures; five units green and eight partial. The 49 consist of +47 differences explained above plus two existing main Rust/harness MoveSeam +limitations in `topology.face_p1`. Those two are not C++ defect claims. +All 17 pre-existing main mismatch records remain. No comparator tolerance, floor, +reference implementation or FFI rejection guard was changed. The full macOS +`--green` reader/hash/deformation baseline (#448-450) was not claimed. diff --git a/plan/units/heal.face_props.toml b/plan/units/heal.face_props.toml index 3b48bcc7ca..b80bf064ac 100644 --- a/plan/units/heal.face_props.toml +++ b/plan/units/heal.face_props.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_trimming_tools", "SM_API_test:TestSmTrimSurfaceWith3 status = "partial" claimed_by = "" claimed_at = "" -notes = "PR #700 rebase on 86f600878 (2026-10-25): fresh full native run has 1528 cases, 1520 matching, 8 existing individually verified WR-263 incomplete-stage marker differences, zero deferred and zero unexpected failures. All recorded mismatches remain exclusively C++ marker 5 versus Rust marker 4; main's cache-owner fixes eliminate the 15 earlier deferrals. Preserve main's green validation notes below, but this PR remains partial because its retry contract intentionally differs on those eight cases. Earlier validation: PR #700 cache-wrapper rebase on e0266d5af (2026-10-25): 1528 native cases, 1505 matching, 8 explicit WR-263 stage-marker differences, 15 surface.surface_p2 deferrals, zero unexpected. Raw outputs prove each of the four newly reachable cases differs only in incomplete markers; all statuses and other outputs agree. Cached and public Stage3 failure retries are regression-tested. Earlier validation: PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 1528 native cases, 1435 matching, 4 explicit known, 89 deferred, zero unexpected. Six remaining Rust regressions pass; failed topology/healing outputs are not consumed and incomplete Stage3 can retry. Earlier validation: [2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1528 native cases: 1427 match, 97 existing UNPORTED deferrals and 4 explicit WR-263 diagnostic stage-marker differences. Failed Stage3 preserves status 1040 and partial diagnostics while resetting its in-progress marker from 5 to 4 for retry; ClassifyLoops errors now propagate before output arrays are read.] 2026-10-25 (PR #863, snapshot surfaces keep their face owner, plan/findings.md 'snapshot surfaces keep their face owner'): gen0646, gen0790, gen1312 pass; GREEN heal.face_props cases=1528 passed=1528 failed=0 deferred=0 max_rel_err=0e0 with_values=1519 no_values=9 vacuous=0. 2026-10-24 (wire: trimmed surface cache history; the intersect::curve_class::p7 tolerance-growth deferral removed, plan/findings.md 'trimmed surface cache history: tolerance-growth deferral removed'), Linux: of 15 surface.surface_p2 deferrals 12 pass, 3 are known mismatches (gen0646, gen0790, gen1312: torus seam FindVertexSector). PARTIAL heal.face_props cases=1528 passed=1525 failed=0 deferred=0 max_rel_err=1e0 with_values=1516 no_values=9 vacuous=0 known_mismatch=3. PR #697 (2026-10-24, face surfaces keep their trimmed caches): 1528 cases, 1439 pass, 0 failed, 89 deferred (topology.face_p4 88, intersect.curve_class_p4 1); the 15 cache-history (tolerance growth) deferrals go: IntersectVertices' gap reads now run with the surface owner cleared, so the standalone trimmed cache upstream keeps is made there and reused (8 pass, the rest defer further on at FindVertexSector). PR #639 (2026-10-23): the runner builds the down face use side (SmBrepConstructor), which fixes the seam EDGE-vs-VERTEX snap; difftest 1528 cases, 1431 pass, 0 failed, 97 deferred (topology.face_p4 FindVertexSector 82, surface.surface_p2 trimmed surface cache history 15; was FindVertexSector 69 + EdgeCurveOnClassify 28). PARTIAL: difftest 1528 cases (28 fixture + 1500 generated: SetProps/Gaps/Stage2/Stage3 scripts, SmLoopProps::SetProps of every loop (op 8), seeded state, ReSet, HasProps, HasProblems, IsPoint3dOnPole, loop tree build + FindItem, on one-face B-reps with a plane or SmSurfOfRevolution surface (cylinder, cone with/without apex, sphere, torus; open or closed) owned by the face, a vertex loop or edge loops with holes, face tolerances up to 1e9; 9 fixture cases are FFI-guard regression cases, UB-131 and UB-128), 1314 pass bit-identical (max_rel_err 0), 0 failed, 214 deferred (topology.face_p4 210: SmFace::HasSeamProblem for closed faces in SetProps_Stage2; query.face_p4 4: SmFace::ClassifyLoops in SetProps_Stage3); --perturb 1314/1314 detected. Upstream defects UB-131 (SetProps_Gaps early return makes the Stage2/3 face re-run ineffective) and UB-128 (SetProps_Stage3 on a plane with a degenerate domain and edge loops segfaults in ClassifyLoops; the guard rejects a plane side below 1e-6 when Stage 3 can be reached); the guard rejects the crashing scripts. Snapshots compare the flat corner points too; no generated surface has a nonzero m_lBadFlatCorners (harness limitation, findings.md). Stage 3 is compared only through op 8 (direct Stage 3 cases defer at ClassifyLoops). Added Surface::get_singularities_of_face (face tolerance for the owned surface; other UNPORTED(topology.face_p2) tolerance stubs on surfaces unchanged). Wired: topology.loopuse (PR #102), SmSurface::GetSingularities (surface.surface_p3), SmFace::GetVertices (query.face_p2 co-port). Remaining UNPORTED stubs: topology.face_p4 HasSeamProblem, query.face_p4 ClassifyLoops, intersect.curve_class_p2 CurveClassification::ReSet (dead branch). No UNPORTED(heal.face_props) stubs existed on main. FaceProps is generic over a LoopObject handle; the loop tree is an arena (Looptree). CO-PORTED in heal.face_props (PR #122): SmMixedLinkType, SmTriedLinkType, SmHealerOpType (SmHealProbArray.h) -> heal::face_props::{MixedLinkType, TriedLinkType, HealerOpType} (owner heal.heal_data_p2 extends); SmContainmentType (SmTypes.h) -> heal::face_props::ContainmentType. 25 fns; largest: SmFaceProps::SetProps_Stage3, SmFaceProps::SetProps_Stage2, SmLoopProps::SetProps, SmFaceProps::ReSet; unit-SCC #22 (220 units); 100 upstream test entry points reach this unit PR #137 (intersect.curve_class_p4): the shim's face builder, seed and snapshot moved verbatim into harness/cpp/shim/sc_one_face_brep.h, shared with the intersect.curve_class_p4 shim; the runner's FaceIn, rust_brep, ffi_face, snapshot, seed and gen_face are pub(crate) for that runner, the ffi gained encode_face and pub seed_ok. Re-run: 1528 cases, 1314 pass, 214 deferred (query.face_p4 4, topology.face_p4 210), 0 failed, --perturb SENSITIVE 1314/1314. Merge of query.face_p3 (PR #132): query.face_p4 and query.face_p3 wired, the 4 ClassifyLoops deferrals reach further: 1528 cases, 1317 pass, 211 deferred (topology.face_p4 210, topology.face_p2 1), 0 failed. topology.face_p4: FaceProps::set_props_stage2_with takes the SetProps_Stage2 HasSeamProblem call as a callback (face::p4::has_seam_problem passes itself); set_props_stage2 keeps the stub for callers without classification objects." +notes = "PR #700 rebase on 86f600878 (2026-10-25): fresh full native run has 1528 cases, 1520 matching, 8 existing individually verified WR-266 incomplete-stage marker differences, zero deferred and zero unexpected failures. All recorded mismatches remain exclusively C++ marker 5 versus Rust marker 4; main's cache-owner fixes eliminate the 15 earlier deferrals. Preserve main's green validation notes below, but this PR remains partial because its retry contract intentionally differs on those eight cases. Earlier validation: PR #700 cache-wrapper rebase on e0266d5af (2026-10-25): 1528 native cases, 1505 matching, 8 explicit WR-266 stage-marker differences, 15 surface.surface_p2 deferrals, zero unexpected. Raw outputs prove each of the four newly reachable cases differs only in incomplete markers; all statuses and other outputs agree. Cached and public Stage3 failure retries are regression-tested. Earlier validation: PR #700 narrowed follow-up after #704 on 0cdc106a4a941c31be2f48402084dca3a72832f0: 1528 native cases, 1435 matching, 4 explicit known, 89 deferred, zero unexpected. Six remaining Rust regressions pass; failed topology/healing outputs are not consumed and incomplete Stage3 can retry. Earlier validation: [2026-10-24 PR #700 / #521, verified on 6ecdaaa21467: 1528 native cases: 1427 match, 97 existing UNPORTED deferrals and 4 explicit WR-266 diagnostic stage-marker differences. Failed Stage3 preserves status 1040 and partial diagnostics while resetting its in-progress marker from 5 to 4 for retry; ClassifyLoops errors now propagate before output arrays are read.] 2026-10-25 (PR #863, snapshot surfaces keep their face owner, plan/findings.md 'snapshot surfaces keep their face owner'): gen0646, gen0790, gen1312 pass; GREEN heal.face_props cases=1528 passed=1528 failed=0 deferred=0 max_rel_err=0e0 with_values=1519 no_values=9 vacuous=0. 2026-10-24 (wire: trimmed surface cache history; the intersect::curve_class::p7 tolerance-growth deferral removed, plan/findings.md 'trimmed surface cache history: tolerance-growth deferral removed'), Linux: of 15 surface.surface_p2 deferrals 12 pass, 3 are known mismatches (gen0646, gen0790, gen1312: torus seam FindVertexSector). PARTIAL heal.face_props cases=1528 passed=1525 failed=0 deferred=0 max_rel_err=1e0 with_values=1516 no_values=9 vacuous=0 known_mismatch=3. PR #697 (2026-10-24, face surfaces keep their trimmed caches): 1528 cases, 1439 pass, 0 failed, 89 deferred (topology.face_p4 88, intersect.curve_class_p4 1); the 15 cache-history (tolerance growth) deferrals go: IntersectVertices' gap reads now run with the surface owner cleared, so the standalone trimmed cache upstream keeps is made there and reused (8 pass, the rest defer further on at FindVertexSector). PR #639 (2026-10-23): the runner builds the down face use side (SmBrepConstructor), which fixes the seam EDGE-vs-VERTEX snap; difftest 1528 cases, 1431 pass, 0 failed, 97 deferred (topology.face_p4 FindVertexSector 82, surface.surface_p2 trimmed surface cache history 15; was FindVertexSector 69 + EdgeCurveOnClassify 28). PARTIAL: difftest 1528 cases (28 fixture + 1500 generated: SetProps/Gaps/Stage2/Stage3 scripts, SmLoopProps::SetProps of every loop (op 8), seeded state, ReSet, HasProps, HasProblems, IsPoint3dOnPole, loop tree build + FindItem, on one-face B-reps with a plane or SmSurfOfRevolution surface (cylinder, cone with/without apex, sphere, torus; open or closed) owned by the face, a vertex loop or edge loops with holes, face tolerances up to 1e9; 9 fixture cases are FFI-guard regression cases, UB-131 and UB-128), 1314 pass bit-identical (max_rel_err 0), 0 failed, 214 deferred (topology.face_p4 210: SmFace::HasSeamProblem for closed faces in SetProps_Stage2; query.face_p4 4: SmFace::ClassifyLoops in SetProps_Stage3); --perturb 1314/1314 detected. Upstream defects UB-131 (SetProps_Gaps early return makes the Stage2/3 face re-run ineffective) and UB-128 (SetProps_Stage3 on a plane with a degenerate domain and edge loops segfaults in ClassifyLoops; the guard rejects a plane side below 1e-6 when Stage 3 can be reached); the guard rejects the crashing scripts. Snapshots compare the flat corner points too; no generated surface has a nonzero m_lBadFlatCorners (harness limitation, findings.md). Stage 3 is compared only through op 8 (direct Stage 3 cases defer at ClassifyLoops). Added Surface::get_singularities_of_face (face tolerance for the owned surface; other UNPORTED(topology.face_p2) tolerance stubs on surfaces unchanged). Wired: topology.loopuse (PR #102), SmSurface::GetSingularities (surface.surface_p3), SmFace::GetVertices (query.face_p2 co-port). Remaining UNPORTED stubs: topology.face_p4 HasSeamProblem, query.face_p4 ClassifyLoops, intersect.curve_class_p2 CurveClassification::ReSet (dead branch). No UNPORTED(heal.face_props) stubs existed on main. FaceProps is generic over a LoopObject handle; the loop tree is an arena (Looptree). CO-PORTED in heal.face_props (PR #122): SmMixedLinkType, SmTriedLinkType, SmHealerOpType (SmHealProbArray.h) -> heal::face_props::{MixedLinkType, TriedLinkType, HealerOpType} (owner heal.heal_data_p2 extends); SmContainmentType (SmTypes.h) -> heal::face_props::ContainmentType. 25 fns; largest: SmFaceProps::SetProps_Stage3, SmFaceProps::SetProps_Stage2, SmLoopProps::SetProps, SmFaceProps::ReSet; unit-SCC #22 (220 units); 100 upstream test entry points reach this unit PR #137 (intersect.curve_class_p4): the shim's face builder, seed and snapshot moved verbatim into harness/cpp/shim/sc_one_face_brep.h, shared with the intersect.curve_class_p4 shim; the runner's FaceIn, rust_brep, ffi_face, snapshot, seed and gen_face are pub(crate) for that runner, the ffi gained encode_face and pub seed_ok. Re-run: 1528 cases, 1314 pass, 214 deferred (query.face_p4 4, topology.face_p4 210), 0 failed, --perturb SENSITIVE 1314/1314. Merge of query.face_p3 (PR #132): query.face_p4 and query.face_p3 wired, the 4 ClassifyLoops deferrals reach further: 1528 cases, 1317 pass, 211 deferred (topology.face_p4 210, topology.face_p2 1), 0 failed. topology.face_p4: FaceProps::set_props_stage2_with takes the SetProps_Stage2 HasSeamProblem call as a callback (face::p4::has_seam_problem passes itself); set_props_stage2 keeps the stub for callers without classification objects." diff --git a/plan/upstream-issues/WR-260--edge-reversal-failed-status.md b/plan/upstream-issues/WR-260--edge-reversal-failed-status.md new file mode 100644 index 0000000000..413138302a --- /dev/null +++ b/plan/upstream-issues/WR-260--edge-reversal-failed-status.md @@ -0,0 +1,10 @@ +#### WR-260. `SmEdge::ReverseOrientation` installs an unset interval after a failed curve reversal +- **Severity:** Wrong result +- **Area:** Topology and healing +- **How found:** Source audit of #521, with the regression/reference evidence below. +- **Location:** `source/SMLib/src/SmEdge.cpp:3640`, the unchecked `ReverseParameterization` call before `m_vInterval = sNewIvl` and the edgeuse loop. +- **Trigger:** a wire edge over `[0,1]` on a valid `SmHermiteCurve`, whose inherited reversal returns `SM_ERR` (1001). +- **What happens:** the interval becomes `[1e20,-1e20]`, orientations flip and UV trims are deleted despite the failed curve edit. +- **Confirmed:** yes, unchanged pinned C++ on macOS arm64: wire creation returns 1000, direct reversal returns 1001, interval `[0,1] -> [1e20,-1e20]`, primary orientation `1 -> 2`. +- **Rust port:** propagates the curve error before downstream topology edits; the existing edge test module verifies interval/uses/trims remain intact for this failure. #521 / PR #700; findings: Failed topology evaluations and incomplete healing stages. +- **Findings:** plan/findings/general--521-topology-evaluation-status.md diff --git a/plan/upstream-issues/WR-261--vertex-failed-angle-predicates.md b/plan/upstream-issues/WR-261--vertex-failed-angle-predicates.md new file mode 100644 index 0000000000..a61708e9ae --- /dev/null +++ b/plan/upstream-issues/WR-261--vertex-failed-angle-predicates.md @@ -0,0 +1,14 @@ +#### WR-261. Vertex tangent predicates consume a failed angle as zero +- **Severity:** Wrong result +- **Area:** Topology and healing +- **How found:** Source audit of #521, with the regression/reference evidence below. +- **Location:** `source/SMLib/src/SmVertex.cpp`, `AreEdgesTangent` and `IsTopologicalVertex`, unchecked `AngleBetween` results. +- **Trigger:** a zero derivative (constant B-spline) or a `1e-12` tangent below the angle routine's scaled zero threshold. +- **What happens:** `AngleBetween` returns 1007 and writes zero; the predicates return a boolean computed from that sentinel. The failed calculation cannot certify tangency or removability. +- **Confirmed:** yes, direct unchanged C++ angle calls reject lengths 0 and 1e-12 (1007, angle 0) and accept length 1 (1000). Native fixtures yield 29 TRUE and 14 FALSE predicates while Rust propagates 1007; 43 explicit known records retain all comparisons. The FALSE results differ only by their lost failure status. +- **Rust port:** propagates tangent and angle errors; related UB-106 and the failed-tangent portion of WR-92 now preserve original evaluation errors too. Cusps retain existing behavior. #521 / PR #700; findings: Failed topology evaluations and incomplete healing stages. +- **Findings:** plan/findings/general--521-topology-evaluation-status.md + +**Current PR scope:** The Rust-port paragraph above describes the historical draft. +PR #700 now preserves #704's vertex predicate contract; all 43 former vertex +exceptions were proven matching and removed. See plan/findings/general--521-topology-evaluation-status--addendum-2026-10-24-after-704.md. diff --git a/plan/upstream-issues/WR-262--loop-failed-uv-corners.md b/plan/upstream-issues/WR-262--loop-failed-uv-corners.md new file mode 100644 index 0000000000..d519e28a53 --- /dev/null +++ b/plan/upstream-issues/WR-262--loop-failed-uv-corners.md @@ -0,0 +1,9 @@ +#### WR-262. `GetUVCornerBoundaries` reuses failed endpoint evaluations +- **Severity:** Wrong result +- **Area:** Topology and healing +- **How found:** Source audit of #521, with the regression/reference evidence below. +- **Location:** `source/SMLib/src/SmLoop.cpp:386`, the trim endpoint evaluations and 3D point-drop fallback in `GetUVCornerBoundaries`. +- **What happens:** failed evaluations/drops leave unset or preceding-edgeuse endpoints available for boundary classification. The trim pointer acquisition status is also ignored upstream. +- **Confirmed:** no native reproduction; source and the #521 report; Rust regressions with an empty trim fail before the fix at both the first and a later edgeuse. Empty-NURB C++ calls were not executed because their reference behavior is unsafe. +- **Rust port:** trim acquisition already propagated failure; endpoint evaluation and fallback drop failures now propagate too, with fresh locals per edgeuse. #521 / PR #700; findings: Failed topology evaluations and incomplete healing stages. +- **Findings:** plan/findings/general--521-topology-evaluation-status.md diff --git a/plan/upstream-issues/WR-263--edgeuse-failed-loop-gap.md b/plan/upstream-issues/WR-263--edgeuse-failed-loop-gap.md new file mode 100644 index 0000000000..a7bf17f833 --- /dev/null +++ b/plan/upstream-issues/WR-263--edgeuse-failed-loop-gap.md @@ -0,0 +1,9 @@ +#### WR-263. `GetStartPointUVLoopGap` computes a gap from failed evaluations or drops +- **Severity:** Wrong result +- **Area:** Topology and healing +- **How found:** Source audit of #521, with the regression/reference evidence below. +- **Location:** `source/SMLib/src/SmEdgeuse.cpp`, `GetStartPointUVLoopGap`, both trim evaluations and the fallback 3D evaluation/drop sequence. +- **What happens:** unwritten/default endpoints, or the first drop's guess retained by the second failed drop, become a reported gap. +- **Confirmed:** no native reproduction; source and #521; Rust-only regressions exercise both trim endpoints and both 3D endpoints, with a healthy zero-gap control. Empty-NURB native calls were not executed. +- **Rust port:** propagates evaluation/drop statuses and refuses unsuccessful drops before reading their UV results. All 4402 defined native edgeuse_p1 cases still match. #521 / PR #700; findings: Failed topology evaluations and incomplete healing stages. +- **Findings:** plan/findings/general--521-topology-evaluation-status.md diff --git a/plan/upstream-issues/WR-264--edgeuse-failed-distance-deviation.md b/plan/upstream-issues/WR-264--edgeuse-failed-distance-deviation.md new file mode 100644 index 0000000000..f03f33ca81 --- /dev/null +++ b/plan/upstream-issues/WR-264--edgeuse-failed-distance-deviation.md @@ -0,0 +1,9 @@ +#### WR-264. `sm_CalcDistAndDev` ignores a failed surface derivative evaluation +- **Severity:** Wrong result +- **Area:** Topology and healing +- **How found:** Source audit of #521, with the regression/reference evidence below. +- **Location:** `source/SMLib/src/SmEdgeuse.cpp:1065`, `sm_CalcDistAndDev` before its normal and distance calculations. +- **What happens:** a failed `Evaluate1stDerivatives` supplies no valid surface point or derivatives, but the helper continues with their defaults. +- **Confirmed:** no native reproduction; source and #521; the safe Rust empty-surface regression previously returned successful `(-1,-1)` and now preserves the derivative error. The unsafe empty native surface was not evaluated. +- **Rust port:** requires successful derivatives; normal fallback for a successfully evaluated singular point and distance-only requests keep their existing paths. #521 / PR #700; findings: Failed topology evaluations and incomplete healing stages. +- **Findings:** plan/findings/general--521-topology-evaluation-status.md diff --git a/plan/upstream-issues/WR-265--stage3-failed-classification.md b/plan/upstream-issues/WR-265--stage3-failed-classification.md new file mode 100644 index 0000000000..593c1621b6 --- /dev/null +++ b/plan/upstream-issues/WR-265--stage3-failed-classification.md @@ -0,0 +1,9 @@ +#### WR-265. `SetProps_Stage3` consumes failed loop-classification outputs +- **Severity:** Wrong result +- **Area:** Topology and healing +- **How found:** Source audit of #521, with the regression/reference evidence below. +- **Location:** `source/SMLib/src/SmFaceProps.cpp`, `SetProps_Stage3`, the unchecked `ClassifyLoops` status before its classification arrays are read. +- **What happens:** failed classification output can be consumed or an empty classification can be finalized as successful properties. +- **Confirmed:** no native reproduction; source and #521; a Rust pre-fix regression on a plane with no loops demonstrates lost `ClassifyLoops` status 1001. A live C++ empty-face probe was not run. +- **Rust port:** restores the owned tree/problem type, then propagates the original classification error before reading output arrays. #521 / PR #700; findings: Failed topology evaluations and incomplete healing stages. +- **Findings:** plan/findings/general--521-topology-evaluation-status.md diff --git a/plan/upstream-issues/WR-266--stage3-failed-retry-marker.md b/plan/upstream-issues/WR-266--stage3-failed-retry-marker.md new file mode 100644 index 0000000000..b2c3d80342 --- /dev/null +++ b/plan/upstream-issues/WR-266--stage3-failed-retry-marker.md @@ -0,0 +1,13 @@ +#### WR-266. A failed `SetProps_Stage3` retains its in-progress marker and skips retry +- **Severity:** Wrong result +- **Area:** Topology and healing +- **How found:** Source audit of #521, with the regression/reference evidence below. +- **Location:** `source/SMLib/src/SmFaceProps.cpp`, `SetProps_Stage3`, `m_ePropStage = SM_PROPSTAGE_3a` and its early `>= SM_PROPSTAGE_3a` guard. +- **What happens:** an error leaves Stage3A set, so a later call can return success without completing the stage. +- **Confirmed:** yes; a pre-fix Rust regression fails on retry after a dangling loopuse. Four actual native comparisons retain error 1040 on both sides but expose marker 5 (C++) versus 4 (Rust); other compared outputs agree. No live C++ dangling-loopuse probe was run. +- **Rust port:** restores Stage2 on error without clearing partial diagnostics, so the next call retries. Four diagnostic differences stay explicit known records and the healer unit remains partial. #521 / PR #700; findings: Failed topology evaluations and incomplete healing stages. +- **Findings:** plan/findings/general--521-topology-evaluation-status.md + +**Current validation:** Eight existing stage-marker differences are explicit known +records; the latest targeted run has 1,520 matching and eight known cases, no +deferrals or unexpected failures. See plan/findings/general--521-topology-evaluation-status--addendum-2026-10-25-cache-owner.md. From ebe6c718f629e2ca95c19a128fd545363a39ffe0 Mon Sep 17 00:00:00 2001 From: Kirill Frolov Date: Mon, 26 Oct 2026 00:16:29 +0300 Subject: [PATCH 8/8] docs: move the PR classification report past merged WR-265 Keep the merged ManifoldBoolean WR-265 unchanged and renumber only this PR's Stage3 classification report to WR-268. Marker WR-266 and all eight mismatch cases are unchanged. Co-Authored-By: Codex --- plan/findings/general--521-topology-evaluation-status.md | 5 +++-- ...sification.md => WR-268--stage3-failed-classification.md} | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) rename plan/upstream-issues/{WR-265--stage3-failed-classification.md => WR-268--stage3-failed-classification.md} (93%) diff --git a/plan/findings/general--521-topology-evaluation-status.md b/plan/findings/general--521-topology-evaluation-status.md index 71b5a3e792..1da103a7f8 100644 --- a/plan/findings/general--521-topology-evaluation-status.md +++ b/plan/findings/general--521-topology-evaluation-status.md @@ -2,8 +2,9 @@ Migrated PR #700 record. Later addenda describe the final scope; older draft claims and validation figures below retain their original bases. Its upstream -issue IDs WR-257 through WR-263 are renumbered WR-260 through WR-266 to avoid -existing main records; the underlying evidence is unchanged. +issue IDs WR-257 through WR-263 are now WR-260 through WR-264, WR-266 and +WR-268. The Stage3-classification report moved from WR-265 to WR-268 after main +merged a different WR-265; the underlying evidence is unchanged. Related addenda: [scope after #704](general--521-topology-evaluation-status--addendum-2026-10-24-after-704.md), [cache-wrapper integration](general--521-topology-evaluation-status--addendum-2026-10-25-cache-wrapper.md), [cache-owner validation](general--521-topology-evaluation-status--addendum-2026-10-25-cache-owner.md). diff --git a/plan/upstream-issues/WR-265--stage3-failed-classification.md b/plan/upstream-issues/WR-268--stage3-failed-classification.md similarity index 93% rename from plan/upstream-issues/WR-265--stage3-failed-classification.md rename to plan/upstream-issues/WR-268--stage3-failed-classification.md index 593c1621b6..2a40aee657 100644 --- a/plan/upstream-issues/WR-265--stage3-failed-classification.md +++ b/plan/upstream-issues/WR-268--stage3-failed-classification.md @@ -1,4 +1,4 @@ -#### WR-265. `SetProps_Stage3` consumes failed loop-classification outputs +#### WR-268. `SetProps_Stage3` consumes failed loop-classification outputs - **Severity:** Wrong result - **Area:** Topology and healing - **How found:** Source audit of #521, with the regression/reference evidence below.