SysDynLab simulates how things change over time — a heating room, a motor, a pendulum, a control loop — by connecting blocks with wires. Everything runs in your browser; nothing is uploaded.
More room for the canvas: click the open tab again (or the arrow at the right end of the ribbon) to fold the ribbon away. Then clicking a tab opens its buttons only until you use one, click elsewhere or press Esc. Click the arrow again to keep it open. Your tab and this choice are remembered.
Block library
Search: type part of a name (pid, delay, park); Esc clears it.
Sections fold and unfold when you click their heading: Sources, Continuous, Math, Discrete, Motor Control, Nonlinear, Logic, Signal Routing, Sinks.
Motor Control holds Clarke, Park, Inverse Park, Inverse Clarke, SVPWM and an average Inverter as ready-built subsystems (double-click one on the canvas to see and change the math), the motor models DC Motor, PMSM and BLDC Motor (double-click for their parameters), and a PI controller with output limits and anti-windup.
Add a block: drag it onto the canvas, or click it to place it in the middle of the view.
Phones and small windows: the library slides in from the left — tap ☰ at the top left; press and hold a block, then drag it out.
Saving, opening and examples
Save (Ctrl+S) writes the whole model — blocks, wires and their routes, block names, the method, step and stop time — to an .xml file. Chrome and Edge ask where to save; other browsers download it.
Open (Ctrl+O) loads such a file and replaces the model on the canvas. New clears the canvas (it asks first).
Nothing is saved automatically and nothing leaves your computer: save before closing the page.
Demos (in the Help tab) loads a ready-made example; it replaces the current model, so save your own work first.
Measured data comes in through a CSV Source block and results go out through a CSV Sink — see Plots & Data.
Status bar
Messages appear here: what the simulator is doing, and warnings that need your attention (shown in red, and kept until the next action). See Troubleshooting for what each one means.
Adding blocks
Drag a block from the library onto the canvas, or click it to place it in the middle of the view.
Drop a block with one input and one output onto an existing wire to insert it there.
Paste copies with Ctrl+V; Ctrl+D duplicates the selection.
Changing a block's settings
Double-click the block. Edit, then click Apply or press Enter.
Number fields accept expressions: 2*pi, 1/0.05, sqrt(2)/2, 1e-3.
A Constant can hold a vector or matrix: 1,2,3 or 1,2;3,4 (see Vectors & Matrices).
Rename a block from its right-click menu; the name is shown under it, saved with the model and used in generated code.
Selecting and arranging
To
Do
Select several blocks
Drag a box on empty canvas, or Shift+click; Ctrl+A selects all on this level
Move
Drag (wires follow), or nudge with the arrow keys (Shift = 1 px)
Rotate / mirror
Right-click → Rotate 90° / Mirror, so wires run the way you want
Resize
Drag the grip at the block's bottom-right corner (it shows when you point at the block)
Pan / zoom
Middle-button drag or Alt+drag / mouse wheel; Fit shows the whole model
Delete
Select, then Delete or Backspace
Drawing wires
The small arrows on a block's sides are its ports: pointing in = input, pointing out = output. An output can feed any number of inputs; an input takes one wire.
To
Do
Connect two blocks
Drag from an output to an input — or release anywhere on the target block and the nearest free input is used. Or click one port, then the other.
Choose the route
Release the wire on empty canvas, then click at each corner; finish on the target port. Backspace removes the last corner.
Cancel
Esc or right-click
Branch a signal
Hold Ctrl (Cmd on Mac), press any point of a wire and drag to the new input (or release, then click the input). A dot marks the branch point.
Feed an input from a wire
Drag from a free input and release on the wire
Re-plug a wire
Drag a connected input port to another input; release on nothing to put it back
Tidy a wire
Drag any straight piece of it sideways
Select / delete a wire
Click it (Shift+click for several), then Delete
Many wires crossing? Send a signal with a Tag Send and pick it up anywhere on the same level with a Tag Read of the same tag — see Subsystems & Tags.
Right-click menu of a block
Item
What it does
Edit Properties
Open the settings (same as double-click)
Rename
Change the name shown under the block
Rotate 90° / Mirror
Turn or flip the block
Duplicate / Copy / Cut / Paste
Same as Ctrl+D / Ctrl+C / Ctrl+X / Ctrl+V
Create Subsystem
Group the selected blocks into a new subsystem
ⓘ Block Help
How the block works, its ports, an example and every setting
Delete
Remove the block and its wires
Subsystem: Enter / Settings / Dissolve / Generate Code
Go inside, add an enable port, ungroup, or export it as C / Python
Plotter: Set Number of Inputs
How many plot rows it has
What a subsystem is
A subsystem is a block that contains other blocks — a box you can open. Use it to keep a large model readable (a "Plant", a "Controller", a "Sensor"), to reuse a part, or to generate code from just that part.
Making one
Select the blocks to group (drag a box, or Shift+click).
Right-click one of them → Create Subsystem (or Edit tab → Subsystem).
Wires that crossed the boundary are kept: inside, they now start at a Port In or end at a Port Out, and the subsystem block gets one pin for each. Dissolve (right-click) puts the blocks back on the parent level.
Working inside
To
Do
Go inside
Double-click the subsystem (or right-click → Enter)
Come back out
Esc, or click a name in the path bar at the top of the canvas
Add a pin
Inside, add a Port In (input pin) or Port Out (output pin). Their port number sets the pin order, top to bottom
Rename
Right-click → Rename; the name is shown on the block and in the path bar
Enabled subsystems
Right-click → Subsystem Settings… → Enable port adds a control pin on top. The subsystem runs only while that signal is non-zero. When it stops, choose what happens:
Reset — its states and outputs go back to their initial values;
Hold — it keeps its last states and outputs until it runs again.
Tag Send and Tag Read
A Tag Send publishes the signal wired into it under a name (its tag); every Tag Read with the same tag outputs that signal. They act like an invisible wire and are the easiest way to avoid long or crossing wires.
Tags work on one level: a Tag Read inside a subsystem only sees Tag Sends inside the same subsystem. To bring a signal in or out, use the subsystem's ports.
Each tag should have exactly one Tag Send.
Ready-built subsystems
The Motor Control section of the library drops complete subsystems built from basic blocks: Clarke, Park, Inverse Park, Inverse Clarke, SVPWM and an average Inverter. Inside, every signal is published with a Tag Send under its name (alpha, cos, offset…) and read next to where it is used, so the blocks read like the equations. Their pins are named on the block. Change anything you like — it is an ordinary subsystem.
Motor models
DC Motor, PMSM and BLDC Motor are subsystems with parameters. Double-click one to set them (resistance, inductances, flux or back-EMF constant, pole pairs, inertia, friction; the PMSM also offers d-q or three-phase terminals). Apply rebuilds the inside with the new values; right-click → Enter Subsystem shows it. Each one tells the automatic step size its electrical time constant, so with Step dt left empty the run stays accurate. The equations are in Block Reference under Motor Control.
Warnings in the status bar
Message
What it means
What to do
Algebraic loop detected: A → B → …
A feedback loop with no memory block in it: each block needs the others' output of the same instant.
Put a Unit Delay, Integrator, filter or other memory block in the loop (see How It Works).
Simulation diverged at "…"
A value became infinite or not-a-number. Either the system really is unstable, or the step is too large for it.
Check signs and gains (a positive feedback loop grows forever). Otherwise set a smaller Step dt or a stiff method (ESDIRK23, Implicit Euler).
Results may be inaccurate — ran out of solver sub-steps / did not converge
The method struggled: the model is stiff (very fast and slow parts together) or switches sharply.
Smaller Step dt, or a stiff method. For models with sharp switching or quantisation, a fixed-step method (RK4) is often best.
"…" has Ts … not a multiple of dt
A discrete block's sample time does not line up with the simulation step, so it samples late.
Set Step dt to Ts or an exact divisor of it (Ts = 0.01 → dt = 0.01, 0.005, 0.001…).
Shape mismatch
A vector or matrix went into a block that takes a single value, or two sizes do not fit together.
A chart has a mistake: an unknown name, an incomplete condition, a loop of junctions.
Open the chart; its status bar names the state or transition. Unknown names can be added as inputs, outputs or local data from there.
Expression errors
The Expression block found an unknown name or a typo.
Use u (one input) or u1, u2… and functions such as sin, pow(a,b); ^ is not power.
Common problems
Problem
Try
The plot is empty
Run first, then double-click the Plotter. Check that the wire really ends on the Plotter's input (its arrow disappears when connected).
A signal is always 0
An input left unconnected reads 0. Look for open input arrows; check Tag Send / Tag Read tags match and are on the same level.
The curve is jagged or steps are visible
Make Step dt smaller, or leave it empty so it is chosen for you.
The run takes long
Shorten the Stop time or enlarge Step dt; very small dt over a long time means millions of steps.
Results differ from the generated code
Simulate with a fixed-step method (RK4, Heun or Euler) and the same dt as the code — see Code Generation.
Can't see the block library
View → Library, or on a phone tap ☰.
Lost something
Ctrl+Z undoes step by step. Models are not saved automatically — save with Ctrl+S.
Still stuck? Use Feedback in the status bar to report it — include what you did and the message you saw.
What is SysDynLab?
SysDynLab simulates how things change over time — a heating room, a motor, a pendulum, a control loop — by connecting blocks with wires, the same way engineers sketch systems on paper. It runs entirely in your browser; nothing is installed and nothing is uploaded.
Each block does one simple job (add, multiply, integrate, delay, plot…). Wires carry numbers from one block to the next. When you press Run, SysDynLab steps time forward from 0 to the Stop time and every block does its job at every step.
Over 50 blocks: sources, continuous dynamics, digital (discrete) blocks, math, logic, lookup tables, matrices
Subsystems to keep large models tidy, and state machines for modes and decisions
Solvers chosen automatically (you can override), live plots, CSV import/export, C/Python code generation
New here? Open the Help tab, click Demos and pick any example, press ▶ Run, then double-click a Plotter block. Then read How It Works — five minutes there makes every block make sense.
New to the window? The Interface explains every tab and button.
Building a Model in 5 Steps
1
Add blocks
Click a block in the left library to place it in the middle of the canvas, or drag it to where you want it. Right-click a block and choose ⓘ Block Help to see what it does, with an example.
2
Connect them
The small arrows on a block's sides are its ports: arrows pointing into the block are inputs, arrows pointing out are outputs. Drag from an output to an input to draw a wire (or click one, then the other). You don't have to hit the port exactly: releasing near it, or anywhere on the target block, snaps to the nearest free input. A port's arrow disappears once it is connected.
Split a signal (send it to a second block): hold Ctrl (Cmd on Mac), press on any point of an existing wire, and drag to the new input. A plain click just selects the wire. Insert a block into a wire: drop an unconnected block onto the wire. Choose the route yourself: release the wire on empty canvas, then click at each corner; finish on the target port. Backspace removes the last corner, Esc cancels. Tidy a wire: drag any straight piece of it sideways.
3
Set parameters
Double-click a block to edit its settings, then click Apply (or press Enter). Number fields accept expressions such as 2*pi or 1/0.05.
4
Run
Set the Stop time (how many seconds to simulate) in the Simulation tab and press ▶ Run in the same tab. Leave the method on Auto and Step dt empty: SysDynLab picks both from your model. The button turns red while running; press it again to stop early. Warnings and errors appear in the status bar at the bottom.
5
Look at the results
Double-click a Plotter block to open its graph. Drag to pan, scroll to zoom, hover to read values. Fit shows everything again. A Display block shows a live number right on the canvas.
Your First Simulation: a Heating Object
A cup of tea on a hot plate doesn't heat instantly — it approaches the plate's temperature gradually. That behaviour is a first-order system, and you can build it in a minute:
Add a Step (Sources), a Transfer Fn (Continuous) and a Plotter (Sinks).
Wire Step → Transfer Fn → Plotter.
Leave the Transfer Fn at its default num = 1, den = 1 1, which means 1/(s+1).
In the Simulation tab set Stop time = 8 and press ▶ Run, then double-click the Plotter.
What you see: at t = 1 the Step switches on, and the output rises towards 1 — fast at first, then slower. One second after the switch (one time constant) it has covered 63 % of the way; after about four seconds it has practically arrived. Change the denominator to 2 1 (a time constant of 2 s) and run again: the rise is twice as slow.
Next step: add a controller. Put a Sum (signs +-) and a PID in front of the Transfer Fn, and branch the output back to the Sum's minus input. That is a feedback loop — see How It Works for why it works.
Everyday Tasks
Task
How
Select several blocks
Drag a box around them on empty canvas, or Shift+click
Move blocks
Drag them (wires follow), or nudge with the arrow keys (Shift = 1 px steps)
Rotate / mirror / rename
Right-click the block
Delete
Select, then Delete or Backspace
Undo / redo
Ctrl+Z / Ctrl+Y
Copy & paste
Ctrl+C, Ctrl+V — wires between the copied blocks come along, routes included
Pan / zoom the canvas
Middle-button drag or Alt+drag / mouse wheel. Fit shows the whole model
Group blocks into a subsystem
Select them → right-click → Create Subsystem. Double-click to go inside, Esc to come back out
Save / open a model
Ctrl+S / Ctrl+O (XML file). Models are not saved automatically
Export results
Wire signals into a CSV Sink; the file downloads when the run ends
Generate C / Python code
Code tab → Whole model or Selected subsystem (also right-click a Subsystem → Generate Code). For a State Machine alone: open it → Generate C
The Big Idea
A block diagram is a recipe that is repeated over and over. SysDynLab chops time into small steps of length dt (for example 0.01 s). At every step, starting at t = 0 and ending at the Stop time, it:
asks every block for its output, in the order the wires dictate (a block's inputs are always computed before the block itself);
records what the Plotters, Displays and CSV Sinks receive;
moves time forward by dt and updates everything that remembers (integrators, filters, delays…).
A wire always carries one number at each moment (or a list of numbers — see Vectors). A Plotter simply draws that number against time.
Two Kinds of Blocks: Instant and Memory
This is the single most useful idea for understanding any diagram. In Block Help every block with inputs and outputs is tagged with one of these two badges:
Kind
What it means
Examples
Instant
The output depends only on the input right now. Change the input and the output changes in the same step. Like a calculator.
Gain, Sum, Product, Saturation, Switch, Compare, Lookup Table, Expression
Has memory
The output also depends on what happened before. These blocks give a model its behaviour over time: rising, settling, oscillating, delaying.
Integrator, Transfer Fn, Low-Pass, PID, Delay, Sample Delay, Discrete TF, Rate Limiter, State Machine
Sources (Step, Sine, Constant…) have no inputs — they produce signals from the clock. Sinks (Plotter, Display, CSV Sink) have no outputs — they only watch.
The Integrator Is the Heart of Dynamics
Almost every physical law says how fast something changes: velocity is how fast position changes, current is how fast charge changes, heat flow is how fast temperature changes. An Integrator turns a rate of change back into the quantity:
rate of change → [ Integrator ] → quantity
Feed it a constant 2 and its output climbs by 2 every second. Feed it 0 and it holds still. Its ic parameter is where it starts.
Turning an Equation into a Diagram
Take a mass on a spring with a damper, pushed by a force F:
m·x″ = F − c·x′ − k·x
The recipe, which works for any such equation:
Solve for the highest derivative:x″ = (F − c·x′ − k·x) / m.
Chain one Integrator per derivative: x″ → Integrator → x′ → Integrator → x. Now x′ and x exist as wires.
Build the right-hand side from those wires: Gain c on x′, Gain k on x, a Sum with signs +-- for F − c·x′ − k·x, and a Gain 1/m.
Close the loop: wire that result into the first Integrator's input. Set the initial position and velocity in the Integrators' ic.
Run it with a Step on F and a Plotter on x and you get the classic damped oscillation. Try the Van der Pol demo for the same pattern with a nonlinear twist.
Feedback Loops — and Why They Need Memory
A feedback loop is a path of wires that comes back to where it started, like the one above or a controller watching its own output. Loops are normal and are what control is all about — but every loop must pass through a block with memory, such as an Integrator.
Why: in a loop made only of Instant blocks, each block needs the other's output in the same step before it can compute its own — a chicken-and-egg problem called an algebraic loop. SysDynLab detects it and warns in the status bar, naming the blocks. Blocks tagged Breaks feedback loops in Block Help (Integrator, Low-Pass, Delay, Sample Delay, …) solve it, because their output this step only depends on the past.
Example: Setpoint → Sum (+-) → PID → Transfer Fn → output, with the output branched back to the Sum's minus input. The Transfer Fn carries memory, so the loop is fine.
Continuous vs. Discrete (Digital)
Continuous blocks
Discrete blocks
Model
The physical world: things that change smoothly
Digital electronics and software that act at clock ticks
Blocks
Integrator, Transfer Fn, PID, Low/High-Pass, Delay
Once per sample period Ts; the output is held constant in between (a staircase)
A typical embedded-control model mixes both: a continuous plant (motor, room) measured through a Zero-Order Hold and controlled by a Discrete PID with the same Ts — exactly like a microcontroller. For accurate sampling, choose a dt that divides Ts evenly (Ts = 0.01 → dt = 0.001).
Step dt, Stop Time and the Solver in One Paragraph
In the Simulation tab, Stop time is how long to simulate. Step dt is the step between recorded points: smaller is more accurate and smoother but slower. The solver is the method used to advance the memory blocks from one step to the next. Leave Auto and an empty dt, and SysDynLab picks them from the fastest thing in your model, showing its reasoning in the status bar. If a run "diverges" (numbers blow up), the status bar names the block where it happened — see the Solver Guide.
Signals Can Be Lists: Vectors
A wire can carry several numbers at once (a vector) — for example the three coordinates of a position. Vector wires are drawn thicker. Bundle signals with Mux, split them with Demux. Most math blocks work element by element, and a Mux into a Plotter draws one coloured line per element. Details are in the Vectors & Matrices tab.
Reading Someone Else's Diagram
Find the sources (left side, no inputs) — they are what drives the system.
Find the memory blocks — they hold the system's state (position, temperature, charge…). Their number is the order of the system.
Follow each loop back and read its Sum signs: a minus sign in the loop means negative feedback (stabilising, like a thermostat).
Right-click any unfamiliar block → ⓘ Block Help.
Blocks are listed in the same groups as the library. Click a block to see how it works, an example and its parameters. Badges: Instant output depends only on the input now · Has memory output also depends on the past · Breaks feedback loops can close a loop on its own (see How It Works).
Loading block reference…
Plotter Block (Scope)
The Plotter block records signals during simulation and lets you view them as time-series plots. It is the primary data visualization tool in SysDynLab.
Setting Up a Plotter
Drag Plotter from the Sinks section of the sidebar onto the canvas.
Double-click the block to set nInputs — the number of signal channels (1–10).
Wire signals from other blocks into the input ports.
Press ▶ Run.
Double-click the Plotter block again to open the signal viewer.
Viewer Toolbar Controls
Button / Action
What it does
Zoom X
Activate X-axis (time) zoom mode. Click once for the start point, click again for the end point — the time axis zooms to that range. Blue band previews the selection.
Zoom Y
Activate Y-axis (value) zoom mode. Same two-click selection, but locks the value axis.
↔+ / ↔−
Zoom in / out horizontally (time axis) by a fixed factor.
↕+ / ↕−
Zoom in / out vertically (value axis) by a fixed factor.
Fit
Return to the full auto-scaled view (all data visible).
Names
Toggle the channel-name panel. Type a custom label for each channel. Names appear on the plot legend and in cursor readouts, and are saved with the model.
Mouse Interaction on the Plot
Action
How
Pan left / right / up / down
Click and drag on the canvas
Zoom in / out
Scroll wheel anywhere on the canvas
Read cursor values
Move the mouse over the canvas — a vertical cursor snaps to the nearest data point and shows time + value for every channel
Channel Names
Click Names in the viewer toolbar. A row of text boxes appears below the toolbar — one per physical input port. Type a descriptive label such as Speed or Error. The name appears:
In the inline legend on the right side of the plot
In the hover cursor readout (Speed = 3.142)
As the row label when subplot mode is active
Names are stored in the block parameters and saved with the model XML.
Mux Input → Overlay Mode
If the Plotter has nInputs = 1 and the single port is wired from a Mux block, SysDynLab automatically expands the vector and draws all elements as separate colored traces on one shared plot. This is the easiest way to compare several signals side-by-side without adding multiple Plotter blocks.
Example: Mux 3 signals (reference, output, error) → single Plotter input → three colored lines in the same plot.
Multi-Input → Subplot Mode
If the Plotter has nInputs > 1, each physical input port gets its own subplot row. All rows share the same time axis so you can compare timing across signals. Each row auto-scales its Y axis independently.
If a port in subplot mode is itself wired from a Mux, that row shows all Mux elements overlaid within its subplot — giving you both subplot isolation and overlay comparison simultaneously.
Typical Patterns
Goal
Setup
Compare 3 signals on one plot
Mux 3 → Plotter (nInputs = 1)
Compare 3 signals each on its own row
Plotter nInputs = 3, one signal per port
Overlay a vector + show another signal separately
Plotter nInputs = 2; port 1 ← Mux(3); port 2 ← scalar signal
Name channels
Open viewer → click Names → type labels
Zoom into transient at t=5s
Click Zoom X → click at t=4.8 → click at t=5.5
XY Plot Block
The XY Plot block plots one signal against another in phase-plane style — there is no time axis. Input 1 maps to the X axis; Input 2 maps to the Y axis. The result is a parametric trajectory curve.
Common uses: visualizing limit cycles, attractors, Lissajous figures, and velocity vs. position phase portraits.
Example: Connect a Sine Wave to Input 1 and a Cosine (Sine with phase = π/2) to Input 2 to draw a circle.
Display Block
The Display block shows the current numeric value of its input in a green LCD-style readout — updated live at every simulation step. Use it to monitor a single value without opening a scope.
If the input is a scalar, one row is shown.
If the input is a vector (from a Mux), each element gets its own row. The block height grows automatically.
Values use automatic precision: small/large values are shown in scientific notation.
The display also shows the last value frozen at the end of simulation, useful for checking final steady-state.
CSV Sink
The CSV Sink records all input channels during simulation and saves them as a comma-separated file when the simulation ends. The filename is the block label.
Set nInputs to the number of channels to record.
Connect a Mux to record multiple signals as separate columns.
The file has a time column followed by one column per channel.
Use this to post-process data in Excel, Python (pandas), or any other analysis tool.
What Is the State Machine Block?
The State Machine block implements statecharts with hierarchical states, parallel (AND) regions, junctions, entry/exit actions, transition conditions, local variables, and arbitrary input/output ports. The entire statechart runs inside the WASM simulation engine at each time step.
Adding a State Machine
Drag State Machine from the Logic section of the library.
Double-click the block to open the chart editor.
With nothing selected, the right-hand Symbols panel lists the inputs, outputs and local data. Add and rename them there.
Double-click the canvas to add states; drag from a state's edge to draw transitions.
Click Done (or press Esc) and wire the block like any other.
Editor Layout
Area
Purpose
Tool strip (left)
State (S) and Junction (J). A tool places one element and then goes back to normal editing; Esc or a second click on it cancels.
Canvas
The chart. Superstates are drawn with their states inside them. Drag on empty space to select several states; pan with Space-drag, middle-drag or Alt-drag; the wheel zooms, F fits the chart.
Inspector (right)
Fields for the selected state or transition; with nothing selected, the Symbols (inputs, outputs, local data). Every change applies immediately.
Status bar
Problems in the chart (click to jump to the state or transition), and hints.
? button
A short cheat sheet of gestures, label syntax and keys (also ? or F1).
Every edit can be undone with Ctrl+Z (redo Ctrl+Shift+Z). Right-click anything for its commands.
States
Only one state is active at a time (per parallel region). A state's label is its text, written on the state:
Constant output values written every step while the state is active (output = number, separated by ;). Use entry: for anything computed.
exit:
Actions run once when the state is left.
Operation
How
Add a state
Double-click the canvas (you can type the label straight away), or the State tool, or right-click → Add state here
Edit the label
Double-click the state (finish with Ctrl+Enter or click away, Esc cancels), or use the inspector fields
Move / resize
Drag the state; select it and drag any of the handles on its corners and sides. Arrow keys nudge.
Several states
Drag a box around them, Shift-click to add or remove, Ctrl+A for all. Drag one to move them together; the inspector aligns, spaces or sizes them.
Default state
The state entered first is marked by a dot and arrow. Right-click → Set as default, or the inspector checkbox. Drag the dot to move the arrow around the state.
Copy / paste / delete
Ctrl+C / Ctrl+V (copies the inner states and their transitions too) / Delete
Superstates (hierarchy)
A state that contains other states is a superstate. Its inner states are active only while it is active.
Nest a state by placing it inside another (State tool click, or drag it in) — the outer state grows to fit. Enlarging a state around existing states also makes them its children.
A transition into a superstate enters its default inner state. A transition out of a superstate fires whichever inner state is active and leaves all of them (exit actions from the inside out).
Transitions join states with the same parent. A move or resize that would split a transition across two superstates is undone with a message.
Subchart: tick Subchart (or right-click → Make subchart) to show a superstate as a plain box; double-click it to open its contents, and use the breadcrumb or Backspace to go back up. Expand subchart in place draws them inside it again.
OR and AND Superstates
Every superstate is one of two kinds; a small OR / AND tag next to its name shows which:
OR (exclusive) — one inner state is active at a time, entered at its default state. This is the usual kind.
AND (parallel) — all inner states are active together and run every step in order.
These are hierarchy, not separate kinds of state: right-click a state, or several selected states, and choose Group into superstate (OR) or Group into parallel superstate (AND) (Ctrl+G / Ctrl+Shift+G). A state that already has states inside it is switched under Inner states in the inspector, or by right-click.
Parallel (AND) details
Select a superstate and choose Parallel (AND) in the inspector (or right-click). All of its inner states are then active together; they are drawn with dashed borders and a number, the order in which they run each step (change it with the inspector's Order). Parallel states have no default state and no transitions between them; they can read and write the same outputs and local data to coordinate.
Transitions
When a transition's guard is true, the chart leaves the source (exit actions), runs the transition action, and enters the target (entry actions). The label uses the UML notation event [guard] / action, every part optional:
Label
Meaning
[temp > 21]
Fires when the guard is true
[rise(button)] / count += 1
Guard, then an action run when it fires
/ y = 0 or empty
No guard: fires on the next step
E [x > 1]
Waits for event E (see Events below), then checks the guard
Operation
How
Draw
Drag from the edge of a state (the pointer becomes a crosshair) to another state or junction. Release on empty space to create a new state there, already connected. Release back on the same state for a self-transition (also right-click → Add self-transition).
Edit the label
Typed right after drawing; later double-click the transition or its label. Enter finishes.
Order
With several transitions from one state, each shows a number: they are checked in that order and the first true guard fires. Change it in the inspector.
Shape
Select it, then drag the middle handle to bend it, an end to move where it attaches — drop it on another state or junction to reconnect the transition, or on empty canvas to leave that end unconnected (no need to select it first). An unconnected transition is drawn dotted with an open dot on its loose end, is listed under ⚑ checks, and is ignored by the simulation and the generated code until both ends are connected again — or the label to move the text. Reset shape undoes all three.
Events
An event is an input pin whose edge a transition waits for. In Symbols, click + Event (or ⋯ → Make it an event on an input) and choose rising, falling or either. Write it in front of the guard: E, E [guard], E [guard] / action. A transition with an event fires only in the step where that edge happens.
Chart runs (under Events) can be set to only on event E: then every transition out of a state that names no event of its own also waits for E, so the chart only changes state on that edge (a triggered chart). State timers (state_time) keep counting real time.
Data Types
Local data is real (any number) by default. Choose int or bool in its row:
int — every assignment drops the fraction (toward zero) and is limited to the 32-bit range; generated C declares int32_t.
bool — stores 1 for any non-zero value, otherwise 0; generated C declares unsigned char.
true and false can be used anywhere as 1 and 0.
int and bool data are single values (size 1).
Flowchart Patterns
Right-click a state and choose Add if / else if / else… or Add switch / case…. A dialog lists the branches: type each condition (or case value) and its action, use + else if / + case for more, and tick else / default for the fall-through action. Insert builds the flowchart: a vertical line of junctions; each condition branches right (order 1), its action runs down its own column (/ action), all columns join back on the line, and falling through (order 2) tries the next condition, then the else. From a state it runs every step the state is active, without leaving it (the paths end in a junction with no way out). Right-click empty canvas to Insert one unconnected and draw a transition to its top junction yourself.
Switch / case uses the standard switch layout: every case branches right to the same column, runs its action down, and joins a rail on the right that returns to the line at the bottom; the default action is on the fall-through.
Stand-alone flow chart: inserted on a level that has no states, the flowchart gets the default transition (dot) and runs every time the chart runs. The optional description in the dialog is shown as a // comment beside it (edit it later under the junction's Comment).
Extending a flowchart: right-click a vertical transition that ends in a junction — for example an action column — and choose Insert if / else if / else here… or Insert switch / case here…. The new pattern hangs from that junction and the rest of the flowchart moves down and right to make room; this builds nested ifs.
Chart Checks and Autocomplete
Next to the problems in the status bar, ⚑ checks lists things that are legal but probably not intended: states nothing leads to, junctions never reached, transitions that can never fire (an earlier one from the same state has no guard), guards without variables, states that are never left, outputs never set, local data read but never assigned, events no transition uses. Click a check to show it; the elements are outlined in amber.
While typing a label or an inspector field, matching inputs, outputs, local data, events, functions and constants are suggested: ↑↓ to choose, Enter or Tab to insert, Esc to dismiss. At the start of a state's line it offers entry:, out: and exit:.
Junctions
Junctions (small circles) are branch points, never active states. When a transition reaches one, the first of its outgoing transitions whose guard is true is followed.
Use
How it works
If / else
Several outgoing transitions with different guards pick the target.
Action only
When no outgoing guard is true (or there are none), the actions along the path still run and the chart stays in the source state — a conditional action without a state change.
Default junction
Make a junction the default (right-click → Set as default) to choose the first state by guards when the chart starts.
Symbols — Inputs, Outputs & Local Data
Click empty canvas to show the Symbols panel:
Section
Purpose
Usage in expressions
Inputs
Signals into the block, one port each (top port = first row).
Read-only: speed > 10
Outputs
Signals out of the block.
Assign in actions or out:: mode = 2. Read in guards: mode == 1
Local data
Values kept between steps, not visible outside. They start at their initial value each run; a size above 1 makes an array (buf[i]).
Read and assign: counter = counter + 1
The panel starts with a picture of the block: the numbers on its pins match the numbered rows.
The second column is the width (1 = scalar). Names in grey italics are not used anywhere yet.
The ⋯ menu on a row moves it up or down (reordering the block's pins), changes it between input, output and local data, or deletes it.
Wires stay connected to their signal. When you close the editor, a wire follows its port wherever it moved; a wire on a port you deleted (or turned into local data) is removed.
Renaming a symbol renames it in every label of the chart.
Use a name in a label before defining it and the panel offers Input / Output / Local buttons to add it (the likely choice is highlighted: assigned names are outputs, read names inputs). A new name on an out: line becomes an output straight away.
Expression Language
Conditions and actions share a small expression language. Both paths (WASM runtime and C code generator) support the same operators.
Operators
Category
Operators
Example
Arithmetic
+ - * /
speed * 0.1 + bias
Unary minus
-x
-error
Comparison
> >= < <= == !=
error >= threshold
Logic
&& || !
a > 0 && b < 1
Bitwise
& | ^ ~ << >>
flags & 0x03
Modulo
%
step % 4 (uses fmod internally)
Assignment (action only)
=+=-=*=/=++--
output = input + 1, count += 1, n++
Multi-statement (action only)
;
y = 1; counter = 0
Built-in Functions
Function
Description
abs(x)
Absolute value
sqrt(x)
Square root
sin(x) cos(x) tan(x)
Trigonometry (radians)
asin(x) acos(x) atan(x)
Inverse trig (radians)
atan2(y, x)
Four-quadrant arctangent
exp(x)
ex
log(x)
Base-10 logarithm
ln(x)
Natural logarithm
pow(x, y)
x raised to the power y
min(a, b) max(a, b)
Minimum / maximum of two values
floor(x) ceil(x) round(x)
Rounding functions
sign(x)
−1, 0, or +1 based on sign
rise(x)
True in the step where input x goes from ≤ 0 to > 0. Input ports only.
fall(x)
True in the step where input x goes from > 0 to ≤ 0. Input ports only.
state_time
Seconds since the current state was entered (for a transition: its source state). Use it for timers: state_time > 5.
pi e
Mathematical constants π and e
Example Patterns
Goal
Condition
Action
Transition when error exceeds threshold
abs(error) > 0.5
output = 1
Count steps in a state
(empty — always fires from junction)
counter = counter + 1
Reset counter on entry
—
Entry: counter = 0
Trigger on rising edge
rise(trigger)
output = 1; timer = 0
Stay in state for N steps
counter >= N
counter = 0
Two-condition if/else via junctions
Trans A: x > 0 Trans B: x <= 0
Route from junction to StateA or StateB
Running the Simulation
After closing the editor, the State Machine block looks like any other block — wire its input and output ports, set simulation time, and press Run. The statechart evaluates at every simulation step: reads inputs, checks transition conditions (in order), fires matching transitions, runs actions, and writes outputs.
Each step, every active state gets one chance to change: transitions of its enclosing superstates are tried first (outermost first), then its own, in their Order. The first whose condition is true fires. A state entered in this step waits until the next step.
Firing a transition leaves the source (its active inner states first, innermost first, each running its exit action), runs the transition's action and any junction actions, then enters the target (entry action, then its initial inner state).
Outputs hold their last value — whether it was written by an action or by a state's fixed output value. A state's fixed output values are written every step while it is active, after the actions of that step. Outputs start at 0.
Local variables start at their initial value at t=0 and persist for the entire simulation.
Parallel regions run side by side — each active region can change state in the same step.
Mistakes are reported, not ignored. A misspelled name, an unfinished condition, = where == was meant, or a chart over the size limits (64 states, 128 transitions, 16 locals) stops the run with a message naming the state or transition. The editor's status bar shows the same problems while you edit.
Use a Display block on an output port to watch the active state value live.
Tip: To inspect which state is active, add an output port named mode and assign a distinct integer in each state's entry action (e.g. mode = 1, mode = 2). Connect it to a Display block to monitor the active state during simulation.
Generating C Code from a State Machine
SysDynLab can export the State Machine block as self-contained C or Python code — the same logic that runs in the WASM simulation engine, translated to portable source code ready for embedded targets or desktop applications.
Double-click the State Machine block to open the editor and click ⚙ Generate C in its toolbar. To get Python, or C for a whole controller, put the chart in a Subsystem and use the Subsystem's Generate Code.
The generated code behaves exactly like the simulation, step for step: it is produced from the same compiled conditions and actions the simulator runs. (Checked by running charts in both and comparing every output at every step.)
Everything lives in a name_ctx_t struct — inputs, outputs, locals and which states are active — so you can run several instances; there are no global variables.
Call name_init(&ctx) once, then name_step(&ctx, dt, inputs…) every dt seconds and read the outputs from ctx, e.g. ctx.heater.
The editor produces a .c and a .h file; a Subsystem's Generate Code includes the chart in its own C or Python output.
What Are Vector & Matrix Signals?
Every wire in SysDynLab carries a signal with a shape (R, C) where R is rows and C is columns. A scalar is (1, 1), a row vector is (1, N), a column vector is (N, 1), and a matrix is (R, C) with both > 1. Hover any wire to see its shape in the tooltip; non-scalar wires also get a thicker stroke and a small badge.
Creating Vectors and Matrices
Use a Constant block with the matrix literal syntax in its value field:
Each cell may contain any expression supported by the numeric evaluator (pi, sin(0), 1e-3, etc.). Trailing commas or mismatched row lengths produce a parse error.
Broadcasting Rules
SysDynLab uses scalar-only broadcasting by design — a scalar (1, 1) broadcasts to any shape, but two non-scalar operands must have identical shapes. NumPy-style row/column broadcasting is deliberately not supported, since it hides numerical bugs in dynamics models.
Wire Visuals
Scalar wires render as 1 px lines.
Vector/matrix wires get a thicker stroke proportional to log₂(N) with a small [R×C] badge near the source port.
Hover any wire to read its full (R, C) shape in the tooltip.
Element-Wise Math Blocks
Most math blocks operate element-wise on vector signals automatically:
Gain, Abs, Sign, Saturation, Dead Zone, Relay — unary, output shape = input shape.
Sum, Product, Divide, Min/Max — element-wise; non-scalar operands must share the same shape; scalars broadcast.
Stateful blocks (integrator, lpf, hpf, pid, transferfn, delay, …) accept vector input and run independent per-element state. Initial conditions broadcast across channels.
Mux & Demux — Packing and Unpacking
Mux concatenates its inputs into a single vector. mux(a, b, c) with three scalars produces (1, 3). With mixed scalar + vector inputs, widths add.
Demux splits a vector input into N scalar outputs (one per element).
Matrix Operations Block
Drop a Matrix Op from the sidebar and pick the operation from the dropdown. The block automatically resizes its input/output ports based on the operation. Switching ops removes any wires that no longer have valid landing ports (a status message reports how many were removed).
Op
Inputs
Outputs
Notes
Transpose
(R, C)
(C, R)
Row-major transpose.
Matrix Multiply
(M, K), (K, N)
(M, N)
Standard A·B. Inner dimensions must match.
Trace
(N, N)
scalar
Sum of diagonal. Square only.
Dot Product
two vectors of equal length
scalar
Σ ui vi.
Cross Product
two 3-element vectors
3-element vector
Preserves orientation.
Norm
(R, C)
scalar
Frobenius (√Σa²), ∞-norm (max row sum), 1-norm (max col sum). Sub-dropdown appears for type.
Determinant
(N, N)
scalar
LU decomposition + product of U diagonal × pivot sign.
Inverse
(N, N)
(N, N)
LU + back-substitution per column. Singular → zero output (NaN sentinel collapses under -ffast-math).
Solve A·x = b
(N, N), (N)
(N)
LU + back-substitution. Cheaper than computing inv(A)·b.
Cholesky
(N, N)
(N, N)
Lower triangular L such that A = L·LT. Requires symmetric positive-definite input.
LU Decomposition
(N, N)
L (N,N), U (N,N), P (N,N)
3 outputs. Partial pivoting. P·A = L·U.
QR Decomposition
(N, N)
Q (N,N), R (N,N)
Householder reflections. Q orthogonal, R upper triangular.
Symmetric Eig
(N, N)
eigvals (N), eigvecs (N, N)
Jacobi rotations. Symmetric matrices only. Each column of eigvecs is an eigenvector.
Storage Cap (N ≤ 11)
All matrix operations use stack-allocated buffers in the WASM engine. The current cap is N ≤ 11 per dimension (since 11×11 = 121 ≤ MAX_PORTS = 128). Inference rejects larger matrices with a clear error. This is plenty for typical control / Kalman / robotics use cases; larger matrices would need a dynamic-allocation refactor.
Input Validation Warnings
When the input matrix is a literal Constant block, SysDynLab statically validates SPD for chol and symmetry for eig. You will see banner errors like:
When the matrix is computed by upstream blocks, only shape and squareness are checked; failure produces an undefined output (typically zeros) instead of an error. Validate your input before relying on the result in those cases.
Displaying Vectors and Matrices
Display blocks auto-detect matrix shape and render an R×C grid with right-aligned, monospace-padded columns.
Plotter blocks expand vectors into multiple traces. A single Plotter port fed by a vector signal shows all elements overlaid; multiple ports get stacked subplots.
CSV Sink writes one column per scalar channel; vector signals are flattened row-major.
Code Generation Support
All 13 matrix operations are emitted by the C and Python code generators, using the simulator's own algorithms (LU with partial pivoting, Householder QR, Jacobi eigenvalues) as small helper functions, so the results match the simulation. No NumPy or LAPACK is needed.
Multi-output operations (lu, qr, eig) emit one signal variable per output port; downstream consumers reference them just like single-output blocks.
A signal carries at most 32 elements. Blocks with memory (Integrator, PID, filters, delays, discrete blocks) take at most 8 per block — Discrete PID 7. A wider signal is refused before the run, with a message naming the block.
N ≤ 11 per dimension for the matrix operations (stack-cap; lift requires dynamic allocation).
Symmetric eig only. General-matrix eigenvalues need the QR algorithm with shifts; a hand-rolled implementation would silently fail on edge cases. Treat as a future LAPACK-binding project.
Cholesky/eig validation is static only when the input traces directly to a Constant literal. Runtime input is not validated.
State-machine blocks remain scalar in expressions (SM array locals are vectors but not matrices).
Overview
SysDynLab turns a Subsystem into a self-contained C file (C99 or ANSI C89) or Python file. The C needs only <math.h>; the Python needs only the standard library. Every block in the library is supported, including vectors, matrix operations, state machines and enabled subsystems.
The generated step function does exactly what the simulator does in one time step: the same execution order, the same arithmetic, the same integration method. With the same dt and a fixed-step solver (Euler, Heun or RK4), generated code reproduces the simulation's results step for step — that is checked automatically for every block type.
How to Open the Code Generator
Open the Code tab and click Whole model, or select a subsystem and click Selected subsystem. (Right-click a Subsystem → Generate Code does the same; a lone State Machine also has Generate C in its editor.)
Choose the language and options in the left panel, then click ▶ Generate.
Copy or ↓ Download the result.
Options
Option
Notes
Integration
How continuous blocks (Integrator, Transfer Fn, PID, filters) advance each step: Runge-Kutta 4, Heun or Euler — the simulator's fixed-step solvers. It is preset to the solver you simulate with. The variable-step and implicit solvers (Auto, RK45, ESDIRK23, Implicit Euler, Trapezoidal) have no fixed-step equivalent, so the generated code's results then differ slightly from the simulation; simulate with RK4 to compare exactly.
Function / Class Name
Prefix of every generated name, e.g. motor_ctrl gives motor_ctrl_init, motor_ctrl_step, motor_ctrl_State (C) or class motor_ctrl (Python).
Data Type (C)
double matches the simulation. float is for targets without a 64-bit FPU; results then differ in the last digits.
C Standard
C99, or ANSI C89 for old compilers (no <stdint.h>, declarations only at block starts).
Optimization Level + GCC optimize attribute
Adds __attribute__((optimize("-O2"))) to the step function. Only GCC uses it; other compilers ignore it.
Linkage
static keeps the functions private to one .c file; extern exports them for use from other files.
const input pointer
Declares the inputs const real *u.
volatile state
Declares the state volatile, for state shared between an interrupt and the main loop.
NaN / Inf guard
Replaces a NaN or infinite output by 0 before returning it.
Generate header (.h)
Appends the prototypes to paste into a header file.
Generate test main()
Appends a main() that runs 1000 steps and prints the outputs: gcc -std=c99 model.c -lm && ./a.out.
Inline comments
Labels each block's code with its name.
Using the Generated C
The inputs are the subsystem's Port In blocks in port order (u[0], u[1], …) and the outputs its Port Out blocks (y[…]; a vector output takes several consecutive entries). Call _init once, then _step every dt seconds with t = k·dt:
static motor_ctrl_State st;
static double u[2], y[1];
static unsigned long k;
void control_init(void) { motor_ctrl_init(&st); }
void control_isr(void) { /* every 1 ms */
u[0] = read_setpoint();
u[1] = read_speed();
motor_ctrl_step(&st, k++ * 0.001, 0.001, u, y);
write_pwm(y[0]);
}
A subsystem with no memory (only gains, sums, …) has no State and its step takes no state argument.
Using the Generated Python
m = motor_ctrl()
dt = 0.001
for k in range(5000):
y = m.step([setpoint, speed], k * dt, dt)
What One Step Does
Every block computes its output, in the simulator's execution order. Blocks with memory (Integrator, Sample Delay, filters…) output their stored state, which is what lets feedback loops work.
Blocks that only read their input into memory (Sample Delay, Derivative, Transport Delay, forward Disc. Integrator) store this step's input.
The continuous states are integrated over dt with the chosen method.
Subsystems are flattened into one function, Tag Send/Read pairs and Mux/Demux become direct references, and an Enabled Subsystem's blocks are wrapped in if (enable != 0) with its hold or reset behaviour. State machines become their own small _ctx_t struct with _init and _step functions — the same code the editor's Generate C produces.
Transport Delay buffers are sized for the simulation dt at generation time; running the code with a larger dt shortens the delay. Noise uses the simulator's generator; with Seed = 0 the simulation picks a random seed, the generated code uses 12345.
Algebraic Loops
A feedback loop with no block that has memory is an algebraic loop. The generated file then starts with a warning naming the blocks; as in the simulation, each of them reads the others' values from the previous step. Insert a Sample Delay, Integrator or filter to break the loop properly.
Solver Selection
Auto (the default, recommended) — SysDynLab looks at the time constants in your model (transfer-function poles, filter cutoffs, PID derivative filters…).
If the slowest is more than 100× slower than the fastest, the model is stiff and Auto uses ESDIRK23; otherwise it uses RK45.
With dt left empty it also picks the step size, and the status bar names the block that limited it.
Solver
Order
Type
Best For
Euler
1
Fixed, Non-Stiff
Quick previews, educational. Fast but least accurate — needs very small dt.
Heun (RK2)
2
Fixed, Non-Stiff
Moderate accuracy at low cost. Good balance for simple systems.
RK4
4
Fixed, Non-Stiff
Excellent accuracy for most models. The workhorse fixed-step solver.
Implicit Euler
1
Fixed, Stiff
Unconditionally stable for stiff systems but low accuracy. Use when stability matters more than precision.
Trapezoidal
2
Fixed, Stiff
Good accuracy + stiff stability. Recommended fixed-step choice for stiff models.
RK45 Dormand-Prince
5
Variable, Non-Stiff
Highest accuracy. Adapts step size internally for optimal performance. Best for smooth, non-stiff dynamics.
ESDIRK23
3
Variable, Stiff
Adaptive + stiff-capable. For models with wide time-constant spread (fast & slow modes together).
When Is a System “Stiff”?
A system is stiff when it has both very fast and very slow dynamics simultaneously. Examples:
A fast filter (e.g. 1 kHz) inside a loop whose other dynamics take seconds
A transfer function with widely separated poles (e.g. s + 0.1 and s + 1000)
A PID whose derivative filter (N) is much faster than the plant it controls
A single fast filter on its own is not stiff — stiffness needs a large ratio between time scales.
Non-stiff solvers (Euler, Heun, RK4, RK45) require very small dt for stiff systems —
they can blow up or oscillate. Stiff solvers (Implicit Euler, Trapezoidal, ESDIRK23) remain stable
even with larger dt.
Step Size (dt) Guide
Leave dt empty (recommended) — SysDynLab auto-suggests dt from your model’s fastest dynamics
(sine frequencies, filter cutoffs, transfer function poles).
Solver
Samples per Period
Rule of Thumb
Euler
~50
dt < 1 / (50 × fmax)
Heun
~30
dt < 1 / (30 × fmax)
RK4
~15
dt < 1 / (15 × fmax)
Implicit Euler
~15
Stable at any dt, but accuracy needs ~15/period
Trapezoidal
~10
dt < 1 / (10 × fmax)
RK45
~10
Adapts automatically — dt sets the output resolution
ESDIRK23
~7
Adapts automatically — dt sets the output resolution
fmax = fastest frequency in your model (highest sine freq, filter cutoff, or natural frequency).
Examples
Model
Recommended Solver
Typical dt
Step → 1st order TF (1/(s+1)) → Scope
RK4
0.01 – 0.05
Sine (10 Hz) → Gain → Scope
RK4
0.005
Step → PID → Plant with fast pole (1/(0.001s+1))
Trapezoidal or ESDIRK23
0.0001 – 0.001
Chirp (0.1–100 Hz) → LP filter (50 Hz) → Scope
RK45
auto (0.001)
Discrete PID + Sample Delay (no continuous blocks)
Euler (any)
Match sample time
Discrete Blocks
Discrete blocks (Sample Delay, Zero-Order Hold, Discrete TF, Discrete PID) update once every Ts seconds and hold their output in between.
Ts = -1 means "every simulation step". For exact sampling, dt must divide Ts evenly (Ts = 0.01 → dt = 0.01, 0.005, 0.001…).
If dt is larger than Ts, the block can only run every dt and SysDynLab warns you. Auto dt takes sample times into account.
A purely discrete model works with any solver. (Disc. Integrator and Disc. Derivative currently update every step: keep their Ts at -1.)
Troubleshooting
Output explodes / NaN: dt is too large, or you need a stiff solver. Reduce dt or switch to Trapezoidal / ESDIRK23. The status bar now names the block where divergence was first detected and the time it happened.
"Results may be inaccurate" warning: the solver ran out of sub-steps or its implicit solve did not converge. The numbers are still shown but are not trustworthy — reduce dt, or switch to a stiff solver (ESDIRK23) if the model has fast dynamics.
Oscillating output: Euler on a stiff system. Switch to Implicit Euler or Trapezoidal.
Simulation too slow: dt is too small or solver is inefficient. Try RK4 (fewer steps needed) or RK45 (auto-adapts).
Step response looks stepped: dt is too large to capture the dynamics. Reduce dt.
Plotter looks wrong at high zoom: Increase the Stop time or reduce dt for more data points.
Simulator & Solver Limitations
SysDynLab is designed for education and rapid prototyping. The following constraints apply:
Hard Limits (compile-time constants in the WASM engine)
Resource
Limit
Notes
Blocks
512
Total flattened blocks after subsystem expansion. A subsystem with 50 blocks uses 50 of the 512 slots, not 1.
Wires
2 048
Total connections across the whole model.
Inputs per block
8
Sum, Mux, Product, Expression and similar blocks accept up to 8 inputs (a Plotter up to 10).
Continuous state slots
6 144 (512 × 12)
Integrator, filter, and TF state variables combined.
Implicit solver state size
128
Stiff solvers (Implicit Euler / Trapezoidal / ESDIRK23) build a dense Jacobian, which is capped at 128 continuous state variables. Above that the engine automatically runs RK45 instead and says so in the status bar.
SM states per block
64
Includes all hierarchy levels and parallel region states within one State Machine block.
SM transitions per block
128
Total transitions (all levels) within one State Machine block.
SM local variables
16
Per State Machine block.
Scope ring buffer
2 M doubles (~16 MB)
Shared across all Plotter channels. Wraps (oldest data overwritten) when full on long runs.
Numerical Precision
Item
Limitation
Floating-point precision
All signals are 64-bit doubles (IEEE 754). Underflow near ±5×10−324 and overflow near ±1.8×10308 produce ±Inf or NaN — the simulation does not automatically detect or recover from this.
Fixed-step accuracy
Euler and Heun accumulate global error proportional to dt. Halving dt roughly halves (Euler) or quarters (Heun) the error. For high-accuracy results use RK4 or variable-step RK45.
Variable-step resolution
RK45 and ESDIRK23 adapt step size internally but still record output at the user-specified dt intervals. Very coarse dt hides the fine-grained solution — reduce dt to improve plot resolution even with adaptive solvers.
Stiff systems with non-stiff solvers
Euler/Heun/RK4 require dt < 2 / |λmax| for stability. A fast pole (e.g. τ = 0.001 s) forces dt < 0.002 — which is often impractical. Switch to a stiff solver.
Model Constraints
Constraint
Detail
Algebraic loops
A feedback path with no state-holding block (Integrator, Unit Delay, Transfer Function, etc.) is an algebraic loop. The simulator warns and uses best-effort ordering, but results may be incorrect. Insert an Integrator or Unit Delay to break the loop.
Transfer function order
The denominator polynomial must have degree ≥ numerator degree (proper or strictly proper). An improper TF (e.g. s/(s+1) is fine; s²/(s+1) is not) will cause incorrect or unstable behaviour.
Discrete sample time mixing
All discrete blocks share the simulation step dt as their base sample time. Blocks with different Ts values are evaluated at each step but only update when their counter triggers — this is not true multirate scheduling and can introduce up to one dt of sample-time error.
Vector signals
Up to 32 elements per wire. Math, nonlinear, continuous and discrete blocks work element by element; see the Vectors & Matrices tab for details.
Delay block
The Transport Delay uses a ring buffer sized to ceil(delay / dt) samples. Very large delay-to-dt ratios consume memory proportionally. A delay of 10 s at dt = 0.0001 s allocates 100 000 doubles per delay block.
State machine expressions
Arithmetic, comparisons, logic, bitwise operators, built-in math functions, and array locals / vector ports (buf[i]). No strings, no user-defined functions, and no ?: operator.
No zero-crossing detection
State machine transitions are evaluated at each time step, not at the exact moment a condition becomes true. A fast event shorter than dt may be missed. Reduce dt or use a discrete block with a matching Ts.
Browser & WASM Constraints
Constraint
Detail
WASM memory ceiling
Initial heap is 64 MB; the runtime can grow up to 512 MB as needed. Very large scope buffers or many delay blocks may push into this range on long simulations.
Plotter data retention
Scope data is held in browser memory. A 100 s simulation at dt = 0.0001 s stores 1 million data points per channel — this can slow plot rendering.
Single-threaded execution
The simulation runs on the main browser thread. Very long simulations will block the UI momentarily per frame.
No persistent storage
Models are not saved automatically. Use Ctrl+S to download an XML file. Closing or refreshing the tab loses all unsaved work.
File access
CSV import, model load/save, and code download all use the browser file picker — there is no direct filesystem access.
What SysDynLab Does Not Support
Partial differential equations (PDEs) — only lumped (ODE) systems.
Distributed-parameter delay — only fixed transport delay with a ring buffer.
Multi-body physics or 3-D simulation.
Real-time hardware I/O — simulation is as-fast-as-possible, not wall-clock locked.
Symbolic math — all parameters must be numeric constants evaluated before simulation starts.
Automatic differentiation for sensitivity or gradient computation.
Optimization or parameter sweeps — run multiple simulations manually and compare Plotter traces.
Tip: For most control and signal-processing models these limits are never hit. If you encounter NaN outputs, check for algebraic loops first, then reduce dt. If the simulation is unexpectedly slow, switch from Euler to RK4 — it needs far fewer steps for the same accuracy.
Keyboard Shortcuts
Action
Shortcut
Undo
Ctrl+Z
Redo
Ctrl+Y
Save model
Ctrl+S
Open model
Ctrl+O
Copy selection
Ctrl+C
Cut selection
Ctrl+X
Paste
Ctrl+V
Duplicate selection
Ctrl+D
Select all
Ctrl+A
Delete selection
Delete or Backspace
Cancel wire / exit subsystem
Escape
Apply block popup
Enter (while popup is open)
Nudge selected blocks
Arrow keys (10 px) / Shift+arrow (1 px)
Remove last corner while drawing a wire
Backspace
Open Help
F1 (Esc closes it)
Canvas & plots
Action
How
Pan the canvas
Middle-button drag, or Alt+drag on empty canvas
Zoom in / out
Scroll wheel on canvas
Enter subsystem
Double-click a Subsystem block
Exit subsystem
Click parent name in breadcrumb, or Escape
Open scope viewer
Double-click Plotter block after running
Zoom scope X or Y
Click Zoom X / Zoom Y → click two points on the plot
Pan scope plot
Drag on the scope canvas
Zoom scope with wheel
Scroll on scope canvas
Reset scope view
Click Fit in scope toolbar
Phones and tablets
Gesture
Does
Tap ☰ (top left)
Show or hide the block library
Tap a library block
Place it in the middle of the canvas
Press and hold a library block, then drag
Drop it where you want it (a quick swipe scrolls the list instead)
Drag a block, port or wire
Move it / draw a wire / move a wire segment, as with a mouse
Drag on empty canvas, or two-finger drag
Pan
Pinch
Zoom
Tap
Select; tap a port, then another, to connect them
Double-tap a block
Open its settings (a Subsystem: go inside)
Long-press a block or wire
Its right-click menu
Selecting, moving, wiring and the right-click menu are described in Blocks & Wiring.
On macOS the keyboard shortcuts use Ctrl (not Cmd); wire branching accepts either.
About SysDynLab
SysDynLab is a browser-based block diagram simulator for building and running continuous and discrete-time dynamic systems. It runs entirely in the browser using WebAssembly — no installation, no server-side computation.
Built with vanilla JavaScript, WebAssembly (Emscripten / C), and no external frameworks.
Disclaimer
No Warranty. Use at Your Own Risk.
SysDynLab is provided "as is", without warranty of any kind, express or implied, including but not limited to warranties of merchantability, fitness for a particular purpose, or numerical accuracy.
The simulation results produced by SysDynLab are approximations based on the numerical methods and model parameters you configure. Results may differ from real-world behaviour due to solver discretization error, model simplifications, floating-point rounding, or incorrect parameter values.
The user assumes full responsibility for verifying the correctness of any simulation result before using it for design, engineering, safety analysis, or any other purpose. SysDynLab and its authors accept no liability for decisions made based on simulation output.
Do not rely on SysDynLab results in safety-critical applications without independent verification by qualified engineers using certified tools.
License
A note on the future of SysDynLab:
If there is enough community interest and people are willing to contribute, I am open to releasing SysDynLab as a fully open-source project on GitHub — so that everyone can improve it together. If you would like to see that happen, send a message via the Feedback button. Enough voices will make it happen.
SysDynLab is currently distributed under a Source-Available License. The source code is published for transparency and personal study. It is not open-source under OSI definitions.
SysDynLab Source-Available License
Copyright (c) 2025 SysDynLab. All rights reserved.
Permission is granted, free of charge, to any individual to:
- Use this software for personal, academic, or educational purposes.
- Read and study the source code for learning purposes.
- Build and run the software locally for non-commercial use.
- Create and share simulation models (.xml files) made with this software.
The following are expressly PROHIBITED without prior written permission
from the copyright holder:
1. Hosting or deploying this software (or any modified version of it)
as a public or private web service, website, or application.
2. Redistributing, sublicensing, or publishing the source code or any
substantial portion of it, whether modified or unmodified.
3. Using this software or its source code as the basis for a competing
product or service, commercial or otherwise.
4. Removing or altering this license notice or any copyright attribution.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, OR NONINFRINGEMENT. IN NO EVENT SHALL
THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY
ARISING FROM THE USE OF OR INABILITY TO USE THIS SOFTWARE.
To request a commercial license or special permission, contact:
sysdynlab.simulator@gmail.com
Third-Party Components
Component
Purpose
License
Emscripten
Compiles the C simulation engine to WebAssembly
MIT / University of Illinois
WebAssembly
Browser runtime for the simulation engine
W3C open standard
Send Feedback✕
Your feedback directly improves the simulator. Thank you!