From 938fc1783a00a8dd30107780ddb3c38d0fabfabe Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Tue, 25 Aug 2026 20:27:39 +0000 Subject: [PATCH] Treat a single-lane leveled value as invariant under the addressed lane --- node-graph/graph-craft/src/document/value.rs | 22 ++++++++++++++++++++ node-graph/graph-craft/src/proto.rs | 6 ++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 583cc760d3..a2bff765a0 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -304,6 +304,28 @@ macro_rules! tagged_value { } } + /// The lane count of the level [`Self::to_edge`] serves, or `None` for a + /// scalar. At most one lane serves the same record at every index, so + /// the addressed lane cannot reach the value. + pub fn lane_count(&self) -> Option { + match self { + Self::TypeDefault(td) => { + let name = td.name.as_ref(); + if name == std::any::type_name::>() { return Some(0); } + if name == std::any::type_name::>() { return Some(0); } + if name == std::any::type_name::>>() { return Some(0); } + if name == std::any::type_name::>() { return Some(1); } + if name == std::any::type_name::>() { return Some(0); } + None + } + Self::F64Array(values) => Some(values.len()), + Self::Color(color) => Some(color.iter().count()), + Self::Gradient(_) => Some(1), + Self::BrushStrokes(strokes) => Some(strokes.len()), + _ => None, + } + } + /// Materializes the value as [`Self::to_dynany`] does, wrapped in a `ClonedNode` edge typed by [`Self::ty`]. pub fn to_edge(self) -> Result { match self { diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index f1763c55ee..147a991d6f 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -556,8 +556,10 @@ impl ProtoNetwork { // We pretend like we have already placed context modification nodes after ourselves because value nodes don't need to be cached ConstructionArgs::Value(value) => { let mut deps = own_deps; - // A leveled value serves its lanes by the innermost index. - if value.value_layout().is_some_and(|layout| layout.depth > 0) { + // A leveled value serves its lanes by the innermost index, unless + // it holds at most one lane to serve. + let leveled = value.value_layout().is_some_and(|layout| layout.depth > 0); + if leveled && value.lane_count().is_none_or(|lanes| lanes > 1) { deps |= core_types::context::ContextFeatures::INDEX; deps.index_levels |= core_types::context::IndexLevels::innermost(); }