The browser simulator
See your model work before you install anything else. The browser simulator runs a model's behaviour — its state machines, its ```arche bodies, the messages on its links, a deployment's periodic threads — on a virtual clock, in plain JavaScript. It runs in four places, all the same code:
| Where | How |
|---|---|
| A terminal | archec sim main.arche prints the trace |
| Any browser | archec sim main.arche --html sim.html writes one page that runs it: open it, press Play |
| VS Code | the ▶ Run button of the diagram panel, or Arche: Simulate in Diagram (arche.sim.run), or ▶ Simulate above a system |
| The Arche Store | a package whose model does something has a Try it link: it runs in the visitor's browser |
Arche's own simulator (archec test, archec run) is the reference: it runs the
model's Python, charges each body its wcet, models the CPUs, the buses and
faults, and checks test plans. The browser simulator is the quick look: nothing
to install, and every step visible.
A first run#
message BLINK { on: bool }
component Blinker "Blinker" {
out led: data sends BLINK
var blinks: int32 = 0
states {
state Off {
on-enter ```arche
send led BLINK { on: false }
```
}
state On {
on-enter ```arche
blinks += 1
send led BLINK { on: true }
```
}
Off -> On on "after 500ms"
On -> Off on "after 500ms"
}
}
component Led "LED" {
in pin: data receives BLINK {
handler BLINK -> show
}
var lit: bool = false
fn show(on: bool) ```arche
lit = on
}
system Desk { part blinker: Blinker part led: Led blinker.led -> led.pin }
```text
$ archec sim main.arche --for 1s
0.000 ms blinker: Off
0.000 ms blinker.led → led.pin BLINK {"on":false}
500.000 ms blinker: Off → On
500.000 ms blinker.led → led.pin BLINK {"on":true}
1.000 s blinker: On → Off
1.000 s blinker.led → led.pin BLINK {"on":false}
--json gives the same trace as data: each event has its time t (ns) and its
kind — send (from and to as part.port, msg, payload, arrive),
state (part, from, to), ignored (a message the part had nothing to do
with in its state), drop (sent on a port linked to nothing), log, note
(something not simulated, once) and error (a division by zero, a loop that
never waits). The same model gives the same trace, every time.
What it simulates#
Parts and links. Every part of the system (the active deployment's, or the first that does something;
--systemnames one). A message sent on a port reaches every port linked to it, through composites and delegated ports (port out = mcu.i2c), after the links'latency.One thing at a time per part. A message that arrives while the part is busy (in a
wait) waits its turn, as in Arche.State machines. A machine starts in its initial state (its
on-enterruns at 0). A message fires the first transition on it from the current state, a state around it, or*, whose guard holds; thenon-exit, the action, the new state,on-enter. A composite state enters its initial child; apseudo choicetakes its first transition whose guard holds.on "after 500ms"fires when the state has lasted that long. A guard may read the trigger's fields:Answered -> Waiting on PING [n > 2].Bodies. The action language, all of it, translated to JavaScript (
waitsuspends the body;gotomoves the machine; an integer divided by an integer is an integer). Python too, when it is the action language in other words (self.x = …,await wait(ms(2)),send(self.p, M(k=v)),await goto("S")— the same translationarchec genuses). TypeScript, all of it, as Arche runs it (handlers.md): its types blanked out,selfthe part,msgthe message that triggered it, Arche's API (await wait(ms(2)),send(self.p, M({ k: v })),reply(…),await goto("S"),await self.f(x), the seededrand()); a guard in a TypeScript component may be TypeScript ([msg.force > 0 && !self.open]).What starts things.
fn mainruns from 0, as a thread of its own. A deployment's thread with aperiodand astepcalls that function each period, the first at one period. A port'son-receiveand its handlers (handler MSG -> fn, the parameters taken from the message's fields by name, else in order) run when a message arrives.Sensors and actuators. A sensor's out port adds its
noiseto what it sends, saturates it by the field'srangeand rounds it to itsresolution; an actuator's in port clamps its command to the field'srange, and its output follows through itsgain,lagandforce-range(adriveline in the trace at each command, a plot in the page). A port'slatencyadds to its links'. The noise is drawn from a seed (--seed,simulation { seed }, else 1): the same run each time, and the page's Reset and time scrubber replay the same draws (spec).Keyframes.
--keyframe raisedstarts from that keyframe of the system: its parts' variables, its actuators' commands (a joint's position and velocity, a ball's or a free body's, are the plant's).
What it does not simulate#
It says so rather than guess: each of these is listed under "not simulated" (in
the terminal, in the page, in VS Code) and is a note in the trace when it
would have run.
- Python the action language cannot say (a list, a library call,
a if c else b…): the function does nothing and returns its type's zero. - A transition whose guard or action is such Python: it is never taken.
- TypeScript that calls what Arche's simulator alone runs (
recv,select,call, locks, bits, streams, timers,wait_until,clock): the body stops with an error that says so.Math.randomandDatestop it too: a run must replay. - A trigger that is not a message and not
"after …"(on boot_done,on "self test passed"): nothing in the simulation fires it — a test step or another part'sgotowould, in Arche. - Execution time (
wcet), CPUs and their scheduling, buses and their bitrate, faults, plants and FMUs, memory and signals, test plans: Arche's simulator does those.
Compared with Arche's simulator on examples/live (test/websim.test.js does it
when arche-cli is beside the checkout), the two give the same messages with the
same payloads and the same state changes in the same order; the times differ by
the execution times Arche charges.
The same model with its behaviour in TypeScript and in Python (examples/ts-handlers,
and each template with its ```arche bodies written out in each language) gives the
same trace in archec sim, the same verdicts against its MuJoCo plant, and the same
verdicts in Arche's simulator; test/tsreach.test.js runs them all.
A timed transition, Braking -> Released on "after 25ms" (written to the Arche
project as trigger: after 25ms), is taken by both simulators, whatever the
handler language: after the delay spent in its state, restarted when the state is
entered again, cancelled when it is left.
Against a plant#
archec sim main.arche --plant mujoco/plant_bridge.py runs the model against a plant
that runs elsewhere: the MuJoCo one archec gen --target mujoco writes (see
MuJoCo), or any command that speaks its JSON lines. The two go
in lockstep, every period the plant says: its sensors' messages come in (an input in
the trace) as if their parts sent them along their links, the model runs one period,
and the messages it sent to the plant's actuators go back, each applied from its
arrival. The plant reads and acts as the model says (noise, latency, ctrl range, lag…:
MuJoCo and the bridge do it, from the same seed), and starts from --keyframe when one is
given.
--test runs the model's test plans against the plant: each case is a run of its own,
with a new plant, as long as its timeline. What the case reads is the parts' variables
and states, so a controller keeps what it measured (controller.q1):
test-plan TP-ARM "The arm reaches its poses" {
case TC-ARM-STEP "A second pose" {
at 2s do controller.target_shoulder = -0.4
at 4s expect abs(controller.q1 + 0.4) < 0.02
at 4s expect never: controller.error > 1.5
}
}
| Moment | What it does |
|---|---|
at T do part.var = v |
sets the variable at T |
at T do send(part.port, MSG(f=v)) |
sends the message from that port at T, along its links |
at T expect c |
c is true at T (after what the plant sent at T) |
within D expect c |
c is true at some time up to D after the last at |
at T expect never: c |
c is never true from the at before to T, at each period |
at T expect always: c |
c is true all along it |
A condition is Python as the test plan writes it: part.var, part.state, the model's
constants, numbers and strings, and or not, abs min max round; a chained comparison
(a < x < b) is refused. A case that fails says what was not so, when, and the values it
read; one that cannot run (a name it cannot read, a fault) says why. --json gives
the cases with each check. The plants are tested so.
$ archec sim main.arche --plant out/mujoco/plant_bridge.py --test
Test cases against out/mujoco/plant_bridge.py
✓ TP-ARM TC-ARM-REACH Reach the first pose (2.000 s)
✓ TP-ARM TC-ARM-HOLD Hold it (3.000 s)
✓ TP-ARM TC-ARM-STEP A second pose (4.000 s)
3 of 3 case(s) passed
The page#
archec sim main.arche --html sim.html writes one file with everything in it:
the sim program and its controls' data (JSON), the runtime, the live simulator
and the player. It fetches nothing, so it works from a file, offline, or on any
static host. The model runs in the page, live — as MuJoCo's viewer runs a scene,
you can stop it, step it, slow it down and poke it:
- Play / Pause (space), Step (→: to the next event), Step by a fixed time (Shift+→; ← goes back by it), Reset (R: from the start, nothing sent).
- Speed, 0.1× to 10× (+ and −). 1× shows the model's shortest period or timeout a second (a second a second for a physical network alone); the slider's tooltip says how much.
- Time: a scrubber over what has run. Going back replays the run up to that time, with what you sent at the times you sent it, so it is the same run; forward again, it plays the same. Sending something while back starts a new run from there: what you sent after that time is dropped. Home goes to 0.
- Send a message: for each input port of a part that does something, the
messages it receives (
receives, its handlers; else its machine's triggers), each field a control — a number with its range (written, or its type's), a checkbox, an enum's values, a record's fields — checked before it is sent. Send now puts it on the port at the current time, as a link would bring it, after what happened at that instant; the trace shows it asyou → part.port. - Push: when the system holds a physical network (ports with a
potentialand aflow,archec physics), the page integrates it with the events (RK4 at itssolverstep) and its sources — the parameter of a voltage, current, pressure or heat source, a force, a torque, a fixed temperature — and itsinsignals linked to nothing can be pushed: a step (added from now on) or a pulse (added for the time given). - Plots: any state (as a step line, its states on the axis), number variable, message field (the last value sent or received) or physical quantity, plotted live from 0 to now. The page starts with the network's first states, or the first state machine and variable.
- Each message is a dot moving along its link; each part shows its state and its first variables in its box; the trace runs on the right.
--forsets how long the run lasts (10 s by default).
The live simulator is window.archeSim in the page's console: archeSim.inject('pong', 'rx', 'PING', { n: 7 }), archeSim.stepBy(10e6), archeSim.series.
In VS Code#
The diagram panel's ▶ button (or Arche: Simulate in Diagram) runs the current model's system live, in the extension (the webview evaluates nothing), and plays it on the diagram you already have: dots along the drawn links, the state on each block, the current state lit in a state-machine diagram. Beside it, the same controls as the page: pause (space), the next event (→), a step by a fixed time (Shift+→; ← back by it), reset (R), the speed (+ / −), the clock, the time scrubber (Home: to 0) and stop. ☰ opens the controls — send a message, push, plots, keys — and opens by itself when the model has an input port or a physical network. The trace goes to the Arche Simulation output as it runs. Edit the model and press ▶ again: it runs the new one.
In the store#
archec store site gives each package whose model does something a Try it
link: p/<owner>/<name>.sim.html, the same page, built from the package's
model. A visitor sees it run without installing anything.