The flags
| Flag | Value | What it is for |
|---|---|---|
--help | — | Print the flag roster and exit |
--console | — | Open the operator console at launch, on a display other than the show’s |
--list-devices | — | Enumerate audio capture endpoints and exit (Windows-only) |
--list-adapters | — | Enumerate graphics adapters and exit, from both rosters |
--list-presets | — | List the presets this launch would load, with each file’s status, and exit |
--schema | — | Print the preset schema as JSON on stdout and exit |
--check | <path> | Check a preset file, or a directory of them, and exit |
--strict | — | Make a --check warning cost the same exit code an error does. Needs --check |
--events | — | Report as JSON lines on stderr, for a parent process |
--preview | stdout | stdout@WxH | Mirror the windowed show to a parent process, as a fixed-size copy |
--input | loopback | line-in | Where audio comes from (Windows-only) |
--device | "<friendly name>" | Which capture endpoint to open (Windows-only) |
--tier | floor | rich | Pin the quality tier instead of letting the engine pick |
--grid-scale | 0.25–1 | auto | Draw the internal grids at this fraction of the frame — the window and --stream both. Overrides [quality] grid_scale for one run |
--osc | <host:port> | Publish analyzer telemetry as OSC over UDP, and turn the sink on |
--control | <host:port> | Listen for studio control messages as OSC over UDP, and turn the listener on |
--soak | [path] | Write a long-run frame-time trace; bare, a default path |
--downbeat-log | [path] | Write the per-beat downbeat decomposition; bare, a default path |
--stream | — | Run headless and publish every frame to a sink |
--sink | spout | stdout | Where the published frames go, default spout. Needs --stream |
--size | WxH | Published frame size; default 1280x720 on --sink spout, 640x360 on --sink stdout. Needs --stream |
--fps | <n> | Published frame rate; default 60 on --sink spout, 30 on --sink stdout. Needs --stream |
--sender | <name> | The published Spout sender name, default ritmolux. Needs --stream |
--frames | <n> | Stop after this many frames. Needs --stream |
--gpu | <name|index> | Which graphics adapter to render on — the window and --stream both. Overrides [output] gpu for one run |
--preset | <name> | Hold one scene and disable rotation |
A flag marked Needs --stream passed without it is a startup error naming both flags, rather
than silence.
The ones that need a paragraph
--help writes to stdout and creates no window, no GPU device and no capture client, so a
script can probe the flag surface without starting a show.
--events turns on the structured report a parent process reads
(the control protocol). One JSON object per line on
standard error: hello with this build’s version, the schema hash and the control port
actually bound; preset and roster as the show moves; preset_error and preset_warning with
the file, the message and — when the TOML parser gives a position — the line and column;
health once a second while frames are being drawn; and pong answering a ctl/ping.
preset_error also covers a ctl/preset the player declined to select: there its file holds
the name that was asked for rather than a path, and the message says the selection did not take. It
is the same event because it is the same fact to whoever asked — the preset you clicked is not on
screen — and without it a click on a name the roster does not hold produced nothing at all.
health reports the control listener alongside the frame timings: ctl_rejected, ctl_dropped
and ctl_refused are what the path discarded, and ctl_received, ctl_recv_errors and
ctl_listening are the listener’s own state — datagrams the socket handed it, receive failures
that were not the ordinary read timeout, and whether the receive loop is still running. Read
together they say which of “nothing was sent”, “something arrived and was discarded”, “the socket
is failing” and “the listener has gone” a silent control path is. A run without a listener reports
zeros and "ctl_listening":false.
Every event line begins with { and no human diagnostic does, so a parent splits the two on the
first byte and needs no framing. The flag is purely additive: without it standard error carries
exactly the lines it always did, and with it those same lines are still there, unchanged, beside
the events.
--preview stdout mirrors a windowed run’s frames to whatever spawned it, as raw 8-bit
frames announced by the same stream event --stream --sink stdout announces. It is how a studio
watches the show that is actually on the projector, rather than a second headless one.
The mirror is a fixed-size copy, and the size is the mirror’s own. --preview stdout sends
640x360; --preview stdout@WIDTHxHEIGHT asks for something else. The show keeps the window’s
resolution and the mirror is a scaled copy of it, letterboxed so a window of any shape keeps its
aspect. The size is answered once, before the first frame, and a resize, a maximize or a
fullscreen toggle does not move it — which is what lets a reader cut the byte stream into frames
by a number it was told at the start. That is the one place --preview and --sink stdout differ
in kind: the latter is an exact feed at the size named on the command line, for ffmpeg, and it is
unchanged by any of this.
Off by default, and the default matters: with the flag absent nothing is allocated, no frame is copied twice, and the show draws exactly what it drew before the flag existed. With it on, the frame is routed through the operator console’s own intermediate, blitted into the mirror’s fixed target, copied to a staging buffer, and read back one frame late with a non-blocking poll — so the display loop never waits for it.
It drops rather than stalls. A reader slower than the show loses frames and the show carries
on; the count of what was dropped is written to diagnostics.log beside the frame-time figures,
so the cost of the mirror is measurable rather than assumed. That is the opposite of the headless
--sink stdout policy, which blocks — a headless loop has no present deadline and a windowed one
does. See Capturing.
--schema answers the other question a program asks before it starts driving the player: what
a preset may contain. It prints one JSON object on stdout — every system and engine stage with its
parameters (name, default, range, and the line that says what each does), every structural table
with its keys and the closed rosters they draw from, the expression grammar’s identifier rosters,
and a hash of all of it. Like --help it creates nothing and exits, and it moves no files on
the way.
The grammar object carries three arrays — variables, functions and constants — which is
what an expression editor colours from. variables is what the parser’s identifier lookup
accepts, so the reserved [latch] placeholders are absent: an author reaches a latch through the
name they declared for it, and a preset’s own latch names are in the preset rather than here.
The parameter half is rendered from the same declarations the reference table in
the preset library’s README is, so the two cannot disagree. The hash
changes when and only when the document does: a studio that cached the schema compares it against
the one the player reports on startup to know whether its panels are stale.
--check <path> is the verdict on a preset before it is rendered. It runs the engine’s own
loader — the same one a show runs — and prints what that loader said, one line per diagnostic on
stdout:
presets/fragment_tunnel.toml:34:1: error[engine]: parameter 'glow' has an invalid expression: unexpected end of expressionThe shape is path:line:col: severity[rule]: message, which an editor’s problem matcher reads
directly. Line and column are 1-based and the column counts characters rather than bytes. A summary
line — files checked, errors, warnings — goes to stderr, so a caller piping stdout into a matcher
gets diagnostics alone on the stream it parses.
<path> is a file, or a directory whose *.toml files are checked non-recursively: a
subdirectory such as presets/pending/ is held back by construction, which is the same convention
the shipped set is embedded under. An empty directory is not an error — the summary says checked 0 files, so a run over nothing reports itself rather than passing silently.
Exit codes: 0 clean, 1 a preset failed, 2 the command was wrong. A path naming nothing is
2, because a missing file is the spelling rather than the content. --strict moves a warning into
the failing class, which is what a gate wants and what an author mid-edit does not: the class the
loader forgives on purpose is a parameter name it does not recognise, where the binding is kept and
nothing reads it — a silent typo, and the one --strict exists for.
Like --help and --schema it creates no window, no GPU device and no capture client, so an
author’s “does this compile” loop costs one process start. Nothing about it writes: there is no
--fix and no formatter, and ADR-0190
records the measurement that decided it.
--console is a presence flag with no value: it turns the console on for this run and
never off, and it does not write itself into config.toml (the same shape --input, --device
and --osc follow). [console] enabled = true is the persistent form, and the C hotkey and the
settings menu’s Console row are the same path — no two of the four can disagree about whether
the console is open.
--list-adapters prints both rosters: the one the renderer selects through and the one the
Spout sender selects through. They are separate enumerations and are not assumed to agree on order,
so both are printed with their own indices.
--list-presets prints one row per preset this launch would load — the display name, the file
it came from, and how that file stands against the set this build ships — then exits without a
window. It reads and never writes: it does not seed, and nothing in the preset directory is ever
deleted, overwritten or migrated by it or by anything else.
The status column has three values. shipped is a file byte-identical to the copy this build
carries. differs is a file whose name this build ships under different bytes: your edit, or a
copy from an older release that seeding left alone — nothing on disk records which, so the two
cannot be told apart. not shipped is a file this build’s set does not have at all, either your
own preset or one a later release retired.
A row also says when two files claim one display name. Only the first in filename order is reachable
by name, so the second is loaded and rotated through but cannot be selected by --preset, by the
studio, or by the browse overlay. The same drift is summarised in one startup line, printed only
when something has drifted, so an untouched install says nothing.
That startup line is for the seeded per-user directory only — the one the app writes the curated
set into on first run. With RLX_PRESET_DIR set, the app neither seeds nor reports: that directory
is yours, differing from the shipped set is usually the point of it, and --list-presets is the only
reading of its drift.
--gpu <name|index> works for both the window and --stream. On a machine with one GPU you
will never need it; on a hybrid laptop it is what decides which GPU the show runs on. A
Spout sender shares a D3D11 texture by handle and the receiver opens it on its own device, which
works only when both are the same physical GPU — and Windows hands a plain console process the
power-saving GPU while the receiving application runs on the discrete one. One flag moves both
halves: the renderer and the sender each resolve the name against their own roster. Unset,
--stream’s renderer asks for the high-performance adapter and the sender follows it by name,
printing what both resolved to.
The window asks for the same thing unset: the high-performance adapter, which on a hybrid
laptop is the discrete GPU rather than the power-saving one the graphics layer would otherwise hand
a window. The startup line in diagnostics.log names the adapter and which carrier chose it —
(default: high performance), (pinned by --gpu) or (from config.toml [output] gpu). For the
window the flag is the per-run override of a stored choice: [output] gpu holds the adapter by
name in config.toml, the settings menu’s Adapter row writes it, and --gpu wins over the key
for one launch without writing it — the precedence --tier has over [quality] tier. The two
carriers fail differently on purpose. A --gpu that names an adapter this machine does not have, or
one that cannot drive the window, is a startup error rather than a quiet fall-back to another GPU.
A stored [output] gpu that no longer resolves — a laptop undocked from its eGPU, a driver that
renamed the card — starts anyway on the default preference, with one line on stderr and a startup
note naming both what the file asked for and what was taken: a file outlives the machine state that
made it valid, and a show that will not start is the wrong failure for a persisted preference.
--preset <name> takes the preset’s display name — Clifford, Rose Window — as the
browse overlay and --preset’s own error listing spell it, not the .toml filename, so most of
them need quoting. An unknown name is a startup error that lists the roster, and no window
opens. Hotkeys still browse, so this pins where a run starts and turns the dwell timer off.
--input loopback|line-in overrides [input] mode. loopback taps whatever the system is
playing; line-in captures an input endpoint (an audio interface, a mixer feed). A value that is
neither is a usage error and the app exits, the same way a bad --tier does. The input also
moves while the app is running — the settings menu’s Input mode and Input device rows swap
the capture stream in place and write the choice to config.toml, so --input pins the launch
rather than the session. Expect a brief hitch on the swap (the old stream is stopped and the new
one opened synchronously), and, when the two endpoints negotiate different sample rates, a second
or two of re-adaptation while the level tracking rebuilds.
--device "<friendly name>" overrides [input] device. Copy a name out of --list-devices; a
substring is enough. The two flags override independently — --device alone keeps the
configured mode, --input alone keeps the configured device name. A name that matches no active
endpoint of the selected mode is not an error: capture falls back to that mode’s default endpoint
and says so on stderr, because the interface being unplugged is a fact about the world rather than
a typo in the flag. Giving no name at all — a trailing --device, or --device= — is an
error: an empty value selects the default endpoint, which is the opposite of what naming a device
asks for. What happens when an endpoint disappears mid-show is in
Running the app.
--stream runs headless as a live video source and publishes every frame as a Spout
sender, for TouchDesigner (or any Spout receiver) on the same machine. No window, no swapchain,
no codec. Windows-only, and present only in a build with the spout feature — the release
ritmolux.exe has it; a plain cargo build does not, and --stream there fails with a named error
saying so. Presets rotate on the operator config’s dwell timer exactly as they do in the window
(rotation is on here even where [rotate] auto is off, since a headless source has nobody to
press Space). Ctrl-C stops it and prints the run’s frames, wall clock and scene clock. See
Headless capture and video for the TouchDesigner
side.
--tier floor|rich pins the quality tier. Unpinned, the app starts on rich and a frame-time
governor demotes it to floor once if the display’s frame budget is not being held. A pin is never
demoted, so this is also how you keep rich on a machine a transient stall demoted. The tier also
moves while the app is running — see Quality tiers.
--grid-scale <0.25..1|auto> sets the fraction of the frame the internal grids are drawn at —
the post stages’ grid (trails, kaleidoscope, bloom) and the attractor’s trail field. Those grids are
where a heavy preset spends its frame, and their cost follows their area, so 0.5 draws a quarter of
the texels at a visible loss of sharpness and nothing else: the picture’s shape does not move. auto
lets the engine pick one for the tier and the kind of GPU from the table under
[quality]: 0.75 for rich on an integrated GPU, 1 everywhere else. Under
--stream it is the only way to draw at less than full size: a headless run takes 1 on any GPU,
so two machines of different kinds capture the same grids, and neither [quality] grid_scale nor
RLX_GRID_SCALE reaches it. A value outside 0.25–1 is a usage error naming the range, and the
app exits, the same way a bad --tier does. The scale in force is printed beside the tier in the
F3 overlay, and in the header of --stream’s pass-cost table together with both grids’ sizes.
--osc <host:port> both aims the sink and turns it on, so enabled = false in config.toml
cannot veto a target typed for this run. Off unless you ask for it. A target that will not
resolve is a usage error and the app exits — the same way a bad --tier does — whereas a stale
target in config.toml degrades to no sink and says so, because a config file must not stop a
show. Sends are non-blocking and dropped on failure: a broken link costs the telemetry, never a
frame, and the app prints one line when it starts failing and one when it recovers rather than a
line per frame.
The one flag --help does not print
--thumb <name> renders one preset’s browser thumbnail into the cache and exits. It is
deliberately absent from the roster, because the process that passes it is the player itself: the
app re-invokes its own executable one preset at a time to fill the thumbnail cache below
(ADR-0230). Like
--schema and --check it opens no window, binds no socket and starts no capture client.
It is documented here rather than hidden outright so that a ritmolux seen in a process list with an
argument nothing explains is not a mystery. Running it by hand is harmless — it renders the still for
one preset of whatever library this launch resolves, writes the cache entry, and says on standard
error either what it wrote or that the preset has not changed since last time. A name no library
holds is refused and nothing is written.
Built from 13c7582 at version 0.158.0. This site tracks main and is not versioned per release.