spec: 0016 — blueprint-view label is the bare type (ascii-dag wide-label limit) (#31)

The amend chose `SMA(length)` (type + knob names) for the param-generic blueprint
view. Implementation surfaced that the ascii-dag 0.9.1 renderer writes a label
verbatim on one line (no multi-line — `render/ascii.rs::write_node` brackets the
raw string) and its Sugiyama subgraph layout overlaps two wide sibling boxes inside
a cluster (`[SMA(length[SMA(length)]`). Width is auto-computed but the cluster
packing does not honor it; the only spacing knob (`node_spacing`) is on the
deprecated config path, global, and width-independent — no robust option, and
domain labels grow unboundedly wide (`LinComb(weights[0], weights[1])`, deep
path-qualified names). Horizontal mode is already rejected (collapses fan-outs).

So `LeafFactory::label()` renders the bare node type (`SMA`). The tunable knobs are
surfaced by `param_space()`, not in the graph; the compiled view still labels built
nodes valued (`SMA(2)`) via `Node::label`. Correct C22 reading either way —
structure (now: type-only) before, values after.

refs #31
This commit is contained in:
2026-06-07 21:04:31 +02:00
parent bf5ff22371
commit b02f31cdd4
+23 -21
View File
@@ -62,13 +62,17 @@ lowering/wiring is unchanged in structure.
bound values, so the `aura graph` *blueprint view* (`render_blueprint`, pre-inline
cluster boxes) can no longer label a leaf `SMA(2)` — there is no `2` until a vector
is injected. The view instead renders the **param-generic** label from
`LeafFactory::label()`: the node type plus its tunable knob *names*, e.g.
`SMA(length)`, `Exposure(scale)`, bare `SimBroker`. Both SMAs of a cross render
identically as `SMA(length)` (they are the same recipe; their distinct values live
in the vector, and the graph's edges still distinguish them positionally). The
*compiled view* is unaffected: it renders post-build flat nodes via the unchanged
`Node::label()`, so a bound `SMA(2)` still shows there. This is the correct C22
reading — structure (param-generic) before a run, values (bound) after.
`LeafFactory::label()`: the **bare node type**, e.g. `SMA`, `Exposure`, `SimBroker`.
Both SMAs of a cross render identically as `[SMA]` (they are the same recipe; their
distinct values live in the vector, and the graph's edges still distinguish them
positionally). The tunable knobs are surfaced by `param_space()`, not in the graph.
The label is the bare type — not `SMA(length)` — because the `ascii-dag` renderer
writes a label verbatim on one line (no multi-line) and overlaps two wide sibling
boxes inside a cluster subgraph; a knob suffix garbles the view, and domain labels
grow unboundedly wide. The *compiled view* is unaffected: it renders post-build flat
nodes via the unchanged `Node::label()`, so a bound `SMA(2)` still shows there. This
is the correct C22 reading — structure (param-generic) before a run, values (bound)
after.
**Vestigial pre-build interface, removed.** `Composite::schema()` and the private
`BlueprintNode::schema()` derive a blueprint item's interface from its interior
@@ -168,16 +172,14 @@ impl LeafFactory {
pub fn params(&self) -> &[ParamSpec] { &self.params }
pub fn build(&self, p: &[Scalar]) -> Box<dyn Node> { (self.build)(p) }
/// The param-generic render label for the blueprint view (C22 "structure
/// before"): the node type plus its tunable param *names* — no values, a
/// value-empty recipe has none — e.g. `SMA(length)`, `LinComb(weights[0],
/// weights[1])`, or bare `SimBroker` when paramless.
/// before"): just the node type, e.g. `SMA`. A value-empty recipe has no
/// values to show; the tunable knobs are surfaced by `Blueprint::param_space`,
/// not in the graph. The label is the bare type because the `ascii-dag`
/// renderer writes a label verbatim on one line (no wrapping / multi-line) and
/// overlaps two wide sibling boxes inside a cluster subgraph — a knob suffix
/// (`SMA(length)`) garbles the view, and domain labels grow unboundedly wide.
pub fn label(&self) -> String {
if self.params.is_empty() {
self.name.to_string()
} else {
let knobs: Vec<&str> = self.params.iter().map(|p| p.name.as_str()).collect();
format!("{}({})", self.name, knobs.join(", "))
}
self.name.to_string()
}
}
```
@@ -247,7 +249,7 @@ pub enum CompileError {
| Crate / file | Change |
|---|---|
| `crates/aura-core/src/node.rs` | New `LeafFactory { name, params, build }` (+ `new`/`params`/`build`/`label`). `label()` renders the param-generic blueprint-view label from `name` + param knob names. `Node` trait unchanged — construction lives in the closure, not a new trait method. |
| `crates/aura-core/src/node.rs` | New `LeafFactory { name, params, build }` (+ `new`/`params`/`build`/`label`). `label()` renders the bare node type for the blueprint view (the ascii-dag renderer cannot render wide cluster-sibling labels, so no knob suffix). `Node` trait unchanged — construction lives in the closure, not a new trait method. |
| `crates/aura-core/src/scalar.rs` | New value accessors `Scalar::as_i64(self) -> Option<i64>` and `as_f64(self) -> Option<f64>` (mirroring `Scalar::kind`), used by the build closures to read the kind-checked slice. |
| `crates/aura-core/src/lib.rs` | Re-export `LeafFactory`. |
| `crates/aura-std/src/*.rs` | Each node gains `fn factory() -> LeafFactory`. With params: `Sma` (`length:I64`), `Exposure` (`scale:F64`), `LinComb::factory(arity)` declares `arity` × `weights[i]:F64` (the arity is topology — fixed per blueprint, C19 — taken as a factory arg; only the weight *values* are injected). Paramless: `Sub`, `Add`, `SimBroker`, `Recorder` (`params: vec![]`; their `build` closures forward the non-param construction args these nodes already take, e.g. the `Recorder` channel — captured by the closure). |
@@ -319,10 +321,10 @@ not the node (C20, established at #30's close), and full domain validation is
- **Paramless harness.** `bootstrap_with_params(vec![])` on a paramless blueprint
bootstraps and runs; a non-empty vector against it returns `ParamArity`.
- **Blueprint-view render (C22).** `render_blueprint` on a value-empty harness
renders param-generic labels (`SMA(length)`, `Exposure(scale)`, bare `SimBroker`);
the compiled view, run on built flat nodes, still renders valued labels
(`SMA(2)`). The `aura graph` golden tests are re-expressed accordingly
(blueprint-view goldens become param-generic; compiled-view goldens unchanged).
renders bare-type labels (`[SMA]`, `[Exposure]`, `[SimBroker]`); the compiled
view, run on built flat nodes, still renders valued labels (`SMA(2)`). The `aura
graph` golden tests are re-expressed accordingly (blueprint-view goldens become
bare-type; compiled-view goldens unchanged).
## Acceptance criteria