# argus run

> Talos next · Since next

Load a project or a scene, run it, render a PNG, and optionally keep serving and watching.

A project (its folder or its argus.project.json) runs its startup scene with the title, icon and window its manifest names, from any working directory. Any other file runs as a bare scene with the scene defaults. The launch's settings, and where each came from, are printed first as `argus: application {json}` on stderr.

## Usage

```sh
argus run [OPTIONS] <PROJECT_OR_SCENE>
```

## Arguments

| Argument | Description |
|---|---|
| `<PROJECT_OR_SCENE>` |  |

## Options

| Flag | Value | Default | Allowed values | Description |
|---|---|---|---|---|
| `--frames` | `<FRAMES>` |  |  | Fixed 60 Hz ticks to simulate before the first render. 60 by default, or 0 with `--paused`, where the point is that nothing has run yet |
| `--out` | `<OUT>` | `frame.png` |  | Where to write the rendered frame |
| `--overlays` | `<OVERLAYS>` |  | `colliders`, `triggers`, `navmesh`, `bounds` | Debug overlays drawn over the `--out` frame, comma-separated: `colliders` (every collider, a mesh's static one included: an invisible wall shows), `triggers`, `navmesh`, `bounds`. The query API's capture takes the same as `"overlays"` |
| `--serve` |  |  |  | Keep serving the query API until Ctrl-C. `GET /frame.png` is refreshed every 30 rendered frames, twice a second at 60 Hz; `POST /capture` renders an exact frame on demand and is the path to use when the frame has to be the one you asked for. |
| `--port` | `<PORT>` | `7700` |  |  |
| `--ws-port` | `<WS_PORT>` | `7701` |  | The websocket port for `/stream`. SPEC assigns 7701. `--ws-port 0` binds an ephemeral one; there is no way to turn the socket off, because `/stream` is also mounted on the HTTP port and a client that knows one port always finds it |
| `--watch` |  |  |  | Reload the scene whenever the file changes, and keep running |
| `--scale` | `<SCALE>` | `1` |  | Simulation speed, 0.1 to 10, for the real-time loop that --watch and --serve enter. Scaling steps more often per rendered frame; it never stretches dt, and it does not change what --frames means (N fixed ticks is always N fixed ticks) |
| `--headless` |  |  |  | Run without a GPU: simulate and build draw lists, but never submit a frame |
| `--render-on-request` |  |  |  | Keep a renderer, but draw only when a request asks (`POST /capture`, `GET /frame.png?fresh=1`): the loop never renders a frame of its own and no start-up PNG is written. What a program that steps the simulation and only sometimes looks (an agent's play session) wants: stepping costs what `--headless` costs, and a capture still has a GPU to read |
| `--parent-pipe` |  |  |  | Exit when standard input closes. The process that started this one holds the other end of the pipe, so the engine never outlives it, whichever way it died |
| `--paused` |  |  |  | Start paused, so the simulation runs no tick until something asks it to. The loop keeps answering commands — that is what `QueryBridge::pump` is separate for — so a driver can attach, snapshot, and step. Without it the engine free-runs on wall-clock time between launch and the first `POST /time`, and how many ticks fit in that gap depends on how long the process took to start, which is what makes a clip recorded through the API irreproducible. |
| `--stub-physics` |  |  |  | Run the stub physics backend instead of Rapier |
| `--net-join` | `<URL>` |  |  | Play in a networked session through the relay at this address (`ws://host:port`), as one of its players. Needs `--window`; the scene must be byte-identical on every machine |
| `--net-observe` |  |  |  | With --net-join: join as an observer, with no seat and no input (a DIS gateway, a spectator) |
| `--net-seat` | `<N>` |  |  | With --net-join: take this seat (player number, from 1), for a player rejoining their own after a drop; without it, the lowest free seat |
| `--net-remotes` | `<HOW>` |  |  | With --net-join: how other players are shown, `predicted` (live, corrected when a guess was wrong; the default) or `confirmed` (a few ticks behind, never corrected) |
| `--net-smoothing` | `<MS>` |  |  | With --net-join: blend a correction away over this many milliseconds (default 100; 0 snaps) |
| `--net-snap-above` | `<METRES>` |  |  | With --net-join: snap a correction larger than this many metres instead of blending it (default 2) |
| `--state-stream` | `<ADDR>` |  |  | With --net-join: serve each confirmed tick's moving entities as JSON lines to WebSocket clients of this local address (e.g. 127.0.0.1:47912) |
| `--deterministic` |  |  |  | Run as a networked session does: every peer computes it the same, whatever its core count (the Rust crowd on any thread count). A remote player sitting down does this anyway |
| `--no-audio` |  |  |  | Do not open the audio output: a `--window` run mixes as usual and plays nothing. The same as setting `ARGUS_NO_AUDIO=1`. A run without a window never opens one anyway |
| `--audio` |  |  |  | Let a `--window --serve` run be heard. A window that serves the query API is driven by a program (a playtest, an agent, a benchmark) and is silent without this; a plain `--window` run is heard anyway. `ARGUS_AUDIO=1` asks the same from the environment (what a development export's launcher sets). `--no-audio` and `ARGUS_NO_AUDIO` still win |
| `--no-scripts` |  |  |  | Do not run `script` components |
| `--nav` |  |  |  | Build a navmesh even when the scene declares no navigation (no `meta.nav`, no `navagent`). For tools that route the player on any scene, such as an automated playtester |
| `--window` |  |  |  | Play mode: open a window and run until Esc. Keyboard, mouse and (later) gamepad feed the same input state `POST /input` writes; `--serve` keeps the query API up beside it. Keys: Esc quit, F1 overlays, F5 reload, F9 capture, F10 pause. Needs a build with the `window` feature |
| `--window-size` | `<WINDOW_SIZE>` |  |  | Initial window and internal render size in physical pixels, `WxH`, each side 64 to 8192. Default: the project's `window`, else 1280x720 |
| `--viewport` | `<VIEWPORT>` |  |  | The offscreen surface for a run **without** a window, in physical pixels, `WxH`, each side 64 to 8192. What the UI is laid out at and what `POST /capture` renders at by default; `POST /ui/viewport` changes it while the engine runs. Default: the project's `window` when the manifest declares one, else 640x360. |
| `--aa` | `<AA>` | `fxaa` | `fxaa`, `off` | Anti-aliasing: lightweight FXAA by default; off for comparisons |
| `--stats` |  |  |  | Show FPS and CPU frame diagnostics; F3 toggles while running |
| `--no-hud` |  |  |  | Draw the frames (a `--record` recording, an `--out` screenshot) without the game's UI and the world labels: video and stills of the world for editing. The UI still runs. Not for a window or a served run, whose captures draw the UI their own way |
| `--preview` |  |  |  | Refresh cached /frame.png previews with GPU readback; off by default for clean profiling |
| `--no-vsync` |  |  |  | Present without vsync |
| `--capture-dir` | `<CAPTURE_DIR>` | `.` |  | Where F9 writes `argus-capture-<frame>.png` |
| `--keys` | `<KEYS>` |  |  | Hotkeys pressed for you at rendered frames: `120:F9,180:Esc` (Esc, F1, F5, F9, F10). For a scripted test or capture with no one at the keyboard |
| `--title` | `<TITLE>` |  |  | The window title for this launch. Default: the project's `application.displayName`, else `Talos — <scene file name>`. Never saved |
| `--icon` | `<ICON>` |  |  | A PNG to use as the window icon for this launch. Default: the project's `application.icon`, else the built-in Talos icon. Never saved |
| `--scene` | `<PATH>` |  |  | Run this project-relative scene instead of the project's `startupScene`. Only with a project; never saved |
| `--render` | `<PROFILE>` |  |  | The renderer for this launch, `standard` or `hd`. Default: the project's `render.profile`; for a scene file, that of the project folder it is in, else standard. Never saved |
| `--base-dir` | `<BASE_DIR>` |  |  | Where `script.module` and relative asset paths resolve, instead of the scene file's folder. What a test worker running an editor artifact's frozen copy of a scene needs |
| `--font` | `<FILE>` |  |  | The TrueType font that draws HUD text and `label` components. Without it the engine looks at `$ARGUS_FONT`, then for `fixtures/render/fonts/Inter-Regular.ttf` at or above the scene's folder — which only exists inside the engine checkout. A scene that declares text and finds no font **refuses to load**, rather than running with every label silently dropped, so this flag is what a project kept anywhere else uses. `--headless` draws nothing and needs no font. |
| `--seed` | `<SEED>` |  |  | The world seed, set before the first tick, at most 2^53-1 (the P1 contract's limit, and the largest integer a JSON number keeps exactly). Snapshots report it and `POST /snapshot` restores it |
| `--lod-freeze` | `<LEVEL>` |  |  | Draw every asset that has levels of detail (a cooked scan) at this level, whatever the camera: 0 is full detail. For captures and benches that must not depend on distance |
| `--input-overrides` | `<FILE>` |  |  | Where the player's own key bindings are kept, as JSON laid over the scene's `meta.input`. Read at load and rewritten by every `ctx.input.rebind`, so a remap survives a restart and a relaunch. The file need not exist yet; the first rebind creates it, folder and all. Without the flag the scene plays on its own map and a rebind lasts only as long as the process. The editor's Play passes `<project>/.argus/input.json`. |
| `--payload-exe` | `<EXE>` |  |  | Read the game's files out of this exported executable instead of a folder: what an export's launcher passes, with `--payload-id` (the payload the launcher read) and `--payload-mount` (where its unpack would have put them). An executable whose payload cannot be read ends the run with exit code 75, and the launcher unpacks instead |
| `--payload-id` | `<ID>` |  |  |  |
| `--payload-mount` | `<DIR>` |  |  |  |
| `--licence-exe` | `<EXE>` |  |  | The export's executable, for its licence check after a full unpack (docs/LICENSING.md): what an export's launcher passes when it does not pass `--payload-exe` |
| `--licence-activate` | `<FILE>` |  |  | Take this activation file for the export's install before the run (an air-gapped site's file from whoever manages its licence) |
| `--licence-audit` | `<FILE>` |  |  | Write the export's install audit file here and exit |
| `--save-dir` | `<DIR>` |  |  | The folder `ctx.save` keeps each application's save under, as `<DIR>/<application id>/save.json`. A project run saves without being asked: the default is `$ARGUS_SAVE_DIR`, else the user's data folder (`%LOCALAPPDATA%\Argus\saves`, `$XDG_DATA_HOME/argus/saves`). A bare scene has no application id and keeps its save in memory unless this flag names a folder, in which case it is saved as the application `scene`. |
| `--session-id` | `<SESSION_ID>` |  |  | Run as a managed session with this id (`ses_` + 32 hex; docs/EDITOR.md). Needs `--session-kind`, implies `--serve`, and needs a build with the `managed` feature. A `test` session is a headless worker and refuses `--window`; `human-play` needs it |
| `--session-kind` | `<SESSION_KIND>` |  | `preview`, `human-play`, `test` | `preview`, `human-play` or `test` |
| `--job-id` | `<JOB_ID>` |  |  | The job that owns a test session (`job_` + 32 hex). Required for `test` |
| `--project-id` | `<PROJECT_ID>` |  |  | The project (a lowercase UUID). A project location fills it from the manifest, and refuses a different one |
| `--artifact-id` | `<ARTIFACT_ID>` |  |  | The build artifact this process runs (`art_` + 64 hex) |
| `--capabilities` | `<CAPABILITIES>` |  |  | What the session holds, comma-separated P1 names (default `observe`). An API caller presenting the session's nonce gets these, capped by the kind: never more than `observe,capture,snapshot-read` on a preview or human-play session |
| `--anonymous-capabilities` | `<ANONYMOUS_CAPABILITIES>` |  |  | What a caller presenting no identity may use (default `observe`), never more than the holder |
| `--self-test` | `<SCENARIO>` |  |  | Run a self-test scenario in-process and exit: 0 when it passes, 1 when it fails, 2 when it cannot be read. A file, `<project>/selftest/<NAME>.json`, or the built-in `smoke`. Renders offscreen (never opens a window) unless `--headless`; writes `self-test.json` to the log folder and prints one `argus-self-test {json}` line on stdout. docs/SELF-TEST.md |
| `--record` | `<DIR>` |  |  | Render the scene offline to DIR as `frame_00000.png`, … and exit, printing one JSON line on stdout. Steps the fixed 60 Hz simulation from tick 0 and takes a frame every 1/--fps simulated seconds, however long each frame takes to draw; the size is --viewport. `--frames N` is then the number of frames, not a prewarm. docs/API.md, "Recording frames offline" |
| `--fps` | `<FPS>` | `30` |  | Frames per simulated second for --record, 1 to 240 |
| `--seconds` | `<SECONDS>` |  |  | Simulated seconds: for a plain run, the time simulated before the first render at the fixed 60 Hz step (`--seconds 3` is `--frames 180`; simulated time, not wall time); for --record, the clip's length (frames = round(seconds × fps)); for --record-data, ticks = round(seconds × 60) |
| `--record-format` | `<RECORD_FORMAT>` | `png` | `png`, `raw`, `none` | `png` (default), `raw` (`frame_NNNNN.rgba`: RGBA8, top row first, no header), or `none`: every frame drawn and none read back or written, one frame queued ahead of the GPU (a timing run, with `--xr-report`) |
| `--record-until-event` | `<NAME>` |  |  | Stop --record after the frame on which a script first emitted this event (`ctx.events.emit(name)`). `ctx.session.quit()` always stops it |
| `--supersample` | `<N>` | `1` |  | Draw each --record frame at N times the viewport and filter it down (1 to 4, default 1): smoother edges and finer detail, at N x N the drawing cost |
| `--aspect` | `<W:H>` |  |  | The output frame's shape, `W:H` (`9:16`, `1:1`, `2.39:1`), drawn natively at that shape: no letterbox and no crop afterwards. Keeps the --viewport's longer side (the default viewport's without one) and takes the shorter from the aspect, rounded to even pixels: `--viewport 1920x1080 --aspect 9:16` is 1080x1920. Beside --record or an --out shot. Each term 1 to 8192, and each side of the frame 64 to 8192 (with --supersample, of the drawn surface too) |
| `--shot-fov-fit` | `<FIT>` |  | `vertical`, `horizontal`, `cover` | How the camera's field of view meets the frame's shape: `vertical` (the camera's vertical field of view as authored: today's drawing, and the default), `horizontal` (its horizontal field of view at the 16:9 reference is kept) or `cover` (the larger field of view, so the whole 16:9 view the scene was authored for stays in frame). An orthographic lens follows the same rule with its half-height. docs/API.md, "Aspect-aware shots" |
| `--warmup-frames` | `<N>` |  |  | Draw N frames before --record's frame 0, at the starting tick and not written (default 0, or 120 with --accumulate), so temporal effects, auto exposure and the GI settle first |
| `--accumulate` | `<N>` | `1` |  | Make each --record frame the mean of N draws of its tick (1 to 256, default 1), in linear light: each draw has its own sub-pixel offset and noise, so edges, shadow filtering and noisy effects come out as a still's, at N times the drawing cost. Warms up 120 frames first unless --warmup-frames says otherwise. A still: `--record DIR --frames 1 --accumulate 64` |
| `--shots` | `<FILE>` |  |  | Play a shot timeline from FILE as the camera, in place of the scene's `meta.shots`: the same JSON (`shots`, `loop`, `end`, `doneEvent`), so a camera move kept apart from the scene records the same way. Refused, with the contract's messages, when the scene rejects it |
| `--record-script` | `<SCENARIO>` |  |  | Play a self-test scenario (docs/SELF-TEST.md: presses, action and axis holds, waits, in fixed ticks) while --record takes its frames, so a trailer has a player. A file, or `<project>/selftest/<NAME>.json`. The recording still ends at --seconds/--frames; a step that fails stops the scenario, not the recording, and `script` in the summary says where |
| `--save-replay` | `<FILE>` |  |  | Write every tick's input (plus seed, scene identity and engine version) to FILE as the run plays, in any mode: a window, `--serve`, `--record-script`, a plain `--frames` run. Off, it costs nothing. Play it back with `--replay`. docs/API.md, "Replays" |
| `--hash-every` | `<N>` |  |  | With --save-replay: write the per-system state hashes every N ticks into the file (60, or `ARGUS_DESYNC_EVERY`; 0 writes none), which a playback and `argus replay verify` check |
| `--replay` | `<FILE>` |  |  | Play a `--save-replay` file back into the scene, offline and deterministically. Beside `--record DIR --fps N --viewport WxH` (or `--size`) it re-renders the session at any resolution; beside `--record-data` it writes the numbers. The length is the replay's unless --frames/--seconds say otherwise. Refuses (`replay-scene-mismatch`, `replay-engine-mismatch`) when the scene or the engine is not the recording's |
| `--replay-camera` | `<CAMERA>` |  |  | Draw a `--replay` through another camera: `entity:<id>` (from that entity, looking along its -Z) or `pose:ex,ey,ez,tx,ty,tz[,fovDegrees]`. The simulation is the same either way |
| `--profile-report` | `<FILE>` |  |  | After the run's ticks, write the world's counters to FILE and the flight recorder's last frames to FILE.argusprof: what a profiler replay re-run compares. With `--frames` |
| `--record-data` | `<FILE>` |  |  | Write the simulation's numbers to FILE, `.csv` or `.jsonl`, and exit: a row every --data-every ticks from tick 0 (tick, simulated time, one column per --data-fields field), plus the events. Headless and deterministic: two identical runs write the same bytes. On its own it steps --frames N ticks or --seconds S (and needs no GPU); with --record it follows the frames. docs/API.md, "Recording data" |
| `--data-fields` | `<SELECTORS>` |  |  | What --record-data writes: a comma list of `<entity>:<field>`, `#<tag>:<field>` or `*:<field>`. Fields: position[.i], rotation[.i], scale[.i], worldPosition[.i], body.velocity[.i], body.angularVelocity[.i], body.mass, controller.velocity[.i], controller.grounded, joint.position, joint.velocity, script.state.<path>, data.<name>. Default `*:position` |
| `--data-every` | `<N>` | `1` | `on`, `auto` | A --record-data row every N ticks (tick 0 always). Default 1 --xr-sim[=<PROFILE>] Run the desktop XR simulator: the real XR path with no headset. Both eyes of a headset profile are drawn every frame, at its per-eye size and field of view (the real stereo cost), and the window shows one eye, both, or the game's camera (`--xr-view`). PROFILE is `focus3` (the default; measured), `pico4`, `quest3`, `index` (nominal), `WxH@FOV` (a symmetric FOV in degrees), or a headset file (`.json`, docs/status/xr-sim.md). Mouse and keys move the head: F7 toggles head look, Ctrl+Shift+arrows, PageUp and PageDown move it, Ctrl+Shift+Home recenters, F6 cycles the view --xr[=<MODE>] Enter VR on a headset through the installed OpenXR runtime (docs/status/xr-sim.md, "Real headset"). Without a runtime, or with no headset attached or on, the run exits with the reason; `--xr=auto` runs on the desktop instead, saying why in one line. `--window` adds a desktop window mirroring the eyes (`--xr-view`; it never takes focus). `--frames N` is then the number of headset frames, after which the session ends cleanly; without it the run lasts until the headset's user quits. `ARGUS_XR_LOADER` names an OpenXR loader, `ARGUS_XR_BINDINGS` a controller binding file laid over the built-in one |
| `--xr-view` | `<VIEW>` |  | `left`, `right`, `both`, `spectator`, `left-16x9` | What the window (and a capture) shows under XR: `left` (default), `right`, `both` side by side, or `spectator` (the game's own camera, with its screen UI), or `left-16x9` (a centred 16:9 crop of the left eye, filling a 16:9 screen with no third render) |
| `--xr-scale` | `<S>` |  |  | Scale each eye's image, the framing kept; the timing report then measures the scaled size. With `--xr-sim` (0.1 to 1; default 1, the headset's own size) a cheaper simulation. With `--xr` (0.1 to 2; default 1) it multiplies the runtime's recommended per-eye size, up to the runtime's largest: under 1 a faster frame, over 1 a sharper one. It replaces the project's `render.xr.resolutionScale` for this run |
| `--xr-hz` | `<HZ>` |  |  | The simulator display's refresh rate the timing report counts missed frames against (default the profile's, 90). `--xr-sim` only: a headset (`--xr`) is refused it, as its runtime sets the rate |
| `--xr-report` | `<FILE>` |  |  | Write the XR timing report to FILE (JSON) when the run ends: CPU and GPU frame time p50/p95/p99, frames over the budget, and the GPU time of each pass |

## Example

```sh
argus run <PROJECT_OR_SCENE>
```
