Quick start
From an empty app to a live RPM bar that reacts to data — no sim required. Seven short steps.
Part 2 · ReferenceNode wiki
Every node explained — what it does, its inputs and outputs, and when to reach for it.
Graph Editor · Guide
// Advanced Editor
Build a sim-racing dashboard by wiring widgets to live telemetry. Start with a 10-minute hands-on, then dig into the reference for every node.
What you'll build: a horizontal bar whose fill follows the rpm channel, scaled against a max of 5000.
On the home Dashboard page, click “+ Nouveau dashboard”. A short wizard asks for a name, a screen resolution (e.g. 1280×720) and a skin. Confirm to create it.

With your dashboard selected, click “Éditeur avancé”. The node editor opens on it.
Spot the areas you'll use. The centre stacks the canvas (visual preview) over the node graph (wiring); tools sit on the left, properties on the right, and the mock telemetry sliders dock by the graph.

| Area | Where | For |
|---|---|---|
| Tool rail | far left | Select, shapes, and the widget tools you'll use next. |
| Canvas | centre, upper | The visual dashboard — place and arrange widgets. |
| Node graph | centre, lower | The wiring behind the visuals. |
| Properties | right | Edit the selected widget's look and data. |
| Mock telemetry | by the graph | Sliders that fake live data so you can test without a sim. |
| Status bar | middle strip | Grid/snap, the telemetry indicator, and zoom. |
In the tool rail, choose the Bar widget tool (or press B).
Drag a rectangle on the canvas to set the bar's size. A bar appears — empty for now, because nothing feeds it yet. Behind the scenes, it also shows up as a node in the graph below.

This is the heart of the editor: data flows left → right along wires. The graph already holds an Input node — every channel the game sends — and your new Bar node. You'll connect them.
In the graph, drag a wire from the Input node's rpm output to the Bar node's value input. That single connection is the binding.
Press Tab, add a Number node, set it to 5000, and wire it to the bar's max. Now the fill is RPM scaled from 0 to 5000.

max. ┌─────────┐ ┌──────────────┐
│ Input │──── rpm ────────────────▶│ Bar · value │
└─────────┘ ┌───▶│ Bar · max │
┌─────────┐ │ └──────────────┘
│ Number │──── 5000 ───────────┘
└─────────┘
Open the mock telemetry panel and drag the RPM slider. Set it to ~2400 and the bar fills to 2400 of 5000 — a live, data-driven widget. 🎉

