diff --git a/crates/aura-cli/src/scaffold.rs b/crates/aura-cli/src/scaffold.rs index a058c00..08e4e21 100644 --- a/crates/aura-cli/src/scaffold.rs +++ b/crates/aura-cli/src/scaffold.rs @@ -115,22 +115,24 @@ const LIB_RS: &str = r#"//! __NAME__ — an aura research project: node logic + //! this cdylib per invocation and merges it with the std vocabulary. use aura_core::{ - Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, + Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder, ScalarKind, }; -/// One-input f64 pass-through. Emits `None` until its input has a value. -pub struct Identity { +/// One-input scalar gain: emits `input * factor`. Emits `None` until its +/// input has a value (warm-up filter, C8). +pub struct Scale { + factor: f64, out: [Cell; 1], } -impl Identity { - pub fn new() -> Self { - Self { out: [Cell::from_f64(0.0)] } +impl Scale { + pub fn new(factor: f64) -> Self { + Self { factor, out: [Cell::from_f64(0.0)] } } pub fn builder() -> PrimitiveBuilder { PrimitiveBuilder::new( - "__NS__::Identity", + "__NS__::Scale", NodeSchema { inputs: vec![PortSpec { kind: ScalarKind::F64, @@ -138,20 +140,14 @@ impl Identity { name: "value".into(), }], output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }], - params: vec![], + params: vec![ParamSpec { name: "factor".into(), kind: ScalarKind::F64 }], }, - |_| Box::new(Identity::new()), + |p| Box::new(Scale::new(p[0].f64())), ) } } -impl Default for Identity { - fn default() -> Self { - Self::new() - } -} - -impl Node for Identity { +impl Node for Scale { fn lookbacks(&self) -> Vec { vec![1] } @@ -160,23 +156,23 @@ impl Node for Identity { if w.is_empty() { return None; } - self.out[0] = Cell::from_f64(w[0]); + self.out[0] = Cell::from_f64(w[0] * self.factor); Some(&self.out) } fn label(&self) -> String { - "__NS__::Identity".to_string() + format!("__NS__::Scale({})", self.factor) } } fn vocabulary(type_id: &str) -> Option { match type_id { - "__NS__::Identity" => Some(Identity::builder()), + "__NS__::Scale" => Some(Scale::builder()), _ => None, } } fn type_ids() -> &'static [&'static str] { - &["__NS__::Identity"] + &["__NS__::Scale"] } aura_core::aura_project! { @@ -186,7 +182,7 @@ aura_core::aura_project! { } "#; -const SIGNAL_JSON: &str = r#"{"format_version":1,"blueprint":{"name":"__NAME_SNAKE___signal","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}},{"primitive":{"type":"__NS__::Identity"}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0},{"from":3,"to":4,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":4,"field":0,"name":"bias"}]}}"#; +const SIGNAL_JSON: &str = r#"{"format_version":1,"blueprint":{"name":"__NAME_SNAKE___signal","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}},{"primitive":{"type":"__NS__::Scale","bound":[{"pos":0,"name":"factor","kind":"F64","value":{"F64":1.0}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0},{"from":3,"to":4,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":4,"field":0,"name":"bias"}]}}"#; const CLAUDE_MD: &str = r#"# __NAME__ — an aura research project @@ -311,10 +307,10 @@ mod tests { assert!(!out.contains("__"), "unsubstituted token in: {out}"); } let lib = render(LIB_RS, &spec); - assert!(lib.contains("\"demo_lab::Identity\"")); + assert!(lib.contains("\"demo_lab::Scale\"")); assert!(lib.contains("namespace: \"demo_lab\"")); let json = render(SIGNAL_JSON, &spec); - assert!(json.contains("\"type\":\"demo_lab::Identity\"")); + assert!(json.contains("\"type\":\"demo_lab::Scale\"")); assert!(json.contains("\"name\":\"demo_lab_signal\"")); let cargo = render(CARGO_TOML, &spec); assert!(cargo.contains("name = \"demo-lab\"")); diff --git a/crates/aura-cli/tests/project_new.rs b/crates/aura-cli/tests/project_new.rs index c91fde2..bbcbb4e 100644 --- a/crates/aura-cli/tests/project_new.rs +++ b/crates/aura-cli/tests/project_new.rs @@ -64,7 +64,7 @@ fn new_scaffold_builds_and_runs_the_authoring_loop() { let introspect = aura(&["graph", "introspect", "--vocabulary"], &proj); assert!(introspect.status.success()); let text = String::from_utf8_lossy(&introspect.stdout); - assert!(text.contains("demo_lab::Identity"), "project id listed"); + assert!(text.contains("demo_lab::Scale"), "project id listed"); let _ = std::fs::remove_dir_all(&base); } @@ -136,8 +136,8 @@ fn new_namespace_override_reaches_the_built_vocabulary() { let introspect = aura(&["graph", "introspect", "--vocabulary"], &proj); assert!(introspect.status.success()); let text = String::from_utf8_lossy(&introspect.stdout); - assert!(text.contains("custom_ns::Identity"), "overridden namespace listed: {text}"); - assert!(!text.contains("demo_lab::Identity"), "default-derived namespace must not leak"); + assert!(text.contains("custom_ns::Scale"), "overridden namespace listed: {text}"); + assert!(!text.contains("demo_lab::Scale"), "default-derived namespace must not leak"); let _ = std::fs::remove_dir_all(&base); }