git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commite4fc366fb15a6a36f2c4f6da423f4d24a70abe13
parent90b6e4d16f
authorLucas Galante <[email protected]>
date2026-09-21 15:53
fix: a kernel's parent-parameter fallback reads the parent resolved

A Sphere template instance's kernel reads chf("Radius") by falling back to
its parent's parameter, and that read bypassed reference resolution: a
Radius of ch("Radius") reached the kernel as the reference string, which
parses as nothing, and the sphere stayed at its default. The fallback (and
the input child's read of its subnet's Input) now resolves the parent first.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 src/geometry.rs | 10 ++++++++++
 src/main.rs     | 24 ++++++++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/src/geometry.rs b/src/geometry.rs
index 060adb7..1005dd3 100644
--- a/src/geometry.rs
+++ b/src/geometry.rs
@@ -980,6 +980,10 @@ pub fn generate_single_node_geometry_with_errors(
                 visited.pop();
                 return Some(fed);
             }
+            // Resolved, like the kernel's parent read above: a subnet's
+            // Input may itself be a reference.
+            let resolved_parent = resolve_param_refs(root, parent, ocl_error);
+            let parent = resolved_parent.as_ref().unwrap_or(parent);
             let input_name = node_param_str(parent, "Input", "");
             if !input_name.is_empty() {
                 if let Some(input_node) = find_input_node(root, target, &input_name) {
@@ -4689,6 +4693,12 @@ pub fn resolve_opencl_geometry_with_errors(
             let mut val_str = node_param_str(target, &p.name, &p.default);
             if !target.params.iter().any(|p_def| p_def.name.eq_ignore_ascii_case(&p.name)) {
                 if let Some(parent) = find_parent_node(root, &target.id) {
+                    // The parent's value RESOLVED: a Sphere instance whose
+                    // Radius is ch("Radius") hands its kernel the subnet's
+                    // number, not the reference string (which parses as
+                    // nothing and left the kernel at its default).
+                    let resolved_parent = resolve_param_refs(root, parent, ocl_error);
+                    let parent = resolved_parent.as_ref().unwrap_or(parent);
                     val_str = node_param_str(parent, &p.name, &val_str);
                 }
             }
diff --git a/src/main.rs b/src/main.rs
index 07bfa85..d335af5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5602,6 +5602,30 @@ mod tests {
         }
     }
 
+    /// A Sphere TEMPLATE instance reads its kernel's chf("Radius") through
+    /// its parent's parameter — and that parameter may be a reference to the
+    /// subnet above, which has to be resolved before the kernel sees it.
+    #[test]
+    fn a_kernel_reads_a_reference_through_its_parent() {
+        let templates_root = crate::app::load_fs_tree();
+        let mut sphere = templates_root.children.iter().find(|t| t.name == "Sphere").unwrap().clone();
+        sphere.id = "sph".into();
+        sphere.name = "sphere1".into();
+        for c in &mut sphere.children {
+            c.id = format!("sph_{}", c.name);
+        }
+        sphere.params.iter_mut().find(|p| p.name == "Radius").unwrap().default = "ch(\"Radius\")".into();
+        let out = ref_node("o", "output1", "output", vec![("Input", "text", "sphere1")], vec![]);
+        let sub = ref_node("sub", "subnet1", "node", vec![("Input", "text", ""), ("Radius", "slider", "0.9")], vec![sphere, out]);
+        let root = ref_node("root", "root", "node", vec![], vec![sub]);
+        let (g, err) = eval(&root, &root.children[0]);
+        assert!(err.is_none(), "{err:?}");
+        let g = g.expect("the subnet evaluates");
+        let center = Vec3::new(0.0, 0.55, 0.0);
+        let r = g.positions().iter().map(|p| (Vec3::from(*p) - center).length()).fold(0.0, f32::max);
+        assert!((r - 0.9).abs() < 0.02, "the kernel got the subnet's radius, not the reference string: {r}");
+    }
+
     /// The params pane shows a referencing value as the text it is.
     #[test]
     fn param_display_shows_references_as_text() {