Press Ctrl+S. A toast confirms “Dashboard enregistré.” Your dashboard is stored as a .slxd file and reopens exactly as you left it.
gear or speed for a readout.A node has a header (name + an operator glyph where relevant), input sockets down the left edge, and output sockets down the right. Each socket has a type, shown by its colour. Drag from an output to an input to wire them — types must be compatible. A thin top stripe marks the node's category.
in a value the node consumes · out a value it produces · display widgets are sinks (inputs only).
Five types, colour-coded everywhere (the same hues the editor uses):
Categories (the toolbar chips) use their own hues: Data · Compute · Widget · Logic · Doc.
value) are polymorphic — they accept any type and format it.The single source of live sim data. Outputs one socket per telemetry channel — RPM, speed, gear, throttle, brake and dozens more — grouped into collapsible categories. There's exactly one Input per graph.
out every telemetry channel (+ your math channels)
A fixed value you type into the node — a number, a true/false toggle, a text string, or a colour from a picker. Use them as thresholds, labels, fallbacks, and tints (the 5000 max in the quick start is a Number const).
out the constant (number / boolean / string / color)
Make Vec2 packs two numbers into a vector; Split Vec2 unpacks one back into x/y; Const Vec2 is a fixed vector you edit inline. Vectors carry 2-D things like position or size.
Make x,y → out (vector) · Split in (vector) → x,y · Const → out (vector)
A position/rotation/scale source. Outputs x, y, rotation, scaleX, scaleY and a normalized anchor (9 presets). Wire it into an asset to place or scale the whole widget.
out x, y, rotation, scaleX, scaleY, anchorX, anchorY (number)
Several are operator-select: one node with a dropdown that picks the operation. All output a single out (number).
Arithmetic on two numbers: add, subtract, multiply, divide, modulo, power. Divide/modulo by zero yields 0.
in in1, in2 · out out · glyphs + − × ÷ % ^. Switches to component-wise vec2 mode when a vector is wired.
Cosine or sine of an angle in radians.
in in (radians) · out out
The smaller or larger of two numbers.
in in1, in2 · out out
Round to the nearest integer, or down (floor), or up (ceil).
in in · out out
Constrains a value to stay within [min, max].
in in, min, max · out out
Abs gives the magnitude (drops the sign); Sqrt the square root (negatives clamp to 0).
in in · out out
Linear interpolation between a and b by t (clamped 0–1). t=0 → a, t=1 → b.
in a, b, t · out out
Rescales a value from one range to another — e.g. RPM 0–8000 into 0–1, or a pedal 0–1 into 0–100%.
in in, fromMin, fromMax, toMin, toMax · out out
Picks one of two colours by a condition — e.g. green normally, red when a limiter flag is true.
in cond (boolean), then, else (color) · out out (color)
Blends two colours by t — a smooth two-colour crossfade.
in a, b (color), t (number) · out out (color)
Maps a 0–1 input through an editable multi-stop gradient — perfect for RPM shift lights or a temperature scale. Add/move colour stops inline.
in t (0–1) + per-stop offset/color · out out (color). Modes: block, linear, perceptual, smooth, cubic.
Turns seconds into a readable time string — lap, sector, race time, or a signed delta.
in input (seconds) · out out (string). Modes: MM:SS.mmm, SS.mmm, H:MM:SS, ±SS.mmm.
Type a maths formula (e.g. rpm * 0.1 + gear * 5). Each variable you name becomes an input socket automatically.
in one number per variable · out out (number)
Boolean algebra and numeric comparisons in one node: &&, ||, !, and == != < <= > >=. Sockets adapt to the operator (one input for !, two otherwise).
in in1 (, in2) · out out (boolean)
Numeric branch: outputs then when the condition is true, otherwise else.
in cond (boolean), then, else (number) · out out (number)
Maps a number to a string via a list of cases — e.g. gear 0→"R" 1→"N" 2→"1"…. Emits the fallback if no case matches.
in input (number), fallback (string) · out out (string)
Repeats the nodes inside the block once per iteration — e.g. a row of N shift-light LEDs from one network. Drops a begin node (giving index and count) and an end marker.
in count · out index, count (number, on the begin node)
The sinks: each is a widget on the canvas. They have inputs but no outputs. Any input can be wired (live) or set as a static value in the Properties panel.
A text label. Its value is polymorphic — wire a number, string, etc., and format it (number, time) in Properties.
in value (any), fillColor (color), effects
A progress/level bar (the one you built in Part 1). Fill scales value against min/max.
in value, min, max (number), fillColor (color), effects
A rectangle — solid or with a linear gradient and strokes. Great as a backdrop, panel, or status block.
in fillColor or gradient stops, stroke width/color, effects
A circle or ellipse. Static-only for now (set its look in Properties).
in effects
An image — a logo, car silhouette, or custom gauge face. Source and scale set in Properties.
in opacity (number), effects
A reusable widget — a packaged sub-network with typed inputs and outputs (like a function or component). Make one by selecting nodes and pressing Ctrl+G, or drop a built-in from +Widget. Assets can nest, and each instance can override inputs and styling.
in the asset's inputs + transform (x, y, rotation, scale, anchor) · out the asset's outputs
Found inside an asset's body: Inputs exposes the asset's parameters as outputs to wire from; Outputs collects its results. Created automatically — you don't add them from the menu.
Inputs → out per parameter · Outputs ← in per result
Documentation, not computation. A Note is a free-floating text label; a Frame is a titled box that groups nodes and moves them together. Neither affects the result — they keep a graph tidy.
no inputs · no outputs · visual only
A graph is two concentric parts. The body is the eval contract — nodes and wires, no visuals. The layout holds positions, notes and frames. Because positions live only in layout, moving a node never changes the body — evaluation and asset signatures are drag-stable.
Graph = {
body: { nodes: StructuralNode[], wires: Wire[] }, // eval contract — no visuals
layout: { nodes: Record<id, {x,y,…}>, notes, frames }, // visual state only
}
Each engine tick, evalGraphCore reads the body only and: (1) topologically sorts the nodes (a cycle throws — which is why cyclic wires are rejected at draw time); (2) handles any ForEach block by partitioning into pre / loop / post zones and running the loop once per iteration; (3) evaluates each node via a tiny dispatcher into the registry; (4) collects the resolved inputs of every display node; (5) re-orders by document order for correct stacking. The result feeds the renderer.
Every node kind is one NodeDef entry colocating its kind, label, category, Zod schema, build() factory, eval(), and socket-type resolver. The goal: adding a node = one entry in registry/primitives.ts or registry/non-primitives.ts (+ a React component), nothing else.
software/simlogix/ui/src/features/advanced-editor/graph/. The folder's own summary.md tracks change history and links the design specs.