# RayRF documentation

Complete documentation for RayRF Studio v1.1.9, bundled as one file. The always-current version is at https://rayrf.com/docs. This file suits offline reading and loading into agentic tools that drive RayRF headless or through the CLI (headless and CLI use need a Pro license). Cross-links jump within this file; each page also names its canonical URL. Screenshots are referenced by URL with descriptive alt text, so the text stands alone.

## Contents

- Getting started
  - [Install and first simulation](#install-and-first-simulation)
  - [The interface](#the-interface)
- Editor
  - [Stackup and materials](#stackup-and-materials)
  - [Drawing geometry](#drawing-geometry)
  - [Variables, constraints, and generators](#variables-constraints-and-generators)
  - [Importing artwork](#importing-artwork)
  - [Ports and excitation](#ports-and-excitation)
- Simulation
  - [Auto mode and quality](#auto-mode-and-quality)
  - [Advanced mode](#advanced-mode)
  - [Boundary conditions](#boundary-conditions)
  - [Running simulations](#running-simulations)
- Results
  - [S-parameters and Smith chart](#s-parameters-and-smith-chart)
  - [Radiation pattern](#radiation-pattern)
  - [Field viewer](#field-viewer)
- Remote running
  - [Remote running](#remote-running)
  - [Runner setup](#runner-setup)
- CLI and automation
  - [CLI overview](#cli-overview)
  - [CLI reference](#cli-reference)
  - [Scripting and specs](#scripting-and-specs)
- Reference
  - [Settings reference](#settings-reference)
  - [RF calculators](#rf-calculators)
  - [Keyboard shortcuts](#keyboard-shortcuts)
  - [File formats and outputs](#file-formats-and-outputs)
  - [FDTD tips](#fdtd-tips)

---

## Install and first simulation

Section: Getting started. Canonical page: https://rayrf.com/docs/getting-started/install

Download RayRF Studio, sign in, and run the bundled 5.8 GHz patch antenna from the welcome picker to results. The walkthrough video covers the same steps.

Video: Install and first simulation: https://www.youtube.com/watch?v=NTDhKJAA7L8

### Download and install

RayRF Studio runs on Windows and Linux. Sign in at [rayrf.com/dashboard/downloads](https://rayrf.com/dashboard/downloads) and download the build for your platform.

- **Windows.** Run `RayRF-Studio-<version>-Setup.exe`. It installs the app and adds **RayRF Studio** to the Start menu.
- **Linux.** Download the `.AppImage`, mark it executable (`chmod +x`), and run it. It is self-contained, so there is nothing else to install.

The desktop app carries a built-in update checker, so a later launch offers to update itself. Telemetry and updates are covered at the end of this page.

### First launch

On the first launch with no license stored, the sign-in dialog opens over the window. It has two tabs and a viewer-mode escape.

![The RayRF sign-in dialog with the browser and license-key tabs](https://rayrf.com/docs/license-dialog.png)

You can sign in now or skip with **Continue with viewer-only license**. Without a license RayRF runs in viewer mode: you can open and explore projects and look at saved results, but the geometry editor is locked and simulations do not run. The viewer is free. You can sign in later from the account button in the title bar. Activation is covered in the next section.

After the sign-in dialog, the welcome picker opens. It also opens from `File` then `New Project`.

![The welcome picker with a blank project, the patch example, and recent files](https://rayrf.com/docs/welcome-picker.png)

- **Blank Project** starts an empty project with no layers, geometry, or settings.
- **Patch Antenna Example** opens the bundled 5.8 GHz patch antenna.
- **Recent projects** lists your recent `.rfsim` files, with **Open Project** for anything else.

Closing the picker with Escape or the corner button keeps whatever project was open, which at startup is a fresh blank one.

### Activate a license

Activation binds this machine to your account. Two ways, from the sign-in dialog:

- **Sign in through RayRF** opens your browser to approve this machine. Approve it and the dialog signs in on its own.
- **Enter license key** takes a key from your dashboard. Paste it and click **Activate**.

The command line shares the same license state. `rayrf auth --license-key KEY` activates a key headlessly, `rayrf auth --status` shows the current state, and `rayrf auth --deactivate` releases the seat. Whichever you use, the desktop app and the CLI both read the result. See [CLI overview](#cli-overview).

A license enables editing and running simulations. What each tier includes is on the [pricing page](https://rayrf.com/pricing).

### Run the patch antenna example

Pick **Patch Antenna Example** from the welcome picker. It opens a complete design: a 5.8 GHz patch on an FR-4 stackup, with a feed port, ready to run. The `Edit` tab shows the stackup and the 2D canvas.

Open the `Simulate` tab and click **Run Simulation**. The example needs to be saved first, because it opens detached from the bundled file to keep that file untouched. RayRF prompts with **Save Project As**: pick a folder and name the `.rfsim`. The run then starts. For any other project, `Ctrl+S` saves in place.

During the run the button reads **Stop Early** and the title bar shows a `RUNNING` badge with the elapsed time and throughput. The **Energy Ringdown** plot fills the tab: it tracks the residual field energy in the domain against the time step on a log axis, with a dashed line at the energy threshold the run stops at. The run is valid once the energy rings down to that line, covered on [FDTD tips](#fdtd-tips). When it reaches the threshold the plot reads **Simulation Finished**, RayRF extracts the results, and the badge reads `COMPLETE`. See [Running simulations](#running-simulations).

Open the `S-Parameters` tab for the return loss: it plots S11 and a Smith chart from the run, with the resonant dip near 5.8 GHz. See [S-parameters](#s-parameters-and-smith-chart). Open the `Radiation Pattern` tab for the far field as a 3D surface and a 2D polar cut. See [Radiation pattern](#radiation-pattern).

### Telemetry and updates

RayRF sends anonymous usage telemetry: which features and dialogs you use, bucketed project-size classes, hardware specs, and run timing. It never sends your designs, results, frequencies, file names, or any text you type. It is on by default outside the EU and UK and off by default inside them, and your account setting overrides the local one. `Settings` then **Anonymous Usage Telemetry** shows the exact fields, your install ID, and a toggle.

The update checker runs once per launch against the current platform. When a newer release is available, the dialog offers **Update now** (download the installer, verify its checksum, launch it, and quit), **Remind me later**, or **Skip this version**. Later launches show an `Update <version> available` badge in the title bar instead of the dialog. `Settings` then **Check for updates** forces a fresh check.

---

## The interface

Section: Getting started. Canonical page: https://rayrf.com/docs/getting-started/interface

A tour of the RayRF Studio main window: the title bar, the five tabs, the bottom panel, and how projects are saved. Every region named here has its own page for the work that happens inside it.

![The main window on the Edit tab with the bundled patch example](https://rayrf.com/docs/interface-overview.png)

### Title bar

The top strip carries the menus and the session's status readouts.

- **Menus.** `File` (new, open, open recent, welcome panel, save, save as, import PNG, exit), `Edit` (undo, redo), `Window` (Live-mode dock layout, shown only in Live mode), `Help` (about), and `Settings` (usage telemetry, check for updates, project settings).
- **Simulation status.** During a run the right side shows a `RUNNING` badge with the elapsed time and throughput in GCell/s, updated on every telemetry step. It reads `COMPLETE` with the run duration when the run finishes, and is hidden when no run is active.
- **Update indicator.** When a startup check finds a newer release you have not skipped, an `Update <version> available` badge appears at the top. Clicking it opens the update dialog.
- **Account button.** Shows your email and plan badge when signed in, or `Sign in` when not. The plan badge is colored by tier. Its menu manages the account, refreshes plan status, deactivates this machine, and signs out.
- **Project name.** The file label shows the current `.rfsim` file name, or is empty for an unsaved project. The window title carries the same name and a trailing `*` while there are unsaved changes.

### The five tabs

The tab bar runs left to right in a fixed order. Edit is the workspace. The four to its right hold results.

- **Edit** is the geometry editor: the stackup and layers, shapes, ports, and the 2D canvas. See [Stackup and layers](#stackup-and-materials).
- **Simulate** holds the mesh, boundary conditions, and run controls, and shows live progress during a run. See [Auto mode](#auto-mode-and-quality).
- **S-Parameters** plots S11 and the Smith chart from the last run. See [S-parameters](#s-parameters-and-smith-chart).
- **Radiation Pattern** shows the far-field pattern when NF2FF output is enabled. See [Radiation pattern](#radiation-pattern).
- **Field Viewer** shows the 3D fields and surface currents when those exports are enabled. See [Field viewer](#field-viewer).

When a run finishes, a dot prefixes each result tab that received new output, so you can see which views changed without opening them. S-Parameters and Simulate always dot. Radiation Pattern dots only when NF2FF was enabled, Field Viewer only when field or surface-current export was enabled. Opening a dotted tab clears its dot.

### Live mode

The `LIVE` button sits at the right end of the tab bar. Live mode re-runs the simulation continuously as you edit the geometry: it docks the result views into one surface and updates them in lockstep with each change, so S-parameters and fields track the edit. Two consecutive failed runs turn it off. The [Running simulations](#running-simulations) page covers it in full.

### Bottom panel

A two-tab panel sits below the tabs, split from the main view by a draggable divider.

- **Console** is the timestamped run log: solver output, status lines, and app messages.
- **Notices** lists the warnings and errors the current project has raised. Errors block a run until fixed. A warning can be dismissed from its right-click menu, either the one specific warning or all warnings of that type, and dismissals persist with the project. A dismissal is keyed to the value that triggered it, so the same warning re-surfaces when that value changes materially.

A seat-decision banner appears above the tabs when your plan allows fewer machines than are activated. Its button opens the account page to choose which machines to keep.

### Undo and redo

Undo and redo are per tab. The Edit tab keeps up to 50 geometry states. `Ctrl+Z` and `Ctrl+Y` apply to whichever tab has focus, and each result tab manages its own history for the edits made in it.

### Project files

A project is one `.rfsim` JSON file that holds the whole design: stackup, geometry, ports, settings, view state, and the last run's results. There is no autosave. Edits set the dirty marker, the `*` in the title, and you save explicitly with `Ctrl+S`. A project can be configured to write its results to a separate `.rfsimout` sidecar next to the `.rfsim`, so the project can be committed to version control while the results stay out of it. See [File formats](#file-formats-and-outputs).

### The bundled example

The welcome picker that opens on launch offers a bundled patch-antenna example alongside a blank project and your recent files. It is the fastest way to open a complete design and see every tab populated.

---

## Stackup and materials

Section: Editor. Canonical page: https://rayrf.com/docs/editor/stackup-and-layers

The stackup is the ordered set of layers that gives your board its vertical structure and its material properties. This page covers the layer model, the dielectric and conductor properties, and where they are edited. Drawing copper and placing ports is [Geometry and drawing](#drawing-geometry).

### The layer model

A layer is one of three types:

- **Conductor** carries the copper you draw on it. It is a zero-thickness sheet at a single Z, not a solid slab. The drawn shapes on that layer become the metal.
- **Dielectric** is a substrate slab with a thickness, a relative permittivity, and a loss tangent. It fills the gap between the conductors above and below it.
- **Air** is a free-space spacer. It has a thickness so it sets a gap, but no permittivity or loss. It emits nothing to the mesh, so the gap simulates as air.

Layers are ordered top to bottom in the Stackup list, topmost first. Each layer's Z is derived, never typed: the bottom layer sits at z = 0 and every layer above it stacks up by the dielectric and air thicknesses below it. A conductor's own thickness does not move any Z, only dielectric and air thicknesses do. Because Z is computed, the `z` field in the properties panel is read only. Change a dielectric thickness and every layer above it shifts.

A new blank project starts with no layers. Build the stackup by adding layers, or open the bundled example to start from a working one.

![The Stackup section with a dielectric layer selected, showing the layer list and the New Layer and Delete Selected buttons](https://rayrf.com/docs/stackup-panel.png)

### Adding, removing, and ordering layers

The Stackup section holds the layer list and two buttons:

- **New Layer** opens a dialog. Pick the type (Conductor, Dielectric, or Air), give it a unique name, and for a dielectric enter the thickness and relative permittivity. The new layer lands at the top of the stack.
- **Delete Selected** removes the selected layer.

Drag a row in the list to reorder it. Right-click a row for Properties, Rename, Copy Layer Above, Copy Layer Below, and Delete. Double-click a row to open its properties.

The **active layer** is the one new geometry is drawn on. Click a row to make it active, or press a digit key `1` to `9` on the Edit tab to select that row by position.

The **Viewport** view-mode selector sets which layers are drawn on the canvas. **All Layers** shows everything, **Focused** highlights the active layer and dims the rest, and **Active Only** shows only the active layer's geometry. `Shift+S` cycles the three modes.

### Dielectric properties

Selecting a layer with nothing else selected shows the Layer Properties panel: the name, the derived `z`, and the material fields for that layer type. The right-click Properties... dialog holds the same fields and works while the panel is hidden by another selection.

A dielectric layer carries a thickness in mm, a relative permittivity `epsilon_r` (at least 1), and a loss tangent (0 for lossless). Higher permittivity slows propagation and shortens the guided wavelength, so it sets the electrical length of traces and the resonant frequencies.

A preset fills these values:

| Preset | epsilon_r | Loss tangent |
| --- | --- | --- |
| Ideal FR4 | 4.4 | 0.0 |
| Real FR4 | 4.3 | 0.02 |
| Custom | (your values) | (your values) |

Editing any value switches the preset to Custom.

### Conductors

A conductor layer carries the copper you draw on it as a zero-thickness sheet at that layer's Z. At export the drawn shapes rasterize onto that plane as a perfect electric conductor: a lossless metal with no thickness and no per-layer material fields to set. A new conductor layer needs only a name.

### Paint every dielectric, or it simulates as air

A dielectric layer with no geometry painted on it is treated as air and its permittivity is ignored. `validate` and running a simulation both warn on this, since running a layer as air is sometimes intended. If the layer is a real substrate, paint a board-sized rectangle across it so it carries its permittivity. If it is left over from an earlier stackup, delete it. A layer at `epsilon_r` = 1 is not flagged, since painting it would change nothing.

### Layer limit on Hobby

The Hobby plan allows a stackup of up to 3 layers total, counting conductors and dielectrics together. Pro and Founding plans have no layer limit.

---

## Drawing geometry

Section: Editor. Canonical page: https://rayrf.com/docs/editor/geometry

The Edit tab is a 2D drafting canvas: draw copper and dielectric shapes on the active layer, then place vias and ports through the stack. This page covers the coordinate model, the draw tools, and every shape primitive.

### Coordinate model

The scene is in scene millimetres with Y up, so a shape moved toward the top of the canvas gains Y. Every center-based shape stores `x_mm` and `y_mm` as the center of the shape, not a corner, and rotation is clockwise-positive degrees. The ruler and the property spins read and write these same scene millimetres.

Snap and the grid step sit on the toolbar. With Snap on (the default), a clicked point rounds to the nearest grid multiple in both X and Y. The grid step defaults to 1 mm. Typing directly in a shape's property fields bypasses the grid, so snap constrains the pointer, not the stored value. `M` starts the ruler: a two-click transient measurement that draws a line with a distance readout in mm and places nothing permanent.

### Draw tools

![The Draw Tools panel on the Edit tab](https://rayrf.com/docs/draw-toolbar.png)

Each tool arms a single drawing mode. Its shortcut fires only on the Edit tab and only when a text field does not have focus.

| Tool | Key | Draws |
| --- | --- | --- |
| Select | `Esc` | Pick, move, and edit existing shapes |
| Rectangle | `R` | Drag a box, or click for a grid-sized default |
| Circle | `C` | Drag out a radius from the center |
| Arc |  | A curved-trace band (unkeyed) |
| Polygon | `P` | Click vertices, close to finish |
| Via | `F` | Drop a plated via through two conductor layers |
| Point Port | `Q` | Single-cell vertical feed |
| More Ports |  | Menu: Area, Planar Line, Planar Bar port |
| Ruler | `M` | Measure a distance, transient |
| Import PNG |  | Rasterize a PNG image as copper |
| Script Shape |  | Author a Python-defined shape |

`Esc` cancels the active tool and returns to Select. Import PNG and Script Shape open a dialog rather than arming a canvas mode. The area, line, and bar ports live under More Ports, with the rectangular Area port also on `A`.

Selecting a shape shows its handles: a rectangle gets corner and edge-midpoint handles that resize it, a circle gets outer and inner rim handles for its diameters, an arc gets rim and angle handles, and a polygon gets one handle per vertex. Drag the body to move. `Space` rotates the selection 90 degrees clockwise about its centroid, `X` mirrors it horizontally, and `Y` mirrors it vertically. `Ctrl+C`, `Ctrl+X`, and `Ctrl+V` copy, cut, and paste the selection, `Delete` removes it. Full key list on [Keyboard shortcuts](#keyboard-shortcuts).

### Shape primitives

![The shape primitives on the canvas](https://rayrf.com/docs/geometry-primitives.png)

The primitive kinds and their parameters, straight from the geometry registry:

#### `rect`

Axis-aligned rectangle (center-based) with optional rotation.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | X of the rectangle center. |
| `y_mm` | number | mm | `0` | yes | Y of the rectangle center. |
| `w_mm` | number | mm | `none` | yes | Full width (local X extent). |
| `h_mm` | number | mm | `none` | yes | Full height (local Y extent). |
| `rotation` | number | deg | `0` |  | Rotation, clockwise-positive. |

Anchors: `center`, `corner_ne`, `corner_nw`, `corner_sw`, `corner_se`, `edge_n`, `edge_s`, `edge_e`, `edge_w`

#### `circle`

Filled disc, or an annular ring when inner_d_mm > 0.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | X of the circle center. |
| `y_mm` | number | mm | `0` | yes | Y of the circle center. |
| `d_mm` | number | mm | `none` | yes | Outer diameter. |
| `inner_d_mm` | number | mm | `0` |  | Inner diameter; 0 is a solid disc, >0 is a ring. |

Anchors: `center`, `quad_0`, `quad_90`, `quad_180`, `quad_270`

#### `arc`

Curved trace as an annular band sector (centerline radius plus width).

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | X of the arc center. |
| `y_mm` | number | mm | `0` | yes | Y of the arc center. |
| `r_mm` | number | mm | `none` | yes | Centerline radius. |
| `width_mm` | number | mm | `none` | yes | Band thickness (radial width). |
| `a0_deg` | number | deg | `0` |  | Start angle, counter-clockwise from +X. |
| `a1_deg` | number | deg | `none` | yes | End angle, counter-clockwise from +X. |

Anchors: `center`, `start`, `end`, `mid`, `start_outer`, `start_inner`, `end_outer`, `end_inner`

#### `poly`

Polygon whose vertices are local offsets from (x_mm, y_mm); rotation is baked into the points.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | X offset of the polygon frame. |
| `y_mm` | number | mm | `0` | yes | Y offset of the polygon frame. |
| `points` | array |  | `[]` |  | Vertices as [[lx, ly], ...] local offsets. |

Anchors: `center`, `vertex_<i>`, `edgemid_<i>`

#### `via`

Plated via: annular conductor pad plus a drilled hole spanning two conductor layers.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | X of the via center. |
| `y_mm` | number | mm | `0` | yes | Y of the via center. |
| `d_mm` | number | mm | `none` | yes | Pad outer diameter. |
| `drill_mm` | number | mm | `0.3` |  | Drill-hole diameter. |
| `annular_mm` | number | mm | `0.6` |  | Annular-ring diameter. |
| `start_layer` | string |  | `` |  | Top conductor layer name. |
| `end_layer` | string |  | `` |  | Bottom conductor layer name. |
| `plated_over` | bool |  | `false` |  | Plated-over-filled cap. False is drilled-through. |
| `solid` | bool |  | `false` |  | Rasterize the via as a solid PEC column, no drill or antipad. Removes the air core and clearance, which raises the via capacitance to nearby planes. For cross-checks against solid-cylinder via models. |

Anchors: `center`

#### `port`

Excitation or measurement port. Set params['port_type'] to point, rect, line, or bar; see PORT_TYPES.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `port_type` | string |  | `point` |  | Port subtype: point, rect, line, or bar. |
| `port_number` | int |  | `1` |  | Port index used to name the S-matrix column. |
| `impedance_ohm` | number | Ohm | `50` |  | Reference impedance. |
| `active` | bool |  | `false` |  | True drives the port; False makes it a passive load/measurement. |

Anchors: `center`

#### `bitmap`

Embedded raster image rasterized as a conductor pattern.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | X of the image center. |
| `y_mm` | number | mm | `0` | yes | Y of the image center. |

Anchors: `center`

#### `dxf`

Imported DXF outline rasterized as a conductor pattern.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | X of the import center. |
| `y_mm` | number | mm | `0` | yes | Y of the import center. |

Anchors: `center`

#### `script`

Python-defined shape: the source draws geometry and declares named point anchors.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | X origin offset for the script frame. |
| `y_mm` | number | mm | `0` | yes | Y origin offset for the script frame. |
| `source` | string |  | `` |  | Python source using the shape DSL (path, point, ...). |

Anchors: `center`, `point:<name>`

A few points the table does not state:

- A `circle` with `inner_d_mm` greater than zero is a ring, not a disc. Use it for annular pads and washers.
- An `arc` is a curved trace: a band of `width_mm` swept along a centerline of radius `r_mm` between two angles. Angles are counter-clockwise from +X, which is the drafting convention and independent of the clockwise-positive rotation used by the box-based shapes.
- A `poly` bakes its rotation into the point list, so it has no `rotation` parameter. Its vertices are local offsets from the frame origin.
- A `bitmap` carries an imported raster rasterized as a conductor pattern. See [Importing artwork](#importing-artwork).
- A `script` is drawn by Python source using the shape DSL and can declare named point anchors. Author it in the Script Shape dialog below.

#### Vias

![The via type dialog](https://rayrf.com/docs/via-dialog.png)

A via is a plated barrel spanning from `start_layer` to `end_layer`, two conductor layers in the stack. The via dialog sets the drill and annular-ring diameters and picks the two layers. Two flags change how the barrel rasterizes. Plated-over (POFV) caps the drill with copper on the outer faces while the hole stays air through the dielectric, for filled HDI microvias. Solid rasterizes the via as a plain PEC column with no drilled core and no clearance antipad, which raises the via capacitance to nearby planes and is meant for cross-checks against solid-cylinder via models. Solid wins over the cap type. The annular ring must be at least the drill diameter.

#### Script shapes

![The script shape dialog](https://rayrf.com/docs/script-shape-dialog.png)

A script shape is a small Python program that draws a 2D shape, for the geometry a rectangle or polygon cannot express: a spiral, a taper, a mathematically swept curve. The Script Shape dialog is a code editor over the drawing calls with a live preview and a status line that shows the current lint or runtime error and its line number. A new shape opens on a spiral template that runs as is.

The program draws in a local millimetre frame whose origin is the shape's `x_mm`/`y_mm`, so you author around (0, 0) and move the shape on the canvas afterward. Four calls are available:

| Call | Draws |
| --- | --- |
| `path(points, width=0.2)` | a stroked open polyline, width in mm |
| `polygon(points)` | a filled polygon, three or more points |
| `disc(cx, cy, r)` | a filled circle |
| `point(name, x, y)` | a named anchor, not geometry |

`points` is a list of `(x, y)` pairs in local mm. A `point` call declares a named anchor that appears on the shape for constraints and dimensions to attach to, the same way a rectangle exposes its corners.

The program runs with a math environment and ordinary Python control flow, and no imports. Available names include `pi`, `e`, `tau`, the trig and `sqrt`/`exp`/`log` family, `hypot`, `atan2`, `floor`, `ceil`, `degrees`, `radians`, and `linspace`, plus the plain builtins `range`, `len`, `min`, `max`, `sum`, `enumerate`, `zip`, and the list, dict, and set constructors. Trig here works in RADIANS, unlike the expression fields elsewhere in the editor, which use degrees: write `cos(2 * pi * t)`, not `cos(360 * t)`.

A bare name that matches a Variable Studio variable is injected as its current value, so a script shape is parametric: reference `turns` or `pad_w` in the source and the shape redraws when that variable changes. Assigning a name inside the script uses the local value instead. The starter template sets its inputs as local assignments and notes which line to delete to drive one from a variable.

Local-mm bounds clip the drawn geometry, and Auto-fit from geometry sets the bounds to the current extent. The output is capped at tens of thousands of points, and a lint blocks imports and attribute escapes before the code runs. A script that raises reports the error and its line in the status bar rather than writing a partial shape into the project.

### Anchors

Every shape exposes named anchors derived from its parameters: a rectangle's corners and edge midpoints, a circle's quadrant points, an arc's ends and rim points, a script shape's declared points. Anchors are computed from the shape, never stored, so they move with it. Constraints and dimensions attach to anchors to lock geometry together. See [Parametric constraints](#variables-constraints-and-generators).

### Layer targeting

A new shape lands on the active layer, the row selected in the stack panel. Digits `1` through `9` select a layer by position, and drawing with no layer selected is refused with a prompt to add one. Vias are the exception: they name their own two layers and span the stack.

The active layer's type decides what a shape paints. On a conductor layer, a shape is copper, and vias carve their holes and antipads out of it. On a dielectric layer, the same shape paints that dielectric region using the same renderer, so a dielectric accepts every shape kind. A dielectric layer left unpainted renders as air and is flagged before a run rather than filled with a guessed slab. Build and order the layers first on [Stackup and layers](#stackup-and-materials).

---

## Variables, constraints, and generators

Section: Editor. Canonical page: https://rayrf.com/docs/editor/parametric

The parametric system drives geometry by relationships instead of fixed numbers: named variables, a constraint solver on the canvas, and generators that stamp copies of a master shape. One change propagates everywhere it is referenced.

### Variables

Variable Studio holds the project's named variables. Each row is a name, a value, and an optional min and max.

![Variable Studio panel](https://rayrf.com/docs/variable-studio.png)

A value is a literal number or an expression over other variables. The `Value` cell tooltip shows the resolved number, and the `Name` cell tooltip shows how many fields use the variable and its group. A name is letters, digits, and underscore, does not start with a digit, and is case sensitive. `pi`, `e`, and the function names are reserved. `Min` and `Max` are an optional allowed range for the variable, checked only for min above max once both are filled. Set a variable's group from the row context menu.

Any numeric field of a shape can bind to an expression. A bound field reads its value from the expression each time the project resolves rather than storing a fixed number. Categorical fields (layer names, port subtype, feed direction) never bind. A binding addresses one field: a shape param, a layer property, a domain or FDTD setting, or a generator param.

The expression grammar: numbers, variable names, `+ - * / // % **` with unary sign and parentheses, the functions `sin cos tan asin acos atan atan2 sqrt abs min max floor ceil round exp log log10`, and the constants `pi` and `e`. Trig is in degrees, the CAD convention, so `sin`, `cos`, and `tan` take degrees and the inverse functions return degrees. An expression evaluates in the field's own unit with no unit suffix. A dependency cycle, an undefined name, or a result of infinity or NaN is reported with the offending token named, never applied.

Set values three ways: edit the cell in Variable Studio, or headless with `rayrf project set --var name=value --project p.rfsim`, or per run with `rayrf run --set name=value`. Both CLI paths re-resolve the whole parametric system (variables, bindings, then constraints) before the project is used. `--set` applies at run start.

### Constraints

The constraint solver holds geometric relationships as you draw and drag. Add a relation and the solver drives it to satisfaction in a least-squares sense, keeping every enabled constraint true. Dimensions and relation glyphs draw on the canvas next to the shapes they bind.

Each constraint type:

| Constraint | Meaning |
| --- | --- |
| `coincident` | Anchor onto an anchor, an edge (point-on-line), or a circle rim. |
| `point_on_line` | An anchor lies on an edge segment. |
| `point_on_circle` | An anchor lies on a circle or arc rim. |
| `horizontal` | Two anchors share a Y coordinate. |
| `vertical` | Two anchors share an X coordinate. |
| `distance` | Distance between two anchors or an edge length (aligned, horizontal, or vertical); value is a number or expression. |
| `angle` | Angle between two edges, in degrees. |
| `radius` | Radius of a circle, arc, or via ring. |
| `diameter` | Diameter of a circle, arc, or via ring. |
| `equal` | Two refs measure the same: edge lengths, circle/arc radii, or anchor-pair distances (four-point form). |
| `tangent` | A rect/poly edge tangent to a circle/arc, or circle tangent to circle. |
| `fix` | Pin an anchor or a whole shape in place. |

A dimensional constraint (`distance`, `angle`, `radius`, `diameter`) carries a target value that is a number or an expression over variables, so a dimension can be driven by a variable. A redundant but consistent dimension is downgraded to a driven reference dimension: it reports its measured value and is excluded from the solve. An `equal` constraint ties two edge lengths or two circle radii together.

Arm a tool from the keyboard, then click the anchors, edges, or rims it relates. Each key toggles its tool, and it stays armed until Escape, another tool, or Select disarms it.

| Key | Tool |
| --- | --- |
| `D` | Dimension |
| `I` | Coincident |
| `H` | Horizontal |
| `V` | Vertical |
| `T` | Tangent |
| `N` | Angle |
| `K` | Fix |
| `E` | Equal |

Dragging is constrained. When you move a shape that carries constraints, the solver runs on release with the dragged shape pinned toward where you dropped it and its constrained partners moved the least amount that keeps the hard constraints satisfied. An infeasible drop lands at the closest feasible pose instead of reverting the whole move. A shape held by `fix` does not move.

### Generators

A generator stamps copies of one or more master shapes. Select the masters, then create an array from the Generators panel.

![Generators panel](https://rayrf.com/docs/generators-panel.png)

The stored state is the master shapes plus the generator parameters. Copies (instances) are never written into the saved shape list. They materialize at render time so the canvas shows them, and again at export time for the solver, always from the current masters. Editing a master updates its copies live: a width change on the source resizes every copy at once, and dragging the source moves the copies with it, holding their relative offsets.

The two generator types:

| Generator | Params | Meaning |
| --- | --- | --- |
| `array` | `nx`, `ny`, `dx_mm`, `dy_mm` | Rectangular grid of copies of one or more master shapes. |
| `linked_clone` | `dx_mm`, `dy_mm` | A single offset copy of a master shape (the degenerate array case). |

At most 4096 instances per generator. A larger count is clamped and `validate` warns.

`nx`, `ny`, `dx_mm`, and `dy_mm` each accept a number or an expression, so a variable can drive the copy count or the pitch.

![EBG array on the canvas](https://rayrf.com/docs/ebg-canvas.png)

The generator has an instance cap. While you edit a grid interactively the array holds at the cap with a warning, but the pre-run resolve refuses an over-cap grid rather than pass a trimmed array to the solver as different geometry than you configured, and `validate` reports the requested count against the cap.

---

## Importing artwork

Section: Editor. Canonical page: https://rayrf.com/docs/editor/import-png

Trace a copper layout from a PNG image. The import dialog calibrates the image scale, masks one color as conductor, and places the result on a layer as a bitmap primitive that rasterizes into the mesh like any other shape.

### Opening the import dialog

Two entry points open the same dialog:

- File > Import > Import PNG... in the menu bar.
- The Import PNG tool in the editor toolbar.

Both prompt for a `.png` file, then open the scale-and-mask dialog on that image. An image whose decoded RGBA buffer is at least 256 MB first shows a size warning with the estimated peak RAM and added project-file cost, and lets you cancel before loading.

![Import PNG dialog: ruler, mask preview, and the scale and layer steps](https://rayrf.com/docs/import-png-dialog.png)

### The steps

The image fills the canvas on the left. Wheel zooms, right-drag pans, and **Fit View** frames the whole image. The side panel walks the steps in order. **OK** stays disabled until both a mask color and a ruler length are set.

- **Crop.** Drag the yellow handles on the image to trim the region. Pixels outside the yellow rectangle are ignored. The default rectangle is the whole image.
- **Pick mask color.** Toggle **Pick Mask Color**, then click a pixel. That pixel's RGB becomes the mask color. Only pixels matching it are kept as conductor.
- **Adjust tolerance.** A per-channel RGB match window, 0 to 255. 0 matches the mask color exactly, higher values include more nearby colors. The overlay updates live: matched pixels fill purple, unmatched pixels drop out.
- **Enter real-world ruler length (mm).** Drag the two ruler endpoints across a feature whose true length you know, then type that length in mm. The dialog divides it by the measured pixel span to get mm-per-pixel. **Snap to pixel edges** locks the endpoints to pixel boundaries.
- **Choose target layer.** The layer the bitmap is placed on, defaulting to the active layer. A bitmap is a conductor pattern, so target the conductor layer the artwork belongs to.

The **Pixels** readout shows the current ruler span. The dialog warns if the mask selects zero pixels or nearly the whole image, both signs the color or tolerance is off.

> Scale accuracy: measure the longest feature you can. A 20 px ruler carries up to about 2.5% dimensional error from one-pixel endpoint uncertainty. A 500 px ruler drops that to about 0.1%. Start from the highest-resolution image you have.

### What mask color to pick

The mask is a single color plus a tolerance, so the image should be close to monochrome artwork: one flat color for copper against a contrasting background. The picked color is the copper, whether it is the dark or the light region. A wider tolerance keeps colors farther from the picked one, which takes in the pixel-to-pixel variation of a noisy or JPEG-compressed image.

### Where the bitmap is placed

On **OK**, the masked pixels become a `bitmap` primitive on the chosen layer, recolored to that layer's editor color. The image is embedded in the project as pixel data, so it saves inside the `.rfsim` and needs no sidecar file. The bitmap is placed past the right edge of the existing geometry (or at the origin on an empty canvas), snapped to the grid, and selected. Its mm-per-pixel scale sets its physical size. Move, rotate, or flip it afterward like any other primitive.

At simulation export the bitmap rasterizes onto its layer's plane: every kept pixel becomes conductor at the layer's Z, the same conductor mask a drawn polygon would produce. A bitmap that has lost its pixels is a hard export error naming the primitive, never a silently blank layer.

### Reference-trace overlays

Importing a measured sweep to plot against a simulated result is a separate flow. Touchstone and CSV traces load on the results view, not here. See [S-parameters](#s-parameters-and-smith-chart).

---

## Ports and excitation

Section: Editor. Canonical page: https://rayrf.com/docs/editor/ports-and-excitation

A port is where the simulation feeds energy into the structure and measures the response. This page covers the four port types, the options every port carries, the excitation waveform, and what `validate` checks before a run.

### What a port is

A port is a shape of kind `port`. Each port is both an excitation source and a measurement site. Its `active` flag decides which: an active port is driven, a passive port is a matched load that the run still measures.

A run produces one S-matrix column per driven port: driving port `n` yields S1n, S2n, and so on across every measured port. The two surfaces gate this differently. The desktop app requires exactly one active port per run and refuses to start with several, so a full matrix is built one driven port at a time. The CLI accepts several active ports in one `rayrf run` and emits a column for each. If no port is active the CLI warns and computes the port-1 column only.

![The four port types placed on traces in the Edit tab](https://rayrf.com/docs/ports-canvas.png)

### Port types

Four port types cover vertical and in-plane feeds, straight from the geometry registry:

#### `point`

Single-cell vertical probe between two conductor layers.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | X of the feed point. |
| `y_mm` | number | mm | `0` | yes | Y of the feed point. |
| `port_number` | int |  | `1` | yes | Port index. |
| `impedance_ohm` | number | Ohm | `50` |  | Reference impedance. |
| `active` | bool |  | `false` |  | Drive (True) or load (False). |
| `top_layer` | string |  | `` | yes | Positive-terminal conductor layer. |
| `bottom_layer` | string |  | `` | yes | Negative-terminal conductor layer. |
| `deembed_mm` | number | mm | `0` |  | Reference-plane shift distance. |
| `deembed_dir` | string |  | `` |  | Shift direction: one of x+, x-, y+, y-. |

#### `rect`

Distributed rectangular (area) feed between two conductor layers.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | X of the footprint center. |
| `y_mm` | number | mm | `0` | yes | Y of the footprint center. |
| `w_mm` | number | mm | `none` | yes | Footprint width. |
| `h_mm` | number | mm | `none` | yes | Footprint height. |
| `port_type` | string |  | `rect` | yes | Must be 'rect'. |
| `port_number` | int |  | `1` | yes | Port index. |
| `impedance_ohm` | number | Ohm | `50` |  | Reference impedance. |
| `active` | bool |  | `false` |  | Drive (True) or load (False). |
| `direction` | string |  | `z` |  | Feed axis: z (transverse), x, or y. |
| `top_layer` | string |  | `` | yes | Positive-terminal conductor layer. |
| `bottom_layer` | string |  | `` | yes | Negative-terminal conductor layer. |
| `deembed_mm` | number | mm | `0` |  | Reference-plane shift distance. |
| `deembed_dir` | string |  | `` |  | Shift direction: one of x+, x-, y+, y-. |

#### `line`

In-plane line feed across a gap on a single conductor layer. The feed injects along the dominant X or Y component of p0 to p1; the drawn footprint is kept, and validate warns when the director is off-axis.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `p0_x_mm` | number | mm | `0` | yes | Endpoint 0 X (negative terminal). |
| `p0_y_mm` | number | mm | `0` | yes | Endpoint 0 Y (negative terminal). |
| `p1_x_mm` | number | mm | `none` | yes | Endpoint 1 X (positive terminal). |
| `p1_y_mm` | number | mm | `none` | yes | Endpoint 1 Y (positive terminal). |
| `width_mm` | number | mm | `0.1` |  | Strip width perpendicular to the line. |
| `port_type` | string |  | `line` | yes | Must be 'line'. |
| `port_number` | int |  | `1` | yes | Port index. |
| `impedance_ohm` | number | Ohm | `50` |  | Reference impedance. |
| `active` | bool |  | `false` |  | Drive (True) or load (False). |
| `bottom_layer` | string |  | `` | yes | Conductor layer the feed sits on. |
| `top_layer` | string |  | `` |  | Same layer as bottom_layer for a planar feed. |
| `excitation_mode` | string |  | `planar` |  | Must be 'planar'. |

#### `bar`

In-plane bar feed on a single conductor layer. The feed injects along the dominant X or Y component of its director.

| Param | Type | Units | Default | Required | Meaning |
| --- | --- | --- | --- | --- | --- |
| `x_mm` | number | mm | `0` | yes | Bar center X. |
| `y_mm` | number | mm | `0` | yes | Bar center Y. |
| `length_mm` | number | mm | `1` | yes | Gap length along the feed director. |
| `width_mm` | number | mm | `4` | yes | Transverse bar width. |
| `angle_deg` | int | deg | `0` |  | Director angle from +X. The feed injects along the dominant axis, so use 0/90/180/270 to match the drawn bar exactly. |
| `port_type` | string |  | `bar` | yes | Must be 'bar'. |
| `port_number` | int |  | `1` | yes | Port index. |
| `impedance_ohm` | number | Ohm | `50` |  | Reference impedance. |
| `active` | bool |  | `false` |  | Drive (True) or load (False). |
| `bottom_layer` | string |  | `` | yes | Conductor layer the feed sits on. |
| `top_layer` | string |  | `` |  | Same layer as bottom_layer for a planar feed. |
| `excitation_mode` | string |  | `planar` |  | Must be 'planar'. |

Placement the table does not carry:

- A `point` port is a single-cell vertical probe. It drives the gap between a `top_layer` and a `bottom_layer`, so the two must be different conductor layers. Reach for it as the default lumped feed on a narrow trace.
- A `rect` port spreads the same vertical feed across a footprint you drag out. Use it when a single cell is too coarse for the conductor, such as a wide trace or a patch edge. Its `direction` sets the feed axis: `z` feeds vertically between `bottom_layer` and `top_layer`, `x` or `y` feeds in-plane along that axis.
- A `line` port is an in-plane feed across a gap on one layer, set by two endpoints: p0 is the negative terminal, p1 the positive, and current runs p0 to p1. Use it for a coplanar or edge feed where both terminals sit on the same layer. The endpoints come from the two-click placement, not a dialog, and snap to the nearest axis.
- A `bar` port is a fixed in-plane bar on one layer, placed with a single click. Use it when you want a set-orientation in-plane gap rather than two picked endpoints. The bar is axis-aligned: its director angle is a multiple of 90 degrees, and the property panel rejects any other value.

The point and line dialogs set the electrical options and the layers:

![The point port placement dialog](https://rayrf.com/docs/port-dialog-point.png)
![The in-plane line port placement dialog](https://rayrf.com/docs/port-dialog-line.png)

### Common options

Every port carries the same electrical options.

- `port_number` names the S-matrix column, so keep it unique across the project. Two ports sharing a number leave the S-matrix mapping undefined, which `validate` warns about. A new port takes the lowest unused number.
- `impedance_ohm` is the reference impedance the S-parameters normalize to, 50 by default. It must be positive: the wave math divides by its square root.
- De-embedding shifts the measurement plane off the feed. Set `deembed_mm` to the distance and `deembed_dir` to the direction (`x+`, `x-`, `y+`, or `y-`) pointing from the feed toward the device. A shift of 0 measures right at the feed. Use it to subtract a feed line so the reported impedance and S-parameters refer to the device itself. The distance cannot be negative, as the direction control sets the sign.

Ports serialize into the solver input as lumped feeds: a point port becomes a `lumped_point`, the others a `lumped_rect`, with the impedance carried as `R_ohm`. An in-plane line or bar emits along its dominant axis, since the backend feeds only along X, Y, or Z.

### Excitation

The source is a Gaussian pulse spanning the frequency band, from `freq_min_hz` to `freq_max_hz`. One broadband pulse excites the whole band in a single run, and the S-parameters come from the ratio of the transformed port voltages and currents.

Continuous-wave mode replaces the pulse with a steady sinusoid at one frequency. It never rings down and produces no meaningful S-parameters, so it is for field animations only. CW is an advanced-mode feature, and an auto-mode project with CW on is refused rather than run as a pulse. See [FDTD tips](#fdtd-tips).

### What validate catches

Run [`validate`](#cli-reference) before a run. For ports it reports:

- A zero-height span, as an error. A point or rect port whose top and bottom conductors land on the same mesh layer collapses to no vertical extent and injects nothing. Setting `top_layer` equal to `bottom_layer` is the same mistake and is flagged as a warning.
- No active port, as a warning. With nothing driven there is no excitation and no S-parameters, and a project with no ports at all warns the same way.
- A port that maps to zero cells or lies outside the domain, as an error, and a non-positive impedance or a negative de-embed distance, as an error.
- A duplicate `port_number` or an unrecognized `deembed_dir`, as a warning.
- An off-axis in-plane feed, as a warning. The editor keeps placements on-axis, but a spec-built line or bar can carry any director. The exporter then drives the dominant X or Y component while the footprint stays as drawn, so an unintended off-axis feed injects along one axis only.

---

## Auto mode and quality

Section: Simulation. Canonical page: https://rayrf.com/docs/simulation/auto-mode

Auto mode sizes an entire FDTD run from one quality value and the geometry: the mesh, the CFS-CPML absorber, the air margins, the ring-down criterion, and the output-capture bins. You set the frequency band and a quality level, and the deriver reads the stackup and the copper to produce every solver parameter with a traceable reason for each one.

### Auto versus advanced

Auto mode derives dx, dy, dz, PML depth, per-face air margins, ring-down, and the convergence targets from a single quality slider plus the measured geometry, and never coarsens the mesh to make a run fit: if it will not fit, the run gate refuses it with a reason. [Advanced mode](#advanced-mode) sets the same parameters by hand. The mode toggle lives at the top of the Simulate tab.

### Frequency band

The **Frequency Range** card takes the band two ways. **Centre + BW** enters a centre frequency and a full bandwidth, the default. **F min / F max** enters the two band edges directly. The button pair switches between them, and both write the same stored band, so a value typed one way updates the readout in the other.

![Frequency range card, centre plus bandwidth form](https://rayrf.com/docs/frequency-panel.png)

The band is stored in Hz and shown in GHz, and the CLI flags take MHz, covered on [FDTD tips](#fdtd-tips). A non-positive or inverted band is refused downstream rather than repaired: auto derivation needs `f_max > f_min > 0` and the run gate blocks a project without a valid band. f_max sets the mesh cell size (finer at higher frequency), and f_min feeds the pulse and the absorber sizing. The radiation-pattern and surface-current readouts report their frequency resolution as the band span divided by the number of capture points.

### The quality slider

Quality is a continuous value from 0.0 to 3.0 with four named stops: **Low**, **Medium**, **High**, and **Very High** at 0, 1, 2, 3. The value interpolates between the stops, so 1.5 is halfway between Medium and High. One value drives every accuracy knob at once:

- Cells across the narrowest measured conductor width and gap on each in-plane axis, and cells per wavelength.
- Cells through the thinnest dielectric (the dz resolution).
- CFS-CPML absorber depth, floored at a per-axis cell count.
- The energy ring-down threshold that ends the run.
- The air-margin cell counts on every face.
- Radiation-pattern, surface-current, and field-FFT frequency-bin counts.
- The convergence-study thresholds and pass count.

Raising quality refines the mesh, deepens the absorber, tightens ring-down, and adds capture bins together. It never changes the physics setup in a way that depends on which outputs are enabled: toggling the radiation-pattern export does not move a resonance.

### Per-axis derivation

Each in-plane axis is sized on its own measured copper. For an axis, the cell is the finest of: the guided wavelength at f_max over the cells-per-wavelength count (guided by the largest stackup permittivity, `lambda / sqrt(eps_r)`), the narrowest measured width on that axis over the width-cell count, the narrowest measured gap over the gap-cell count, and edge fringing from the thinnest dielectric height. A cross-axis feature guard then bounds an axis that carries no measured feature of its own by the finest cell any measured feature on the other axis demands, so a notch invisible to a scan along one axis still resolves. dz is the through-substrate rule: the thinnest dielectric over the dielectric-cell count, capped by the free-space cell. The estimate and derived panels expose which rule set each axis.

### Minimum feature per axis

The **Min feature X/Y/Z** fields set a resolution floor per axis, each independent. On X and Y the value is both a floor and a measurement threshold: set it, and the mesh refines that axis to resolve it, while measured copper below the value does not drive the mesh and is reported as a warning naming the feature, its location, and the consequence. Left at 0, an in-plane axis takes no feature-size constraint: measured copper only warns, and the cell stays at the cells-per-wavelength and dielectric baseline. Z is a pure floor with no threshold behavior, and stackup layers are always resolved regardless. Below the fields, a **Measured minimum** line reports the narrowest measured feature per axis, and **Use suggested** copies the values in: X and Y from the measured copper, Z from the thinnest dielectric layer.

![Quality and geometry card with the measured-minimum readout](https://rayrf.com/docs/quality-panel.png)

### The derived-settings summary

The **Derived Settings** panel reports the full result: the per-axis base cell and what drove it, the resolved mesh dimensions with cell count and VRAM, the per-face air margins, the PML depth in cells, the ring-down threshold and runtime estimate, and the enabled outputs with their bin counts. A **Constraint Traceability** block lists one line per derived value naming the rule behind it, with the mesh-scale driver marked. When the probe measured a feature the mesh does not resolve, a **Mesh Feature Findings** block quotes it, and a **Per-term Mesh Cost** block reports the mesh each measured term would produce alone.

![Derived settings summary with constraint traceability](https://rayrf.com/docs/auto-derived.png)

A footer under the panel reads the quality name, the total cells, the VRAM, and the runtime cost relative to a Low-quality run. The CLI mirrors it:

```
rayrf estimate --project patch.rfsim --quality medium
```

`estimate` derives the same mesh and prints the cell count, memory, boundary sizing, ring-down, runtime cost, the measured per-axis suggestion, and the same advisory warnings, without launching the solver. `--quality` accepts a name or a number in 0 to 3.

### Convergence study

The **Perform Convergence Study** checkbox, which writes `auto_convergence_enabled`, runs the solver several times with an increasingly fine mesh until the tracked resonance dips stabilize, instead of a single pass. The frequency-shift and depth thresholds follow the quality level, and each pass refines the mesh by a fixed factor up to a pass cap. This costs several full runs. The threshold, refine-factor, and pass-cap fields are in the [settings reference](#settings-reference).

---

## Advanced mode

Section: Simulation. Canonical page: https://rayrf.com/docs/simulation/advanced-mode

Advanced mode hands you the mesh, air margins, PML depth, run length, and convergence criteria that [Auto mode](#auto-mode-and-quality) derives for you. Boundary face types live on [Boundaries](#boundary-conditions). This page covers everything else the Advanced toggle exposes.

### Switching to Advanced

The Simulate tab has an Auto / Advanced toggle. In Advanced mode the quality slider stops driving the mesh: you set `dx_mm`, `dy_mm`, `dz_mm`, the air margins, the PML depth, and the run-length fields, and the run uses them exactly as written. Nothing is snapped or re-derived behind you.

Advanced values persist in the project independently of the auto values. Every Advanced field has an `adv_*` shadow, so a project saved in Auto mode keeps its Advanced entries and reopening in Advanced mode restores them. **Transfer to Advanced** copies the current auto-derived mesh, margins, PML, and boundary preset into the Advanced fields as a starting point, then you edit from there.

### Manual mesh

The Mesh / Cell Size card sets the three base cell sizes.

![Mesh / Cell Size card](https://rayrf.com/docs/mesh-panel.png)

`dx_mm`, `dy_mm`, and `dz_mm` are the Yee cell dimensions in millimetres, default `0.2` each. The run uses them exactly. [FDTD tips](#fdtd-tips) covers picking cell sizes.

`dz` is the trap. In Advanced mode it is used exactly, never snapped, so if it does not evenly divide the conductor stackup height the substrate lands between grid lines. `validate` warns (`dz does not divide the substrate height`) and reports the closest dz that would fit, but it does not change your value. The reasoning and how to pick dz are on [FDTD tips](#fdtd-tips).

### Mesh grading

Grading keeps fine cells in the board region and lets them grow toward the air and PML, cutting total cells while holding resolution where the fields are. Enable it with the grading checkbox. The controls:

- Refine margin (`mesh_grading_margin_mm`, default `1.0`): how far the fine-cell region extends past the board in every direction.
- Per-axis multiplier (`mesh_grading_{x,y,z}_mult`): the board-region refinement factor, `1.0` meaning no refinement. Default `2.0` in Z (half the Z cell near the board), `1.0` in X and Y.
- Per-axis max ratio (`mesh_grading_{x,y,z}_max_ratio`, default `1.10`): the largest cell-to-cell size step allowed in that axis' transition. Must exceed `1.0`, or no transition can satisfy it.

### Air margins

The air gap between the geometry and the boundary has three modes, selected by the Air Margins mode combo, each with per-face around / above / below values.

![PML and Air Margins card](https://rayrf.com/docs/domain-panel.png)

- Cell count (`air_cells`, default `6`): the same number of air cells on every face. Keyed to the mesh, so the physical gap follows the cell size.
- Millimetres (`air_around_mm`, `air_above_mm`, `air_below_mm`, default `10.0`): each face set in mm directly.
- Frequency fraction (`auto_air_sides_factor`, `auto_air_above_factor`, `auto_air_below_factor`, default `0.25`): each face sized as a fraction of the wavelength at `f_min`.

PML depth has its own three modes (`pml_cells_manual` default `8`, `pml_thickness_mm` default `4.0`, `auto_pml_factor` default `0.10`), set on the same card and covered under [Boundaries](#boundary-conditions).

### Run length

The Energy Ringdown card sets when the run stops.

![Energy Ringdown card](https://rayrf.com/docs/runtime-panel.png)

The ring-down criterion is the remaining field energy relative to peak at which the solver stops. It is edited in dB and stored as a ratio (`ringdown_end_crit`), default `-50 dB` (ratio 1e-5). More negative is a longer, more converged run. The floor is `-120 dB`, since the control is logarithmic and zero has no dB value. The check interval and minimum-steps floor default to 20 and 20000 steps and are adjustable through the settings registry (see CLI equivalent below).

**Limit max steps** adds a hard timestep cap (`max_steps`). Leave it off and the run has no cap and stops on ring-down alone. If the cap is hit before ring-down, the S-parameter and radiation results are not converged. See [FDTD tips](#fdtd-tips).

**S-param early stop** (`sparam_early_stop`, off by default) stops the run once the S-parameters stop changing by more than `sparam_delta_crit_db` (default `-40 dB`) between checks. `sparam_min_steps` (default `10000`) is a floor so it cannot fire during the source excitation transient.

### Mesh convergence

The Mesh Convergence card runs a multi-pass study that refines the cell size until the tracked resonances stabilise. Its **Mesh Convergence Study** checkbox writes `auto_mesh_enabled`. It starts from the Cell Size above and refines only the mesh each pass.

- Source (`conv_source`, default `S11`) and resonance count (`conv_num_resonances`, default `1`): which S-parameter and how many of its deepest dips to track, matched between passes by rank.
- Freq shift (`conv_freq_threshold_db`, default `-20 dB`) and depth diff (`conv_depth_threshold_db`, default `-20 dB`): the per-resonance thresholds a pass must meet to count as converged. `-20 dB` is about 1 percent.
- Mesh step (`conv_mesh_refine_factor`, default `1.26`): the division factor each pass. 1.26 is about cbrt(2), roughly doubling total cells.
- Max passes (`conv_max_passes`, default `8`): the hard iteration limit.

### CLI equivalent

Every field above is a project setting reachable headless: override one with `--setting <name>=<value>` or carry the whole set in a JobSpec. See [Scripting and specs](#scripting-and-specs). The full field list with units and bounds is on [Settings reference](#settings-reference).

---

## Boundary conditions

Section: Simulation. Canonical page: https://rayrf.com/docs/simulation/boundaries

The simulation sits in a box of six faces, and each face carries one of three boundary conditions. This page covers the types, the presets, the per-face custom matrix, the spacing between the geometry and each face, and the PML depth controls.

### The three types

| Type | Meaning |
| --- | --- |
| `PML` | Perfectly Matched Layer (CFS-CPML), the only absorbing boundary; use for radiating/open problems. |
| `PEC` | Perfect Electric Conductor hard wall (tangential E = 0); a closed metal box. |
| `PMC` | Perfect Magnetic Conductor hard wall (tangential H = 0), the dual of PEC. |

Presets: `PML`, `PEC`, `PMC`, `Custom`. Faces: `x_lo`, `x_hi`, `y_lo`, `y_hi`, `z_lo`, `z_hi`.

PML, a CFS-CPML absorber, is the only absorbing boundary. Outgoing waves enter the layer and decay instead of reflecting, which makes it the boundary for antennas and any radiating or open problem. PEC is a hard electric wall (tangential E forced to zero): a closed metal box that reflects everything reaching it. PMC is the dual hard wall (tangential H forced to zero) and serves as a symmetry plane. [FDTD tips](#fdtd-tips) covers giving each hard-wall face an explicit spacing.

Radiation capture (NF2FF) works with any face mix that leaves at least one PML face. A box with no PML face, or a PEC or PMC pair on both faces of one axis, is refused. See [Radiation pattern](#radiation-pattern).

### Presets and per-face control

The Type selector on the Simulate tab sets all six faces at once: Open Air (PML), PEC box, PMC box, or Custom.

![Boundary condition panel with the Open Air preset](https://rayrf.com/docs/boundaries-preset.png)

Custom reveals a six-row matrix, one row per face (+Z, -Z, +X, -X, +Y, -Y), each with its own type and its own spacing field.

![Custom per-face matrix mixing PMC, PEC, and PML faces](https://rayrf.com/docs/boundaries-custom.png)

From the CLI the same control is the repeatable `--per-face-bc` flag. `FACE` is one of `x_lo`, `x_hi`, `y_lo`, `y_hi`, `z_lo`, `z_hi`, and any per-face setting forces the preset to Custom:

```
rayrf run --project p.rfsim --per-face-bc z_lo=PEC:0 --per-face-bc x_lo=PMC:4
```

### Spacing between geometry and each face

Every face has an air gap between the bounding box of the geometry and the wall. Two regimes exist per face:

- Derived: with the spacing checkbox unchecked, the face inherits the air margins (auto mode sizes them by cell count with a substrate-height floor). The field shows the derived gap greyed out as a placeholder, never as an editable value. This sizing exists to give a PML face room to absorb.
- Fixed: with the checkbox set, the gap realizes exactly as entered, in mm, overriding the margins on that face. A fixed gap of 0 puts the wall flush against the geometry, which turns a PEC bottom face into an infinite ground plane.

A PEC or PMC face in the Custom matrix requires an explicit spacing. A hard wall that inherits the PML-sized margin can end up against, or inside, the geometry, reflecting energy back into the design and corrupting the S-parameters. The GUI does not let that happen: for a PEC or PMC face the spacing checkbox locks on and the field stays marked required until you enter a value. A Custom-mode case that reaches export with a hard-wall face and no spacing is refused with an error naming the face, so the run never starts on an unspecified gap. The PEC box and PMC box presets are the exception: they keep the derived margins on every face.

### PML depth

`pml_thickness_mm` is the authoritative absorber depth. The panel offers three ways to set it: a manual cell count (default 8 cells), a manual thickness in mm, or an automatic fraction of the wavelength at f_min (default 0.10 λ). A resolved cell count of 0 derives the count from the thickness at export.

The PML is added outside the air margin. It extends the domain rather than consuming the gap, so it never overlaps the geometry. Deeper PML only increases total cell count. Air margins themselves are covered under [Advanced mode](#advanced-mode), and every boundary and spacing field is listed in the [settings reference](#settings-reference).

---

## Running simulations

Section: Simulation. Canonical page: https://rayrf.com/docs/simulation/running

Run Simulation exports the case and launches the solver. This page covers the run path and its pre-flight gate, choosing the CPU or GPU engine, the live telemetry a run shows, where results land when it finishes, and Live mode.

![Simulate tab during a run: left rail, config, and the runtime panel with the energy ringdown plot](https://rayrf.com/docs/running-simulate-tab.png)

### Run Simulation and Preview Mesh

The two actions sit at the top of the left rail. Both need a saved project, and both save a dirty project before they start.

![Left rail: mode, actions, run button, engine checkboxes, and the last-run summary](https://rayrf.com/docs/simulate-actions.png)

Run Simulation exports the project to a solver case and launches the backend. Before anything launches, the same pre-flight gate the CLI uses (`sim.preflight`) validates the project. A blocking error refuses the run and names the reason: a port too small for the mesh, a port whose vertical span collapses to zero cells, a collapsed shape that would rasterize blank, an unknown boundary type, a negative air margin, a radiation pattern with no open face. Nothing is silently clamped or recomputed. A warning does not block: it is shown before the run so you decide. The gate also refuses a GPU run whose estimated VRAM exceeds the detected card, and a CPU run whose footprint exceeds free RAM.

Preview Mesh builds the 3D FDTD mesh and shows it without running the solver, so you can check resolution and cell count. It runs the same pre-flight gate first, so a bad port does not silently render an empty mesh. Above about 20 million estimated cells the preview can take minutes to build and render, so it asks before continuing. It never blocks: you always decide. With no mesh estimate yet, it says so in the log and proceeds rather than inventing a number.

### GPU and CPU engines

The CUDA GPU backend is the default. At startup the app probes for a compatible NVIDIA GPU. When one is found, its name and VRAM print to the console. When none is found, the run falls back to the CPU backend (OpenMP): the **Use CPU backend** checkbox locks on for the session and a banner explains the fallback. The same fp32 solver runs on either engine and produces equivalent results to numerical precision. The CPU is significantly slower.

Check **Use CPU backend** yourself to run on the CPU with a compatible GPU present. Leaving it unchecked with no GPU detected, then clicking Run, raises one dialog offering the CPU fallback, with an option to remember the choice until the GPU hardware changes. A remembered choice is keyed to the GPU fingerprint, so a driver or card change re-prompts.

Selecting a specific GPU by index is a remote and CLI concern only: `device_index` pins a GPU slot on a multi-GPU remote runner, and a local run refuses it.

### Live telemetry during a run

While the solver runs, the title bar shows elapsed time and throughput in GCell/s, and the runtime panel updates in place.

- **Energy Ringdown** plots the energy ratio against time step on a log axis, with the stop threshold drawn as a dashed line. The run ends when the ratio crosses it.
- **S-Parameter Convergence** overlays the S-parameter estimate from recent passes so you can watch it settle.
- **Runtime Stats** reads out elapsed, time step, GCell/s, cell size, ringdown, cell count, RAM, VRAM, and disk. A VRAM overflow raises a warning here.

Two buttons sit below the stats. **Pull Snapshot** exports the results computed so far without stopping: the solver pauses briefly to write S-parameters, fields, and radiation data, then resumes. The loaded snapshot is marked partial on the ringdown plot and in the result tabs' freshness badge. **Stop Early** ends the run before the energy criterion, and asks what to do with the work so far: Stop and Keep Results flushes a partial run and keeps it, or Discard All Results throws it away. A kept partial run is recorded as stopped, never as a clean completion.

### When a run finishes

A run ends when the energy ringdown crosses its threshold or hits the configured step and ring-down caps, and a capped run is not converged ([FDTD tips](#fdtd-tips)). Results land in the project: the S-Parameters and Simulate tabs always update, and the Radiation Pattern and Field Viewer tabs update when their outputs were enabled. Each tab that got new output shows a dot until you open it. When the project has result externalization on, the heavy run output is written to a `.rfsimout` sidecar next to the project instead of inside the `.rfsim`.

If you edit the geometry or settings after a run, the result tabs show a freshness badge saying the geometry, the config, or both changed since the run produced the data on screen. The badge separates results that still match the current inputs from results that are now stale, so a plot is never mistaken for the edited design. Switching between auto and advanced mode keeps the existing results, and the badge marks them changed only when the effective settings differ from the run.

### Live mode

Live mode reruns the simulation automatically as you edit, for fast iteration on small designs. Enter it with the **LIVE** button in the tab row. The tabs reparent from the tab bar into a 2x2 dock grid: Edit above Simulate on the left, S-Parameters above the Field Viewer on the right, with Radiation tabbed onto the Field Viewer. You can rearrange the grid and the layout persists with the project. Markers placed on the S-parameter plot keep tracking across reruns.

Every geometry or settings edit schedules a rerun after a 120 ms debounce, so a burst of edits fires one run, not one per keystroke. An edit that arrives while a run is in flight supersedes it: the running solver is stopped and relaunched on the newer input, and the superseded run is discarded rather than counted. Live mode auto-runs with no Run dialog, so it falls back to the CPU backend when no GPU was found, and it disables itself after two consecutive failed runs. Leave Live mode with the same LIVE button.

### From the command line

`rayrf run` runs the same solver headless, and `rayrf serve` and `rayrf remote` drive runs on another machine. On the Simulate tab, **Configure remotes** opens the runner manager, and **Run remotely** with the runner picker beside it submits the run to the picked runner. See [Remote overview](#remote-running). The full verb and flag list is in the [CLI reference](#cli-reference).

---

## S-parameters and Smith chart

Section: Results. Canonical page: https://rayrf.com/docs/results/s-parameters

The S-Parameters tab plots the network response of a finished run: magnitude in dB against frequency, and the same data on a Smith chart. It also carries the markers, reference overlays, imports, and exports for that data.

After a run, each active port contributes a column of traces. The tab shows the reflection Sii for every active port, and both Sij and Sji for every active pair. The reflection trace of the port that was driven this run is the one that carries impedance, so it also appears on the Smith chart.

![Notch-filter S-parameters, dB magnitude](https://rayrf.com/docs/sparams-notch.png)

### Traces and parameter chips

The toolbar starts with a chip for **S11** and **S21** and a **More Parameters** popover. A chip toggles that trace on the plot and colours itself to match it. More Parameters lists every remaining Sij for the port count, plus S12 and S22 even for a one-port run, and holds one checkbox per imported overlay. Each trace keeps a fixed colour so a port reads as the same hue across the plot, the chart, and the editor canvas.

Which traces start visible follows the active ports, and changing the active port set resets the selection to that default. Missing data never disables a chip: a checked but empty trace means the run did not produce it, so re-run rather than read a blank plot as a broken control.

The freshness badge in the corner reads **Latest results** when the plot matches the current project. It switches to a stale warning ("Geometry changed since this run", "Config changed since this run", or both) once you edit the project after the run, and to "Running, results shown reflect last run" during a solve. A stale badge means re-run before trusting the numbers.

### The two views

**Split View** is on by default and shows the dB magnitude plot and the Smith chart side by side, divided by a draggable splitter. Uncheck it to show one view at a time. The **dB Magnitude** and **Smith Chart** buttons then switch between them. In split view those buttons only pick which plot is active for marker placement and export.

The dB plot has frequency in GHz on the x-axis and magnitude in dB on the y-axis. Drag to pan, scroll to zoom both axes, `Ctrl`+scroll to zoom frequency only, `Shift`+scroll to zoom magnitude only.

![Notch-filter reflection on the Smith chart](https://rayrf.com/docs/sparams-smith.png)

The Smith chart is normalized to 50 Ω. Constant-resistance circles and constant-reactance arcs are labelled in ohms, the centre is the matched load, the left real-axis endpoint is a short and the right is an open, and inductive (+jX) reactance is the upper half. It stays square and aspect-locked, so it never stretches. Only reflection traces carry the complex impedance the chart needs, so a magnitude-only transmission trace does not draw here.

#### Range and Home

**Range** opens a popover to set the frequency and magnitude axis limits. Leave a bound on **Auto** to fit the data. The popover reflects the plot's true current extent: a manual bound shows its value, and an Auto bound shows the value it resolves to in the grey caption beneath. **Home** clears every bound back to Auto and refits both views. Range applies to the dB plot only.

### Markers

**Marker** (shortcut `M`) arms placement. Click the plot to drop a marker on the nearest trace point, and placement disarms after one marker. A marker reads out its frequency and magnitude, the impedance where the trace carries it, and, on a reflection trace, the bandwidth of the dip it sits in. On the Smith chart the readout is frequency, |Gamma|, angle of Gamma, and impedance.

Drag a marker to slide it along the trace, or to another trace. `Esc` cancels placement, `Delete` removes the selected marker, and `Ctrl+Z` undoes the last marker edit.

Right-click a marker for Rename, Hide, Delete, and, on the dB plot, **Properties**. The properties dialog:

- Sets the source trace and renames the marker.
- Follows a chosen dip. Pick the rank N and the ordering (deepest, shallowest, lowest frequency, highest frequency). A tracked marker re-snaps to that dip on every replot, including each Live-mode rerun, so a linked [surface-current or radiation](#field-viewer) viewer stays on the resonance as it moves. If the dip is gone the marker shows **(no match)** and holds its last frequency.
- Draws a vertical line at the centre frequency, shades the bandwidth band below a dB threshold (default -10 dB, reflection traces only), and shows a centre-frequency and bandwidth readout on the plot.

### Set as Reference

**Set as Reference** freezes the traces currently on screen as grey dashed overlays, so you can change the design and watch each trace shift against the frozen one. The baseline is stored in the project, and one is kept at a time: setting a new reference replaces the old one.

The reference is view-neutral. Reflection traces keep their complex impedance and phase, so the baseline shows on both the dB plot and the Smith chart, while transmission traces keep magnitude and show on the dB plot only. Setting a reference does not move the axes. In live mode the button moves into the More Parameters popover.

### Importing reference traces

Import a measured or external sweep to overlay it on the run. Both live in the **Import / Export** dropdown, render as dashed traces in distinct colours, and are stored in the project so the source file can be deleted afterward. Importing requires a signed-in license.

- **Touchstone** reads `.s1p` through `.s8p` and `.sNp`. Each S-parameter in the file becomes one overlay trace.
- **CSV** opens a column-mapping dialog, because a plain CSV is not self-describing. Choose the frequency column and one of three data types: an S-parameter as dB magnitude plus phase in degrees, an S-parameter as real plus imaginary, or an impedance Z as real plus imaginary in ohms. The second value column is optional. The frequency unit is taken from an explicit choice, then from the column header, then guessed from the values, with the guess confirmed before import. The reference Z0 you set converts an imported impedance to S and is stored with the trace so the Smith chart renders it correctly.

### Exporting

Every export is in the **Import / Export** dropdown. Exact file layouts are on [File formats and outputs](#file-formats-and-outputs).

- **CSV** writes one row per frequency, with each trace interpolated onto the first parameter's frequency grid and points outside a trace's own band left blank. In split view, when reflection data is present, a dialog offers the dB magnitude, the reflection coefficient, or both.
- **Touchstone** writes all S-parameters as one network file, labelled by port count (`.s2p` for two ports). Filled values are disclosed in the export dialog and in file comments. The fill rules are on [File formats and outputs](#file-formats-and-outputs). Touchstone export is a Pro feature and shows a lock badge otherwise.
- **PNG** saves the plot as an image. In split view a dialog offers the active plot, both plots as two files, or both stitched into one wide image. Marker and curve legends are drawn into the saved image.

---

## Radiation pattern

Section: Results. Canonical page: https://rayrf.com/docs/results/radiation-pattern

The Radiation Pattern tab shows the antenna far field as a 2D polar plot and a 3D lobe over the geometry, one pattern per simulated frequency. It fills in when a run finishes with the radiation pattern enabled.

### Where the data comes from

The solver wraps the structure in a Huygens surface and records the near field on it during the run, then transforms that near field to the far field. Turn it on with `enable_nf2ff` (the **Compute Radiation Pattern** output toggle). The transform runs at a set of frequencies spread across the band, so every result below is available at each of those points. In auto mode the frequency count comes from the quality preset. In advanced mode `nf2ff_num_freqs` sets it directly, default 51, up to the backend ceiling of 512 points.

The far field is defined only when the box radiates into open space, so the run needs at least one PML face. Any mix of PML, PEC, and PMC on the remaining faces works. A PEC face acts as an infinite ground plane by image theory. Two cases are refused before the solve: a box with no PML face (a closed cavity, no radiated power), and a PEC or PMC pair on both faces of one axis (that axis is closed, so the pattern is undefined). See [Boundaries](#boundary-conditions).

### Polar view

The polar view draws the two principal cuts, the E-plane and the H-plane. The right panel holds a frequency selector, a metrics block, and toggles for the two traces.

![Polar radiation pattern with the E-plane and H-plane cuts and the metrics panel](https://rayrf.com/docs/radiation-polar.png)

The frequency slider and the linked GHz spinbox pick which stored pattern to show. The dB scale is fixed across the slider (floor at -25 dB, ceiling a little above the peak directivity of the whole band) so the traces stay comparable as you scrub. The title reads the frequency and the peak directivity in dBi.

The metrics block reports:

- **Directivity max.** Peak directivity at the selected frequency, in dBi.
- **HPBW theta** and **HPBW phi.** Half-power beamwidth of the E-plane and H-plane cuts, in degrees, measured across the connected -3 dB region around the peak.
- **Sidelobe.** Level of the largest lobe outside the main lobe, in dB relative to the peak, measured on the plane cut that carries the global peak.
- **F/B ratio.** Peak gain minus the gain in the opposite direction on that same cut, in dB.

### 3D view

The 3D view draws the pattern as a colour-mapped lobe with the geometry overlaid at scale, so the beam direction reads against the antenna. Radius and colour both follow directivity over a 40 dB window below the peak, and the scalar bar is labeled in dBi.

![3D radiation lobe colour-mapped in dBi over the antenna geometry](https://rayrf.com/docs/radiation-3d.png)

Two opacity sliders set the alpha blend between the lobe and the geometry, and either can be hidden with its visibility toggle. Position and scale each have an auto mode and a manual mode:

- **Auto position** centers the lobe on the geometry. Turn it off to set the X, Y, and Z origin directly in millimetres.
- **Auto scale** sizes the lobe so its tip-to-tip diameter matches the longest dimension of the geometry. Turn it off to set the scale directly, which holds a fixed lobe size while you step across frequencies.

**Split View** shows the polar and 3D views side by side with a draggable splitter, both driven by the one frequency slider. With split off, the **Polar Pattern** and **3D Pattern** buttons switch between the two panels.

### Export to CSV

**Export CSV** asks what to write, then a save path. The scope dialog offers two choices:

- **E-plane and H-plane cuts** writes only the two displayed curves. Columns: `theta_deg`, `phi_deg`, `gain_dBi`, `plane`.
- **Full 3D pattern** writes every theta/phi sample the run holds for that frequency, with no resampling. Columns: `theta_deg`, `phi_deg`, `gain_dBi`.

Both files open with comment lines naming the frequency, the run, and the project. The export covers the frequency selected in the tab.

The CLI writes a flat table across the whole band instead, straight from the backend columns:

```
freq_hz,theta_deg,phi_deg,D_total_dBi,D_theta_dBi,D_phi_dBi
```

`D_total_dBi` is the total directivity, `D_theta_dBi` and `D_phi_dBi` the two polarization components, all in dBi.

### From the CLI

`rayrf run` writes the same patterns headless: `--export-polar` for the polar PNG, `--export-rad3d` for the 3D PNG (add `--export-rad3d-vtk` for the surface as VTP), and `--export-rad-pattern` for the flat CSV above. `--polar-freq-mhz` and `--rad3d-freq-mhz` pick the frequency, defaulting to the band centre. Full flag list on the [CLI reference](#cli-reference).

---

## Field viewer

Section: Results. Canonical page: https://rayrf.com/docs/results/field-viewer

The Field Viewer tab shows what a run wrote inside the domain: surface current on a conductor layer in 2D, and the 3D E, H, and energy volumes. Two mode buttons at the top of the toolbar switch between them.

Both views share one toolbar. A slider with prev and next buttons scrubs frames, the colormap and geometry-overlay controls sit on the second row, and the corner freshness badge reads **Latest results** when the view matches the current project or a stale warning once you edit after the run.

### Surface currents

**Surface Currents** is the default mode. It draws the current-density magnitude |J| on one conductor layer as a heatmap, with a colorbar in A/m and the layer name in the status bar. The conductor outline is overlaid as a white contour.

![Surface current magnitude on a conductor layer](https://rayrf.com/docs/fieldview-currents.png)

**Frequency** and **Time** switch the domain. Frequency shows the steady-state current at each DFT point, labelled in GHz, and is the default. Time steps through the recorded time-domain frames, labelled in picoseconds, and **Play** auto-advances them. Play is hidden in the frequency domain, which is a static sweep. Each domain keeps its own scrub position.

**Range** sets how the colour scale is chosen: Local rescales to the current frame, Global spans every frame in the domain, and Ref-based pins the scale to one reference frequency or frame that you pick. **Cmap** selects the colormap. Drag to pan, scroll to zoom.

To record surface currents, turn on `enable_surface_currents` and name the conductor layer in `surface_current_layer`. The run refuses if that layer is not a conductor. Time-domain frames are written every `surface_current_interval` steps, or at an automatic stride of about ten samples per period at f_max when the auto interval is on. The frequency-domain DFT accumulates every step regardless of the frame interval, over `surface_current_num_freqs` points (default 63).

### E/H fields

**E/H Fields** renders the 3D field as a volume: a translucent glow over the whole domain with a lit hot core where the field is strongest, drawn over the geometry mesh. It is a time-domain view that plays through the exported frames.

![Volumetric E-field with the hot core over the geometry](https://rayrf.com/docs/fieldview-volumetric.png)

Three quantities can be exported and viewed, each written as one frame per step:

| Source | Setting | Unit |
| --- | --- | --- |
| E magnitude | `export_e_mag` | V/m |
| H magnitude | `export_h_mag` | A/m |
| Total energy density | `export_energy` | J/m^3 |

`export_e_mag` is the default content. H is time-centered to the E level before the magnitude, and energy density combines the aligned E and H at each cell. The **Source** selector appears when a run wrote more than one of them, and picks which the volume shows. A run that wrote E alone keeps the plain toolbar.

**Range** sets the colour scale: Local for the current frame, Global across all frames, or Frame-based pinned to a reference frame you pick. Global scans every frame off the main thread, so the view holds the local scale until it finishes. **Cmap** selects the colormap, **Geometry** toggles the structure overlay, and the opacity slider sets its transparency.

### Export cost and cleanup

Field export is heavy. A VTK run writes one volume per exported quantity per frame, so files reach hundreds of MB or more with a matching disk and runtime cost. Two settings bound the volume of data:

- The capture interval, dense for smooth playback or sparse to save disk.
- The step-range window (`field_export_range_enabled` with a start and end step), which drops warm-up frames. The solver still runs every step, only the disk write outside the window is skipped.

[FDTD tips](#fdtd-tips) covers when the fields are worth exporting. The on-disk frame formats are on [File formats and outputs](#file-formats-and-outputs).

`export_vdb` writes OpenVDB volumes instead of VTK, in the format on that same page. VDB carries |E| only, so the **Source** selector is E-only for a VDB run.

> Note: When you close a project whose field frames, geometry mesh, and surface-current data together exceed 100 MB on disk, RayRF asks which of those categories to delete. Removed data cannot be viewed again without re-running.

---

## Remote running

Section: Remote running. Canonical page: https://rayrf.com/docs/remote/overview

Submit a simulation to a machine you own and stream the results back to your desktop. Nothing goes to a third-party cloud: the case runs on your hardware and the results land back in your project folder. Setting up the runner box is a separate walkthrough: [Runner setup](#runner-setup).

Remote running is a Pro feature. Founding accounts are Pro.

### What it is

A runner is a machine you own that accepts RayRF jobs. When you run remotely, the case uploads to the runner, the solver runs there, telemetry streams back live, and the results download to your project folder.

Jobs move over a direct TLS 1.3 link with certificate pinning, straight from your desktop to the runner's address. The website never carries the case files, the results, or the session keys. It brokers discovery metadata only: a runner registers its reachable address and identity fingerprint under your account, and the desktop resolves that to connect directly.

> Note: The link is pinned to the runner's key, not to a certificate authority. The first connection records the runner's fingerprint. If that key ever changes, the connection is refused rather than trusted silently.

### The Remote runners manager

Open it from the Simulate tab with Configure remotes. The manager has a machines column on the left and a jobs column on the right. Selecting one or more machines lists their jobs, unioned across machines. Actions live on right-click menus.

![Remote runners manager: machines column, jobs column, and the setup actions](https://rayrf.com/docs/remote-tab.png)

Three buttons add a machine:

- Add server connects by host and port. You confirm the runner's identity the first time you connect, then it is pinned.
- Discover from account pulls in runners registered to your account and adds them with their address and attested fingerprint, so you do not type a host by hand. This needs a signed-in Pro account.
- Use this computer as a runner closes the editor and opens a runner status window that serves jobs for your account from this machine. Quit that window and start RayRF again to return to editing.

Each machine card shows the runner's CPU, RAM, disk, and GPU, and a status dot: online, offline, or running. The address shows on hover.

Machines that stop answering leave the main list for a collapsed archive strip below it, ordered by when each was last seen. Click the strip to show or hide them. An archived machine returns to the main list on its own when it answers again, and right-click still offers Remove for entries that are gone for good.

### Submitting a run

On the Simulate tab, check Run remotely and pick a runner from the selector below it. With the checkbox on and no runner selected, the run is blocked with a message rather than falling back to your local machine.

On submit, the case uploads to the runner, which queues and runs it. Only the solver-input files move: the geometry description and the port bitmasks. The license token is never sent over the wire. Telemetry streams back to the same progress panel a local run uses, so the S-parameters, Smith chart, and field previews update as the solve advances.

When the run finishes, the small results, the CSV and JSON tables, download automatically into the project's `_sim_result` folder, and the result tabs open as they do for a local run. Heavy artifacts are fetched on demand: the raw field frames, surface currents, and mesh geometry stay on the runner until you ask for them. The Radiation and Field Viewer tabs show a Download banner when a view needs an artifact that the automatic bundle skipped, and the jobs column lets you download any tier with its on-disk size shown.

Because the job lives on the runner's queue, closing the app detaches from it. The run keeps going, and you collect the results later from the manager.

### Managing jobs

Right-click a job in the jobs column to download its results or cancel it while it is queued or running. Right-click a machine to refresh it, rename it, or clear its job history. Clearing keeps a job that is currently running and any job that finished in the last minute.

The runner's queue is durable. Jobs survive a runner restart, and a running job whose backend is interrupted by a restart or power loss is re-queued and run again from the start.

### Identity and trust

Every runner has an identity fingerprint, printed when the box is set up. The first time you connect to a runner, the desktop shows that fingerprint and asks you to trust it. Compare it against the value the runner printed, accept, and the key is pinned for that machine. From then on the runner must present the same key. A changed key is refused, so a substituted runner cannot impersonate one you trusted.

### Roles

The runner grants control by account. The owner account, which defaults to the account on the runner's license key, can submit, cancel, and clear jobs. Any other account connects view-only: it can watch jobs and download results, but cannot submit or control. Submitting also needs your own Pro entitlement on the connecting machine.

### From the command line

The `rayrf serve` and `rayrf remote` verbs drive runners headless, for scripted submission and for agents. See the [CLI reference](#cli-reference) for the full verb list and [Runner setup](#runner-setup) for the box-side commands.

---

## Runner setup

Section: Remote running. Canonical page: https://rayrf.com/docs/remote/runner-setup

A runner is a machine you own that accepts RayRF jobs: install RayRF on it, license it against your account, open one TCP port, and keep the daemon running. This page is the box-side walkthrough. Driving runners from the desktop app is covered in [Remote running](#remote-running).

Runner licensing is seatless. A Pro or Founding license key from the dashboard licenses page validates the runner against your account and signs a machine-bound token for every job, without consuming an activation seat. Run as many runners as you want, and your desktop installs keep their seats.

### Linux

Install once, with the license key from the dashboard licenses page:

```
./RayRF-Studio-1.1.9-x86_64.AppImage serve --install --license-key RAYRF-XXXX-XXXX-XXXX-XXXX
```

Then start serving:

```
./RayRF-Studio-1.1.9-x86_64.AppImage serve
```

The runner validates the key, registers this machine's address to your account, and your other machines list it under Discover from account. On a GPU box pass `--backend cuda` with `--install` so jobs default to the GPU. Later starts are the same plain `serve`, because the configuration is stored.

Open the runner port so clients can reach it. Jobs move over a direct link to port 8787, which a host firewall or cloud security group often blocks by default. On a box running `ufw` that is `sudo ufw allow 8787/tcp`. On a cloud VM also open the port in the provider's security group.

To keep the runner alive across reboots, pass `--autostart` with `--install`. It writes a systemd unit into the state dir and prints the install command:

```
sudo cp /var/lib/rayrf/rayrf-runner.service /etc/systemd/system/ && sudo systemctl enable --now rayrf-runner
```

### Windows

The same two steps, from the install folder (`%LOCALAPPDATA%\Programs\RayRF Studio`). `rayrf` resolves to the console launcher `rayrf.com`, which waits and streams output, and `RayRF.exe` is the windowed app.

```
.\rayrf serve --install --license-key RAYRF-XXXX-XXXX-XXXX-XXXX
```

```
.\rayrf serve
```

Open the runner port so clients can reach it, the same as on Linux. Windows Firewall blocks inbound connections by default, so a runner that registers with your account is still unreachable until port 8787 is allowed. From an Administrator PowerShell:

```
netsh advfirewall firewall add rule name="RayRF runner 8787" dir=in action=allow protocol=TCP localport=8787
```

On a cloud VM also open the port in the provider's security group. If the machine's network is set to Public, allow the rule on the Public profile (the command above covers all profiles).

To run at startup, register a Scheduled Task (or a service wrapper) that runs `RayRF.exe serve --state-dir "C:\ProgramData\RayRF\runner"`. Passing `--autostart` with `--install` prints this hint with the resolved state dir.

### Advertise a fixed address

By default the runner registers its own detected address. When that address is not what peers reach, pass the reachable endpoint yourself with `--advertise HOST:PORT` at install or serve time: on a NAT or portmapped host (a rented container, a port-forwarded home box) that is the external address and mapped port, which can differ from the listen port. Registration is metadata only. Jobs move over a direct TLS 1.3 link to the advertised address, so the port (default 8787) must be reachable through the firewall or cloud security group.

`--advertise off` skips account registration entirely. The runner then serves direct connections only, and clients add it by host, port, and fingerprint.

### What each piece does

- `--install` writes `runner.json` (name, owner, host, port, slots, backend, advertise, and the license key when one is resolved) and the identity into the state dir. Command-line flags always win over the stored values.
- The state dir holds the identity, the configuration, and the durable job queue. It resolves from `--state-dir`, then the `RAYRF_STATE_DIR` environment variable, then the machine default (`C:\ProgramData\RayRF\runner` on Windows, `/var/lib/rayrf` on Linux when writable, else `~/.rayrf/runner`). Keep every invocation on one state dir: a new dir mints a new identity, and clients that pinned the old fingerprint refuse the new one.
- `--backend cuda` or `--backend cpu` sets the default engine for jobs that do not pin one.
- The owner defaults to the license key's account: that account gets submit and control, and every other connection is view-only. `--owner` overrides the account, and `--open-owner` grants control to any peer on a trusted LAN. `--allow-unlicensed` skips the license gate entirely for closed-network use.
- `--slots` sets how many jobs run concurrently (default 1).

### Verify and connect

`--install` prints the runner's identity fingerprint. Give it to anyone who will connect, or compare it in the trust prompt on first connect. Either way the pin is stored, and a later key change on the runner is refused.

```
rayrf serve --health                     status JSON on the box
rayrf remote info --host HOST --fingerprint FP     from a client
```

A client that connects by CLI stores the fingerprint it first trusted in `~/.rayrf/pins.json` (`%USERPROFILE%\.rayrf\pins.json` on Windows). If a runner's key later changes (wiping its state dir mints a new identity), the next connection is refused. After verifying the new fingerprint out of band, reconnect with `--fingerprint NEWFP`, or remove that runner's `host:port` entry from the pins file and reconnect to trust it fresh. In the desktop app, remove the stale machine and re-add or rediscover it, then confirm the new fingerprint at the trust prompt.

The runner also has a windowed form, `rayrf serve --window`, which is what the desktop app's "Use this computer as a runner" button starts: the same daemon with a status window showing account, address, fingerprint, discovery state, and live job counts.

Submit work from the desktop app ([Remote running](#remote-running)) or from the CLI with the `rayrf remote` verbs ([CLI reference](#cli-reference)).

---

## CLI overview

Section: CLI and automation. Canonical page: https://rayrf.com/docs/cli/overview

The `rayrf` command line builds, inspects, validates, runs, and post-processes `.rfsim` projects headless, driving the same solver as the desktop app. This page covers invocation, licensing, the verbs, an end-to-end workflow, and the exit codes and machine-readable output an agent depends on.

### Invocation

The CLI exports a case and launches the backend for you. You never call the solver directly.

```
rayrf run --project patch.rfsim --quality medium    bare form, latest CLI version
rayrf --cli-v1 run --project patch.rfsim            pin the CLI version
```

The bare `rayrf <verb>` form routes to the latest CLI version. `rayrf --cli-v1 <verb>` pins the version so a script keeps working across releases. From a source checkout the entry point is `python app.py`, so the same commands read `python app.py --cli-v1 run ...`. Every example here uses the packaged `rayrf` form.

On Windows the install folder (`%LOCALAPPDATA%\Programs\RayRF Studio`) ships the console launcher `rayrf.com` next to the windowed `RayRF.exe`. A shell resolves `rayrf` to the launcher, which waits and streams output. On Linux the AppImage itself is the command: `./RayRF-Studio-1.1.9-x86_64.AppImage run ...`. A launch with no arguments opens the desktop app, and an argument that is not a verb or a project file prints the unknown command and exits 2.

### Licensing

Headless runs need a Pro-tier license, which carries the CLI capability (`CAP_CLI`). The `auth`, `schema`, `guide`, and `calc` verbs work with no license, and `remote describe-schema` does too, so an agent can read the contracts and size a design before activating. Every other verb, including `validate` and `estimate`, refuses without CAP_CLI and exits 2 with one line on stderr.

```
rayrf auth --license-key KEY    activate this machine (shared with desktop sign-in)
rayrf auth --status             show cached state and re-check with the server
rayrf auth --deactivate         release this machine's seat
```

Activation binds the key to this machine and stores the result where the desktop app reads it, so signing in from either surface covers both.

### Verbs

Nine local verbs plus the `serve` and `remote` group. Each `--help` lists its flags, and [CLI reference](#cli-reference) documents every one.

| Verb | Purpose |
| --- | --- |
| `rayrf auth` | Manage the local license cache. |
| `rayrf run` | Run one project or all projects in a folder (sequential). |
| `rayrf reprocess` | Recompute S-parameter CSV/PNG from embedded port time-series without running the backend. |
| `rayrf project` | Build / inspect / mutate a project from a declarative spec. |
| `rayrf schema` | Print the JSON Schema / reference for a file format or contract. |
| `rayrf guide` | Built-in reference: orientation and topics (workflow, geometry, ports, boundaries, meshing, settings, diagnostics, remote, pitfalls). |
| `rayrf validate` | Validate a project, spec, or job without running the backend; prints blocking errors and advisory warnings. |
| `rayrf estimate` | Derive the auto-mode mesh and print cell count, VRAM, boundary sizing, and runtime cost without running the backend. |
| `rayrf calc` | RF transmission-line and antenna calculators (microstrip, patch with inset feed, quarter-wave, Smith, cpw, ...). |
| `rayrf serve` | Run the remote-runner daemon. |
| `rayrf remote` | Drive a remote runner. |

### End-to-end workflow

Size the geometry with a calculator, build the project from a spec, inspect and check it, run it, then re-post-process without re-running.

```
# 1. Size a 50 ohm microstrip feed on an FR4 stackup.
rayrf calc microstrip --freq 3.0 --er 4.4 --h 1.6 --z0 50 --json

# 2. Build the project from a spec (schema --what spec is the format).
rayrf project build --spec patch.json --out patch.rfsim

# 3. Inspect what you built.
rayrf project query --project patch.rfsim
rayrf project dump-settings --project patch.rfsim --changed-only

# 4. Check it before paying for a run.
rayrf validate --project patch.rfsim --json
rayrf estimate --project patch.rfsim --quality medium

# 5. Run with a quality preset and the exports you want.
rayrf run --project patch.rfsim --quality medium \
  --export-sparams all --export-touchstone --save-after-run

# 6. Re-post-process the embedded time series over a new band.
rayrf reprocess --project patch.rfsim --fmin 4000 --fmax 7000
```

`rayrf run --dry-run` applies overrides, validates, exports the case, and prints the mesh estimate without launching the backend. `rayrf guide workflow` prints this recipe offline. A project saved with `--save-after-run` or `--save-as` opens in the desktop app with the S-parameters and radiation pattern tabs populated from the embedded results.

### Quality grammar

The `--quality` flag on `run` and `estimate` takes a name, a midpoint, or a number. The names are `low`, `medium` (alias `med`), `high`, and `very-high`, mapping to 0, 1, 2, and 3. The three midpoints are `low-med` (0.5), `med-high` (1.5), and `high-vhigh` (2.5), and any decimal in `[0, 3]` sets the value directly. `run --quality-frac` instead takes a slider fraction in `[0, 1]`, multiplied by 3. A value outside the range is refused, never clamped.

### Exit codes

Scripts branch on the code. The stderr message is one line unless `RAYRF_CLI_TRACEBACK=1` is set.

| Code | Meaning |
| --- | --- |
| `0` | Success. |
| `2` | Usage, spec, validation, or license error the caller can fix. One line on stderr. |
| `1` | The run started but failed at runtime. |
| `130` | Interrupted (Ctrl+C). |

A missing or sub-Pro license, a bad flag, an unknown `--setting`, or an out-of-range `--quality` all exit 2 before the backend launches.

### Machine-readable output

`--json` emits a machine-readable report from `validate`, `estimate`, `calc`, `project query`, and `project dump-settings`. The `remote` subcommands print JSON to stdout and human text to stderr, so a caller reads stdout and logs stderr. Every run writes `run_manifest.json` into the out dir with the resolved parameters, the auto-derived mesh and its reasoning, performance figures, and the artifact list. [Scripting and specs](#scripting-and-specs) documents the manifest.

### Environment variables

| Variable | Effect |
| --- | --- |
| `RAYRF_STATE_DIR` | Directory for remote-runner identity and queue state. |
| `RAYRF_LICENSE_KEY` | License key a runner resolves when no `--license-key` is given. |
| `RAYRF_CLI_SHOW_TELEM=1` | Echo the backend's per-step telemetry stream to stdout. |
| `RAYRF_CLI_TRACEBACK=1` | Print the full traceback on a runtime error instead of one line. |

### Driving the CLI from an agent

`rayrf guide` and its topics are bundled with the executable as offline orientation, and `rayrf schema --what all` prints the machine-readable contract for every file format. The [CLI reference](#cli-reference) lists every verb and flag, [Scripting and specs](#scripting-and-specs) covers the spec and JobSpec formats, and the docs landing page offers the whole reference as a single-file download to load into an agent's context.

---

## CLI reference

Section: CLI and automation. Canonical page: https://rayrf.com/docs/cli/reference

Every verb and flag of the `rayrf` command line, generated from the live argument parsers, so this page always matches the shipped build. Workflows and worked examples are on [CLI overview](#cli-overview), spec authoring on [Scripting and specs](#scripting-and-specs).

### Local verbs

#### `rayrf auth`

Manage the local license cache. Activation binds the key + machine id pair on the RayRF licensing service and persists the result so the desktop UI and the CLI both pick it up on next launch. The auth subcommand does not require the headless capability.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--license-key` | LICENSE_KEY |  | License key to activate on this machine. Format: the same string you received by email after subscribing. Case is normalized to upper, surrounding whitespace stripped. |
| `--machine-name` | MACHINE_NAME |  | Optional human-readable label for this machine (shown in the account dashboard's seat list). Defaults to the host name. |
| `--status` |  |  | Print the cached license state and perform a fresh server check. Default action when no other flag is given. |
| `--deactivate` |  |  | Release this machine's seat on the licensing service and clear the local cache. |

#### `rayrf run`

Run one project or all projects in a folder (sequential).

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--project, -p` | PROJECT |  | Path to a single .rfsim/.emflux project file. |
| `--folder, -f` | FOLDER |  | Path to a folder containing .rfsim/.emflux projects. |
| `--recursive` |  |  | When using `--folder`, search recursively. |
| `--backend` | BACKEND |  | Backend to run: cpu, cuda, or a path to a backend executable. Overrides the job file's backend field. If omitted, auto-detected next to the app (CUDA preferred). |
| `--air-around, --air-around-mm` | AIR_AROUND_MM |  | Override FDTD air around (mm). |
| `--air-above, --air-above-mm` | AIR_ABOVE_MM |  | Override FDTD air above (mm). |
| `--air-below, --air-below-mm` | AIR_BELOW_MM |  | Override FDTD air below (mm). |
| `--dx, --dx-mm` | DX_MM |  | Override dx (mm). |
| `--dy, --dy-mm` | DY_MM |  | Override dy (mm). |
| `--dz, --dz-mm` | DZ_MM |  | Override dz (mm). |
| `--mesh` | uniform / graded |  | Mesh-style override for manual/advanced meshing. 'uniform' disables grading so dx/dy/dz are the literal cell size everywhere (a true uniform mesh); 'graded' enables board refinement by the per-axis grading multipliers. Default: leave the project's saved setting (in advanced mode that can silently refine the board to dx/mult). |
| `--freq-min, --freq-min-mhz` | FREQ_MIN_MHZ |  | Override frequency band minimum in MHz. |
| `--freq-max, --freq-max-mhz` | FREQ_MAX_MHZ |  | Override frequency band maximum in MHz. |
| `--boundary-condition` | BC_ALL |  | Override boundary condition (e.g. PML8). |
| `--eps-r` | EPS_R |  | Override dielectric eps_r. |
| `--thickness, --thickness-mm` | THICKNESS_MM |  | Override dielectric thickness (mm). |
| `--loss-tangent` | LOSS_TANGENT |  | Override every dielectric layer's loss tangent (tan d).  0 = lossless; typical FR4 ~0.02; low-loss substrates ~0.001-0.005. |
| `--fft-length-multiplier, --fft-mult` | FFT_LENGTH_MULTIPLIER |  | Force override FFT length multiplier used for S-parameter post-processing (zero-padding). Must be >= 1. |
| `--job` | JOB_FILE |  | Path to a YAML/JSON JobSpec. Overrides all other run flags. |
| `--quality` | QUALITY |  | Auto-mode quality. Name ("low"\|"medium"\|"high"\|"very-high"\|"med-high") or float in [0,3]. |
| `--quality-frac` | QUALITY_FRAC |  | Auto-mode quality as a slider fraction in [0, 1] (multiplied by 3). |
| `--sim-mode` | auto / advanced |  | Force simulation mode (auto re-derives from `--quality`). |
| `--convergence, --no-convergence` |  |  | Enable or disable the mesh-convergence study (`--convergence` / `--no-convergence`). When enabled the run repeats up to conv_max_passes backend passes, refining dx/dy/dz by conv_mesh_refine_factor per pass, until the deepest S-parameter dips stabilize within the conv_* thresholds. Omit to use the project's saved setting. |
| `--perf-mode` |  |  | Performance-benchmark mode: keep mesh/domain at the chosen quality but loosen ringdown to a fixed -20 dB (1e-2 energy ratio) and disable the convergence study so a high-quality CPU run finishes in minutes instead of 30+ minutes. Used by the perf harness. |
| `--max-steps` | MAX_STEPS |  | Hard cap on backend steps. Use to run a fixed-length throughput-only benchmark (e.g. `--max-steps` 100). When the cap is hit before ringdown, the S-parameter / radiation results are NOT physically valid; intended for kernel-throughput measurement only. |
| `--vram-budget-mb` | VRAM_BUDGET_MB |  | Pre-flight VRAM budget in MB.  When the auto-derive engine estimates the run will need more VRAM than this, the CLI bails out before launching the backend (writes a manifest with status='vram_overflow_predicted').  No effect in advanced mode.  Default: no pre-flight check. |
| `--no-surface-currents` |  |  | Force-disable surface current capture regardless of the project's saved setting. |
| `--no-nf2ff` |  |  | Force-disable NF2FF far-field capture regardless of the project's saved setting. The far-field DFT is VRAM-resident and scales with the Huygens surface x frequency count, so skipping it on an S-parameter-only run avoids large VRAM use for results that are not needed. |
| `--no-raw-fields` |  |  | Force-disable the 3D field VTK dumps (every type: E, H, and total energy density) regardless of the project's saved setting. |
| `--skip-geometry-export` |  |  | Do not write geometry.vtk/geometry.vdb when NF2FF or 3D field export would normally request it. Useful for very large domains when the mesh is not needed. |
| `--dry-run` |  |  | Apply overrides, validate, export the case, and print the mesh estimate, then stop before launching the backend. Exit 2 if validation finds a blocking error. |
| `--per-face-bc` | FACE=TYPE[:MM] | `[]` | Set a per-face boundary condition (repeatable). FACE is one of x_lo, x_hi, y_lo, y_hi, z_lo, z_hi; TYPE is PML, PEC, or PMC; an optional :MM sets a fixed gap from the geometry to that face, realized exactly as given and overriding the air margins there. Faces without :MM follow the air margins. Example: `--per-face-bc` z_lo=PEC:2.0. Any per-face setting forces bc_all to Custom. Always give PEC/PMC faces an explicit gap. |
| `--mesh-preview-size` | MESH_PREVIEW_SIZE |  | Pixel size of the mesh-preview PNG written by `--export-mesh`. |
| `--save-after-run` |  |  | After the run, save the updated project (in-place unless `--save-as` is given). |
| `--save-as` | SAVE_AS |  | Path to save the project as after the run (implies `--save-after-run`). |
| `--externalize-results` | project / run / off |  | Where to store run results when the project is saved. 'project' writes results to a sidecar .rfsimout next to the .rfsim and makes that the project's saved default (so the .rfsim can be committed and the .rfsimout gitignored). 'run' does the same for this run only without changing the saved default. 'off' keeps results inside the .rfsim. Omit to use the project's own setting. |
| `--out-dir` | OUT_DIR |  | Directory to write exports + run_manifest.json. Default: <project_dir>/_sim_result/<case>. |
| `--export-sparams` | all / s11 / s21 / none | `all` | S-parameter CSV/PNG export selection. |
| `--export-touchstone` |  |  | Also write Touchstone (.s1p/.s2p). |
| `--export-smith` |  |  | Write Smith chart PNG. |
| `--export-polar` |  |  | Write polar radiation pattern PNG (E-plane + H-plane). |
| `--polar-freq-mhz` | POLAR_FREQ_MHZ |  | Target frequency (MHz) for polar plot; nearest available is used. |
| `--export-rad3d` |  |  | Write 3D radiation pattern PNG (PyVista off-screen). |
| `--rad3d-freq-mhz` | RAD3D_FREQ_MHZ |  | Target frequency (MHz) for 3D radiation plot; nearest available is used. |
| `--export-rad3d-vtk` |  |  | Also write radiation pattern as a VTK/VTP file. |
| `--export-rad-pattern` |  |  | Write a flat radiation-pattern CSV (freq_hz,theta,phi,gain) from NF2FF data. Not a 3D render; runs under `--no-3d-renders`. |
| `--export-mesh` |  |  | Write mesh preview PNG (PyVista off-screen). |
| `--export-mesh-vtk` |  |  | Also copy geometry.vtk into the out dir. |
| `--export-currents` |  |  | Write surface-currents heatmap PNG at nearest frequency. |
| `--currents-freq-mhz` | CURRENTS_FREQ_MHZ |  | Target frequency (MHz) for surface currents. |
| `--export-all` |  |  | Convenience: enable every export artifact. |
| `--no-3d-renders` |  |  | Skip PyVista off-screen exports (mesh preview, rad3d). |
| `--set` | VAR_SET | `[]` | Variable override name=value applied before the run (repeatable; parametric-studio). |
| `--setting` | NAME=VALUE | `[]` | Set any FDTD setting by name (repeatable), gated by the same value gate as the GUI. An unknown name, bad type/enum, or hard-bound value exits 2; a soft-bound value prints one warning and runs as set. See 'rayrf schema `--what` settings' for the field list. |

#### `rayrf reprocess`

Recompute S-parameter CSV/PNG from embedded port time-series without running the backend. Updates the project file with refreshed embedded outputs.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--project, -p` | PROJECT |  | Path to a single .rfsim/.emflux project file. |
| `--folder, -f` | FOLDER |  | Path to a folder containing .rfsim/.emflux projects. |
| `--recursive` |  |  | When using `--folder`, search recursively. |
| `--fmin, --fmin-mhz` | FMIN_MHZ |  | Override fmin (MHz). |
| `--fmax, --fmax-mhz` | FMAX_MHZ |  | Override fmax (MHz). |
| `--fft-length-multiplier, --fft-mult` | FFT_LENGTH_MULTIPLIER |  | Override FFT length multiplier used for reprocessing (>= 1). |

#### `rayrf project`

Build / inspect / mutate a project from a declarative spec.

#### `rayrf project build`

Build a project from a spec file.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--spec` | SPEC |  | Spec file (.json/.yaml). |
| `--out` | OUT |  | Output project (.rfsim). |

#### `rayrf project export-spec`

Export a project back to a spec.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--project` | PROJECT |  | Project (.rfsim). |
| `--out` | OUT |  | Output spec (.json/.yaml). |

#### `rayrf project query`

Machine-readable project readout.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--project` | PROJECT |  | Project (.rfsim). |
| `--json` |  |  | Emit JSON (default). |

#### `rayrf project dump-settings`

Print every FDTD setting with its value, default, units, and description.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--project` | PROJECT |  | Project (.rfsim). |
| `--json` |  |  | Emit a machine-readable JSON list. |
| `--changed-only` |  |  | Only show settings that differ from their default. |
| `--category` | CATEGORY |  | Only show one category (e.g. boundary, mesh, frequency, field_export). |
| `--advanced, --no-advanced` |  |  | Restrict to advanced-only (`--advanced`) or to non-advanced (`--no-advanced`) settings. |

#### `rayrf project set`

Set variable values (validated).

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--project` | PROJECT |  | Project (.rfsim). |
| `--var` | VAR_SET | `[]` | name=value (repeatable). |
| `--out` | OUT |  | Output project (defaults to in-place). |

#### `rayrf schema`

Print the JSON Schema / reference for a file format or contract.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--what` | spec / query / settings / shapes / ports / boundaries / manifest / all | `all` | spec, query, settings, shapes, ports, boundaries, manifest, or all. |

#### `rayrf guide`

Built-in reference: orientation and topics (workflow, geometry, ports, boundaries, meshing, settings, diagnostics, remote, pitfalls).

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `topic` | TOPIC |  | Topic to print; omit for the orientation and the topic list. |

#### `rayrf validate`

Validate a project, spec, or job without running the backend; prints blocking errors and advisory warnings.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--project, -p` | PROJECT |  | Project (.rfsim) to validate. |
| `--spec` | SPEC |  | Project spec (.json/.yaml) to build and validate. |
| `--job` | JOB |  | JobSpec (.json/.yaml) to validate. |
| `--json` |  |  | Emit a machine-readable JSON report. |

#### `rayrf estimate`

Derive the auto-mode mesh and print cell count, VRAM, boundary sizing, and runtime cost without running the backend.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--project, -p` | PROJECT |  | Project (.rfsim). |
| `--quality` | QUALITY |  | Quality name or 0..3 to estimate at (default: the project's saved quality). |
| `--json` |  |  | Emit a machine-readable JSON report. |

#### `rayrf calc`

RF transmission-line and antenna calculators (microstrip, patch with inset feed, quarter-wave, Smith, cpw, ...). Frequencies in GHz, lengths in mm, impedances in ohm.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `calc_kind` | microstrip / stripline / cpw / gcpw / diff / embedded / patch / lambda / lambda-diel / qwt / smith |  | Which calculator to run. |
| `--freq` | FREQ |  | Frequency (GHz). |
| `--er` | ER |  | Substrate relative permittivity. |
| `--h` | H |  | Substrate height (mm). |
| `--w` | W |  | Strip width (mm); omit to solve from `--z0`. |
| `--z0` | Z0 |  | Target/reference impedance (ohm); patch feed target defaults to 50. |
| `--s` | S |  | Gap/spacing (mm) for cpw/gcpw/diff. |
| `--b` | B |  | Plate spacing (mm) for stripline. |
| `--t` | T |  | Copper thickness (mm) for stripline. |
| `--h2` | H2 |  | Cover height (mm) for embedded microstrip. |
| `--er2` | ER2 |  | Cover permittivity for embedded microstrip. |
| `--tand` | TAND |  | Loss tangent (microstrip dielectric loss). |
| `--z1` | Z1 |  | Source impedance (ohm) for qwt. |
| `--z2` | Z2 |  | Load impedance (ohm) for qwt. |
| `--r` | R |  | Resistance (ohm) for smith. |
| `--x` | X |  | Reactance (ohm) for smith. |
| `--json` |  |  | Emit a machine-readable JSON report. |

### Remote verbs

The `serve` and `remote` verb group runs and drives remote runner boxes. Setup walkthrough: [Runner setup](#runner-setup).

#### `rayrf serve`

Run the remote-runner daemon.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--state-dir` | STATE_DIR |  |  |
| `--name` | NAME |  | runner name (default rayrf-runner) |
| `--owner` | OWNER |  | owner account email; only this account gets control, everyone else connects view-only |
| `--host` | HOST |  | bind address (default 0.0.0.0) |
| `--port` | PORT |  | TCP port (default 8787) |
| `--slots` | SLOTS |  | concurrent jobs (default 1) |
| `--backend` | cpu / cuda |  | default backend when the JobSpec does not pin one (default cpu) |
| `--backend-dir` | BACKEND_DIR |  | directory containing the backend binary (searched first) |
| `--advertise` | ADVERTISE |  | reachable host:port to register with the broker so the desktop can discover this runner (e.g. 45.76.170.248:8787). A licensed runner defaults to this machine's own address; on a NAT or container host pass the externally mapped address instead, and pass 'off' to skip account discovery and stay direct-connect only. `--install` persists it to runner.json. |
| `--license-key` | LICENSE_KEY |  | account license key (or set RAYRF_LICENSE_KEY). A Pro account key licenses the runner with no seat consumed: the website signs a machine-bound token per job, and the same key registers with the broker for 'discover by account'. `--install` persists it to runner.json. The key is never sent to a peer. |
| `--allow-unlicensed` |  |  | development option for a trusted LAN: run jobs without the backend license gate on a runner with no activated license. Without this flag an unlicensed runner refuses to serve. The mode is reported to every connecting client in the handshake. |
| `--open-owner` |  |  | trusted-LAN option: with no `--owner` configured, grant owner control to any connecting peer instead of the default view-only role |
| `--install` |  |  | generate identity + config, print fingerprint |
| `--autostart` |  |  | also emit a run-on-startup service unit |
| `--health` |  |  | print status JSON and exit |
| `--servers` |  |  | print this box's hardware and current load, then exit |
| `--window` |  |  | run the runner with a status window instead of the console; the editor's "Use this computer as a runner" button starts this. |

#### `rayrf remote`

Drive a remote runner.

#### `rayrf remote ls-jobs`

List jobs on a runner.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--host` | HOST |  |  |
| `--port` | PORT | `8787` |  |
| `--fingerprint` | FINGERPRINT |  | pinned runner SPKI fingerprint (hex) |
| `--account` | ACCOUNT |  |  |

#### `rayrf remote status`

Job status.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--host` | HOST |  |  |
| `--port` | PORT | `8787` |  |
| `--fingerprint` | FINGERPRINT |  | pinned runner SPKI fingerprint (hex) |
| `--account` | ACCOUNT |  |  |
| `job_id` | JOB_ID |  |  |

#### `rayrf remote info`

The runner's advertised hardware and version.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--host` | HOST |  |  |
| `--port` | PORT | `8787` |  |
| `--fingerprint` | FINGERPRINT |  | pinned runner SPKI fingerprint (hex) |
| `--account` | ACCOUNT |  |  |

#### `rayrf remote submit`

Submit a pre-exported case dir.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--host` | HOST |  |  |
| `--port` | PORT | `8787` |  |
| `--fingerprint` | FINGERPRINT |  | pinned runner SPKI fingerprint (hex) |
| `--account` | ACCOUNT |  |  |
| `--case-dir` | CASE_DIR |  |  |
| `--spec` | SPEC |  | JobSpec JSON |
| `--meta` | META |  | metadata JSON |
| `--device-index` | DEVICE_INDEX |  | pin a GPU ordinal on a multi-GPU runner |
| `--wait` |  |  | attach and stream to completion |

#### `rayrf remote poll`

Poll status until terminal or timeout.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--host` | HOST |  |  |
| `--port` | PORT | `8787` |  |
| `--fingerprint` | FINGERPRINT |  | pinned runner SPKI fingerprint (hex) |
| `--account` | ACCOUNT |  |  |
| `job_id` | JOB_ID |  |  |
| `--interval` | INTERVAL | `5` | seconds between status checks (default 5) |
| `--timeout` | TIMEOUT |  | give up after this many seconds (0 = no limit) |

#### `rayrf remote list-files`

List a finished job's result files.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--host` | HOST |  |  |
| `--port` | PORT | `8787` |  |
| `--fingerprint` | FINGERPRINT |  | pinned runner SPKI fingerprint (hex) |
| `--account` | ACCOUNT |  |  |
| `job_id` | JOB_ID |  |  |

#### `rayrf remote fetch`

Download results.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--host` | HOST |  |  |
| `--port` | PORT | `8787` |  |
| `--fingerprint` | FINGERPRINT |  | pinned runner SPKI fingerprint (hex) |
| `--account` | ACCOUNT |  |  |
| `job_id` | JOB_ID |  |  |
| `--dest` | DEST |  |  |
| `--artifacts` | auto / ports / pattern / mesh / currents / fields / all |  |  |

#### `rayrf remote cancel`

Cancel a job.

| Flag | Value | Default | Meaning |
| --- | --- | --- | --- |
| `--host` | HOST |  |  |
| `--port` | PORT | `8787` |  |
| `--fingerprint` | FINGERPRINT |  | pinned runner SPKI fingerprint (hex) |
| `--account` | ACCOUNT |  |  |
| `job_id` | JOB_ID |  |  |

#### `rayrf remote describe-schema`

Print the JobSpec / ExportSpec / telemetry contract (no connection)

---

## Scripting and specs

Section: CLI and automation. Canonical page: https://rayrf.com/docs/cli/scripting

A project is a declarative spec: a JSON or YAML document describing the domain, stackup and materials, shapes, variables, constraints, generators, and settings. `rayrf project build` turns a spec into a `.rfsim`, `rayrf project export-spec` turns an existing project back into a spec, and one JobSpec file can drive a whole run.

### The project-as-spec model

`rayrf schema --what spec` prints the authoritative JSON Schema for the format. Read it once, author against it. The spec has these top-level keys:

- `domain`: the editor rectangle (`x_mm`, `y_mm`, `w_mm`, `h_mm`, `auto_fit`). With `auto_fit` true (the default) the rectangle is derived from the geometry plus the project's auto-fit padding, both when the spec is built and again at run time, so `w_mm`/`h_mm` in the spec are ignored (a warning names them). Set `auto_fit` false to size the box yourself, and then `w_mm` and `h_mm` are required and used verbatim. The auto-fit padding round-trips through `pad_mode` (`percent` or `mm`), `pad_percent`, and `pad_mm`, so `export-spec` then `build` reproduces the same box. Unknown domain keys are rejected.
- `layers`: the stackup, each `conductor`, `dielectric`, or `air`, top to bottom. An `air` layer is a free-space spacer that sets the gap between conductors through its `thickness_mm` and emits no material entry, so it renders as air. `z_mm` is recomputed from stack order, so omit it. Unknown layer keys are rejected, so `eps_r` in place of `epsilon_r` is a clear error.
- `shapes`: geometry, each with a `kind` and a `layer`. See [CLI reference](#cli-reference) and `rayrf schema --what shapes|ports` for the per-kind parameters.
- `variables`, `bindings`, `constraints`, `generators`: the parametric model. A `bindings` entry maps a param path to an expression over variables. Path prefixes are `primitive:ID:param` (a poly vertex is `primitive:ID:points.INDEX.x`), `layer:NAME:field`, `domain:field`, `fdtd:field`, and `generator:ID:param`. A `domain:w_mm` or `domain:h_mm` binding takes effect only with `auto_fit` false. With `auto_fit` on the geometry fit replaces it, and a warning names it.
- `settings`: any `FDTDSettings` field, set verbatim. `rayrf schema --what settings` lists every field with its type, default, units, and description.

Only `layers` and `shapes` are required. Any setting not present takes the project default.

> Note: The GUI resolves the `cells` and `auto` air-margin modes to `air_*_mm` on collect, but the exporter reads only `air_around_mm`, `air_above_mm`, and `air_below_mm`. A headless run of a spec-built project reads whatever those mm values hold, so set them (or `air_mode=mm`) to control the margins.

`rayrf project build --spec FILE --out p.rfsim` writes the project. On a spec error or a failed resolve (an undefined variable, an unknown layer) it prints one line to stderr and exits 2.

`rayrf project export-spec --project FILE --out FILE` is the inverse: it writes the spec for an existing project, emitting the verbatim `params` form so a rebuild reproduces the project. Export, edit the text, rebuild.

### A worked example

A single rectangular patch over a ground plane on FR4, with one vertical feed port and one variable driving the patch width. Built with `rayrf project build` and checked with `rayrf validate`.

```json
{
  "schema": "rayrf-project-spec/v1",
  "domain": {"auto_fit": true},
  "layers": [
    {"name": "Top", "type": "conductor"},
    {"name": "Core", "type": "dielectric", "thickness_mm": 1.6, "epsilon_r": 4.3,
     "loss_tangent": 0.02},
    {"name": "Bottom", "type": "conductor"}
  ],
  "variables": [
    {"name": "patchW", "value": "16", "min": 12, "max": 20, "group": "Geometry"}
  ],
  "shapes": [
    {"kind": "rect", "layer": "Top", "name": "Patch",
     "x_mm": 0, "y_mm": 0, "w_mm": "patchW", "h_mm": 12.3},
    {"kind": "rect", "layer": "Core", "name": "Substrate",
     "x_mm": 0, "y_mm": 0, "w_mm": 22, "h_mm": 22},
    {"kind": "rect", "layer": "Bottom", "name": "Ground",
     "x_mm": 0, "y_mm": 0, "w_mm": 22, "h_mm": 22},
    {"kind": "port", "layer": "Top", "name": "Feed",
     "x_mm": 0, "y_mm": -4, "port_number": 1, "impedance_ohm": 50,
     "active": true, "bottom_layer": "Bottom", "top_layer": "Top"}
  ],
  "settings": {"freq_min_hz": 4.5e9, "freq_max_hz": 7.0e9, "bc_all": "PML"}
}
```

```
rayrf project build --spec patch.json --out patch.rfsim
rayrf validate --project patch.rfsim
```

A `rect` on a numeric field takes a plain number (`"h_mm": 12.3`) or, when the value is a non-numeric string (`"w_mm": "patchW"`), an expression binding on that field. The feed spans vertically from `bottom_layer` to `top_layer`, so the layer order matters: a port whose two conductors land on the same mesh cell injects nothing and `validate` reports it. The ground and substrate rects paint the `Bottom` and `Core` layers so the dielectric is real, not air.

### Inspecting a project

`rayrf project query --project FILE --json` prints a machine-readable readout: every shape (id, kind, layer, name, params), every variable (source value, resolved value, range, group, where-used count), constraints, generators, bindings, settings, the domain, a geometry readout (per-shape center, bounds, anchors, resolved boundary faces, domain clearance), and a last-results summary. Use it to inspect a project you did not build.

`rayrf project dump-settings --project FILE` prints every setting with its value, default, units, and description, grouped by category. Flags narrow the output:

- `--changed-only` lists only settings that differ from their default.
- `--category NAME` limits to one category (an unknown name exits 2).
- `--advanced true|false` filters by the advanced flag.
- `--json` emits a machine-readable list instead of the grouped text.

### Mutating a project

`rayrf project set --project FILE --var name=value [--out FILE]` sets one or more variables (repeatable `--var`), re-resolves bindings and constraints, and saves the project in place or to `--out`. A value may be a number or an expression over other variables. An unknown reference or a bad expression exits 2 with the field message and leaves the geometry unchanged.

Two run-time override forms change values for a single run without editing the project:

- `rayrf run --set name=value` overrides a variable, resolved before export.
- `rayrf run --setting name=value` sets any `FDTDSettings` field. It goes through the same value gate as the GUI: an unknown name, a bad type or enum, or a hard-bound violation exits 2 before the run starts, a soft-bound value prints one warning and runs with the value exactly as set. Both flags repeat.

> Note: A setting fixed in auto mode is derived and overwritten at run time. The run prints the discarded derived value so an override never loses silently.

### Variables and expressions

A numeric field accepts an expression over the project variables: the operators `+ - * / // % **`, parentheses, the trig and math functions, and the constants `pi` and `e`. Trig is in degrees. The full function list is in `rayrf schema --what spec` and `rayrf guide geometry`.

### JobSpec: one payload per run

`rayrf run --job FILE` reads a YAML or JSON JobSpec that carries the project, the overrides, the save behavior, the output directory, and the artifact selection. The job file overrides every other `run` flag, including which project runs, so a job is a single reproducible run request. An unknown key in the file is refused, so a typo cannot silently revert to a default.

```yaml
project: patch.rfsim
quality: medium
out_dir: _job_out
settings:
  freq_max_hz: 6.5e9
exports:
  sparams: all
  sparams_touchstone: true
  smith: true
```

The `settings` block passes through the same value gate as `--setting`. The `overrides` block sets advanced numeric overrides (cell sizes, air margins, the frequency band, and material fields). The dielectric permittivity key is `epsilon_r`, with `eps_r` accepted as an alias. Setting both to different values is an error, not a last-wins pick. The JobSpec fields:

| Field | Type | Default |
| --- | --- | --- |
| `project` | Path | `required` |
| `save_after_run` | bool | `false` |
| `save_as` | Optional[Path] | `none` |
| `quality` | Optional[str] | `none` |
| `quality_frac` | Optional[float] | `none` |
| `sim_mode` | Optional[str] | `none` |
| `mesh_style` | Optional[str] | `none` |
| `vram_budget_mb` | Optional[float] | `none` |
| `backend` | Optional[str] | `none` |
| `device_index` | Optional[int] | `none` |
| `overrides` | Dict[str, Any] | `factory` |
| `settings` | Dict[str, Any] | `factory` |
| `convergence` | Optional[bool] | `none` |
| `perf_mode` | bool | `false` |
| `max_steps` | Optional[int] | `none` |
| `sparam_stop` | bool | `false` |
| `sparam_stop_db` | float | `-50` |
| `sparam_stop_periods` | float | `2` |
| `sparam_stop_min_periods` | float | `8` |
| `sparam_stop_consec` | int | `3` |
| `force_disable_surface_currents` | bool | `false` |
| `force_disable_nf2ff` | bool | `false` |
| `force_disable_save_vtk` | bool | `false` |
| `force_skip_geometry_export` | bool | `false` |
| `out_dir` | Optional[Path] | `none` |
| `exports` | ExportSpec | `factory` |

The `exports` block selects what is written after the run:

| Field | Type | Default |
| --- | --- | --- |
| `sparams` | str | `all` |
| `sparams_touchstone` | bool | `false` |
| `smith` | bool | `false` |
| `polar` | bool | `false` |
| `polar_freq_mhz` | Optional[float] | `none` |
| `rad3d` | bool | `false` |
| `rad3d_freq_mhz` | Optional[float] | `none` |
| `rad3d_vtk` | bool | `false` |
| `rad_pattern_csv` | bool | `false` |
| `mesh_preview` | bool | `false` |
| `mesh_preview_vtk` | bool | `false` |
| `mesh_preview_size` | Optional[int] | `none` |
| `surface_currents` | bool | `false` |
| `surface_currents_freq_mhz` | Optional[float] | `none` |
| `no_3d_renders` | bool | `false` |

### The run manifest

Every run writes `run_manifest.json` into its output directory. It is the machine-readable record of the run, and a failed write is fatal so a missing manifest is never mistaken for success. `status` is one of:

- `ok`: the run completed.
- `preflight_refused`: a pre-flight gate refused before launch.
- `vram_overflow_predicted`: the `--vram-budget-mb` estimate exceeded the budget.
- `error`: a runtime failure.

The manifest records the resolved sim params (cell sizes, frequency band, boundaries, PML and air), the auto-derived mesh with its reasoning, performance (duration, steps, cells, GCell/s, VRAM, ringdown, convergence history), and the list of artifacts written. `overrides_requested` and `overrides_honored` are both present so a value overwritten by auto derivation is visible. The full contract is at `rayrf schema --what manifest`.

### Batch running

`rayrf run --folder DIR` runs every project in a folder in sequence, `--recursive` to descend into subfolders. One project that fails does not abandon the rest: the batch continues to the next project and exits 1 if any project failed, 0 if all succeeded. Each failure is also appended to `batch_failures.jsonl` in the working directory.

---

## Settings reference

Section: Reference. Canonical page: https://rayrf.com/docs/reference/settings

Every simulation setting lives in the project's FDTD settings block. This reference is generated from the same registry the app uses, grouped by category.

### Reading and changing settings

The same values reach the solver from four surfaces:

- The Simulate tab panels edit settings in the GUI. Advanced-mode settings appear when Advanced mode is on.
- `rayrf project dump-settings --project FILE` prints every setting with its value, default, units, and description, grouped by category. `--changed-only` shows only settings that differ from their default, `--category NAME` limits to one category, and `--json` emits a machine-readable list.
- `rayrf run --setting name=value` overrides a setting for one run (repeatable). It is value-gated by the same gate as the GUI. A hard bound out of range refuses with exit 2, a soft bound out of range prints one warning and runs the value exactly as set.
- `rayrf schema --what settings` prints the machine contract: every field with its type, default, units, enum, and category.

### Reading the table

Type and Default are the live values from the registry. A `ui-only` setting persists viewer or editor state and does not change the physics. An `advanced` setting surfaces in Advanced mode. A Caution note is the registry's warning about a common mistake for that setting.

### All settings

#### Frequency band

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `freq_min_hz` | number | `0` | Hz |  | Lower edge of the excitation band, in Hz. Also sets the reference wavelength for PML and air sizing. Caution: This field is in Hz; the CLI run flags and the GUI use MHz/GHz. A value of 3e9 is 3 GHz. |
| `freq_max_hz` | number | `0` | Hz |  | Upper edge of the excitation band, in Hz. Sets the required mesh resolution (cells per wavelength at f_max). Caution: Must be greater than freq_min_hz. |
| `cw_enabled` | boolean | `false` | bool |  | Inject a continuous sinusoid at cw_freq_hz instead of a Gaussian pulse. Advanced-mode only: export refuses an auto-mode project with CW enabled. Caution: Continuous wave never rings down, so the run stops only at max_steps, and S-parameters and radiation are not meaningful. Use only for field animations. |
| `cw_freq_hz` | number | `2.4e+09` | Hz |  | Continuous-wave excitation frequency, in Hz, used when cw_enabled is on. |
| `auto_freq_mode_centre` | boolean | `true` | bool | ui-only | UI input style for the auto-mode frequency band: centre plus bandwidth (True) or f_min / f_max (False). |

#### Mesh / cell size

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `dx_mm` | number | `0.2` | mm |  | FDTD cell size in X. Smaller cells raise accuracy, memory, and runtime. Caution: In advanced mode this is used exactly; in auto mode it is derived from quality and overwritten. |
| `dy_mm` | number | `0.2` | mm |  | FDTD cell size in Y. |
| `dz_mm` | number | `0.2` | mm |  | FDTD cell size in Z. Must resolve the substrate thickness (aim for several cells per dielectric layer). Caution: In advanced mode dz is not snapped; if it does not divide the stackup height the substrate can land between grid lines. |

#### Non-uniform mesh grading

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `mesh_grading_enabled` | boolean | `false` | bool |  | Use finer cells in the board region and coarser cells out toward the air and PML. |
| `mesh_grading_x_mult` | number | `1` | factor |  | Board-region refinement factor in X (1.0 means no refinement). |
| `mesh_grading_y_mult` | number | `1` | factor |  | Board-region refinement factor in Y. |
| `mesh_grading_z_mult` | number | `2` | factor |  | Board-region refinement factor in Z. Caution: Default 2.0 halves the Z cell near the board, roughly doubling Z cells there and raising memory. |
| `mesh_grading_margin_mm` | number | `1` | mm |  | How far the fine-cell region extends beyond the board in every direction. |
| `mesh_grading_x_max_ratio` | number | `1.1` | ratio |  | Maximum cell-to-cell size ratio in the X grading transition. At or below 1 no transition can satisfy the ratio and the size jump would violate it. |
| `mesh_grading_y_max_ratio` | number | `1.1` | ratio |  | Maximum cell-to-cell size ratio in the Y grading transition. At or below 1 no transition can satisfy the ratio and the size jump would violate it. |
| `mesh_grading_z_max_ratio` | number | `1.1` | ratio |  | Maximum cell-to-cell size ratio in the Z grading transition. At or below 1 no transition can satisfy the ratio and the size jump would violate it. |

#### Boundary conditions and PML

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `bc_all` | string | `PML` |  | enum: PML / PEC / PMC / Custom | Boundary preset applied to all six faces, or Custom to set each face individually. Caution: PEC and PMC are hard walls with no absorber. Give every PEC/PMC face an explicit per-face spacing, otherwise it inherits the air margin sized for PML and the geometry can sit on the wall. |
| `pml_thickness_mm` | number | `4` | mm |  | Physical thickness of the PML absorber, the authoritative depth when pml_mode is manual. In the cells and auto modes the export derives the depth (pml_cells_manual x finest cell, or auto_pml_factor x wavelength) on every path and this field is display state. The PML is added outside the air margin (it extends the domain), so it never overlaps the geometry; thickness only increases total cell count. |
| `pml_cells` | integer | `0` | cells |  | Resolved PML cell count. 0 means derive it from pml_thickness_mm at export; a positive value is a per-face floor, and the axis count is raised to ceil(pml_thickness_mm / cell) when the thickness needs more. |
| `auto_pml_enabled` | boolean | `true` | bool |  | Compute pml_thickness_mm automatically from the wavelength at f_min. |
| `auto_pml_factor` | number | `0.1` | fraction |  | PML thickness as a fraction of the wavelength at f_min when auto PML is on (0.10 means lambda/10). The realized depth is floored at the CFS-CPML cell minimum with a printed line. |
| `pml_mode` | string | `cells` |  | enum: cells / auto / manual | How PML depth is specified: a cell count (pml_cells_manual), auto-sized from the wavelength at f_min, or the explicit pml_thickness_mm. |
| `pml_cells_manual` | integer | `8` | cells |  | PML absorber depth in cells per face when pml_mode is cells; the export pins every face to exactly this count on every path. A face declared PML needs at least one absorber cell to be one. |
| `bc_x_lo` | string | `PML` |  | enum: PML / PEC / PMC | Boundary type on the -X face when bc_all is Custom. |
| `bc_x_hi` | string | `PML` |  | enum: PML / PEC / PMC | Boundary type on the +X face when bc_all is Custom. |
| `bc_y_lo` | string | `PML` |  | enum: PML / PEC / PMC | Boundary type on the -Y face when bc_all is Custom. |
| `bc_y_hi` | string | `PML` |  | enum: PML / PEC / PMC | Boundary type on the +Y face when bc_all is Custom. |
| `bc_z_lo` | string | `PML` |  | enum: PML / PEC / PMC | Boundary type on the -Z (bottom) face when bc_all is Custom. |
| `bc_z_hi` | string | `PML` |  | enum: PML / PEC / PMC | Boundary type on the +Z (top) face when bc_all is Custom. |
| `bc_x_lo_spacing_enabled` | boolean | `false` | bool |  | Use a fixed -X gap (bc_x_lo_spacing_mm) instead of the auto/global air margin. |
| `bc_x_hi_spacing_enabled` | boolean | `false` | bool |  | Use a fixed +X gap (bc_x_hi_spacing_mm) instead of the auto/global air margin. |
| `bc_y_lo_spacing_enabled` | boolean | `false` | bool |  | Use a fixed -Y gap (bc_y_lo_spacing_mm) instead of the auto/global air margin. |
| `bc_y_hi_spacing_enabled` | boolean | `false` | bool |  | Use a fixed +Y gap (bc_y_hi_spacing_mm) instead of the auto/global air margin. |
| `bc_z_lo_spacing_enabled` | boolean | `false` | bool |  | Use a fixed -Z gap (bc_z_lo_spacing_mm) instead of the auto/global air margin. |
| `bc_z_hi_spacing_enabled` | boolean | `false` | bool |  | Use a fixed +Z gap (bc_z_hi_spacing_mm) instead of the auto/global air margin. |
| `bc_x_lo_spacing_mm` | number | `0` | mm |  | Fixed gap from the geometry to the -X boundary when its spacing override is enabled. |
| `bc_x_hi_spacing_mm` | number | `0` | mm |  | Fixed gap from the geometry to the +X boundary when its spacing override is enabled. |
| `bc_y_lo_spacing_mm` | number | `0` | mm |  | Fixed gap from the geometry to the -Y boundary when its spacing override is enabled. |
| `bc_y_hi_spacing_mm` | number | `0` | mm |  | Fixed gap from the geometry to the +Y boundary when its spacing override is enabled. |
| `bc_z_lo_spacing_mm` | number | `0` | mm |  | Fixed gap from the geometry to the -Z boundary when its spacing override is enabled. A Custom PEC/PMC face requires it (0 puts the wall flush, e.g. an infinite ground plane); PML faces fall back to the derived margin. |
| `bc_z_hi_spacing_mm` | number | `0` | mm |  | Fixed gap from the geometry to the +Z boundary when its spacing override is enabled. A Custom PEC/PMC face requires it; PML faces fall back to the derived margin. |

#### Air margins / domain

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `air_around_mm` | number | `10` | mm |  | Air gap in the XY plane from the meshed board edge to the side boundaries; a per-face spacing overrides it on that face. |
| `air_above_mm` | number | `10` | mm |  | Air gap above the topmost stackup plane to the upper boundary; a per-face top spacing overrides it. |
| `air_below_mm` | number | `10` | mm |  | Air gap below the bottom stackup plane to the lower boundary; a per-face bottom spacing overrides it. |
| `air_mode` | string | `cells` |  | enum: cells / mm / auto | How the advanced air margin is specified: a per-face cell count, the explicit air_*_mm values, or auto-sized from the wavelength at f_min. Caution: Every mode is resolved at export on every path: cells from air_cells x the face's own cell size, auto from auto_air_*_factor x the wavelength at f_min. In those two modes the air_*_mm fields are display state; only air_mode=mm reads them. |
| `air_cells` | integer | `6` | cells |  | Air cells on every face when air_mode is cells. The export resolves the count against the face's own cell size (the coarser in-plane cell laterally, dz above and below). |
| `auto_air_enabled` | boolean | `true` | bool |  | Size the air margins automatically from the wavelength at f_min instead of using the fixed air_*_mm values. |
| `auto_air_sides_factor` | number | `0.25` | fraction |  | Side air margin as a fraction of the wavelength at f_min when auto air is on. |
| `auto_air_above_factor` | number | `0.25` | fraction |  | Above air margin as a fraction of the wavelength at f_min when auto air is on. |
| `auto_air_below_factor` | number | `0.25` | fraction |  | Below air margin as a fraction of the wavelength at f_min when auto air is on. |

#### Run length and telemetry

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `typical_speed_gcells_per_s` | number | `10` | GCell/s | ui-only | Saved throughput estimate used only for the UI runtime prediction. |
| `max_steps` | integer | `1000000` | steps |  | Hard cap on backend timesteps. Auto mode derives it; the advanced Limit-max-steps override caps it on both the GUI and CLI run paths. Caution: If the cap is hit before ringdown, the S-parameter and radiation results are not physically converged. |
| `ringdown_end_crit` | number | `1e-05` | ratio |  | Energy ratio (relative to peak) at which the run stops. Smaller means a longer, more converged run. Auto mode derives it from quality; advanced mode honors the value as written. The floor is -120 dB: the control is logarithmic and zero has no dB representation. |
| `ringdown_check_steps` | integer | `250` | steps |  | How often, in steps, the ringdown energy criterion is evaluated. |
| `ringdown_min_steps` | integer | `0` | steps |  | Minimum steps before the ringdown criterion is allowed to stop the run. 0 (the default) resolves to the physics floor at export: source settle plus a few periods at f_min, at least 2000 steps. A positive value is a deliberate floor: auto mode lowers an oversized one to the physics floor, advanced mode honors it as written. |
| `sparam_delta_crit_db` | number | `-40` | dB |  | Threshold on the change in S-parameters between ringdown checks for the optional early stop (0 disables). |
| `sparam_delta_check_steps` | integer | `0` | steps | advanced | How often, in steps, the S-parameter early-stop delta is evaluated. 0 reuses ringdown_check_steps. |
| `sparam_delta_consecutive` | integer | `2` | count | advanced | Consecutive stable S-parameter checks required before the early stop fires. |
| `sparam_min_steps` | integer | `10000` | steps |  | Minimum steps before the S-parameter early stop is allowed to fire (a floor so it cannot stop during source excitation). |
| `sparam_early_stop` | boolean | `false` | bool | advanced | Stop early once the S-parameters stop changing by sparam_delta_crit_db across consecutive checks. |
| `telemetry_interval` | integer | `25` | steps | ui-only | Emit step telemetry every N steps. Lower values update the live charts more often at some compute cost. |

#### Mesh convergence study

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `auto_mesh_enabled` | boolean | `false` | bool | advanced | Advanced-mode toggle for the mesh convergence study. |
| `auto_convergence_enabled` | boolean | `false` | bool |  | Run a multi-pass mesh convergence study, refining until the tracked resonances stabilise. |
| `conv_source` | string | `S11` |  | enum: S11 / S21, advanced | Which S-parameter the convergence study tracks. |
| `conv_num_resonances` | integer | `1` | count | advanced | Number of deepest resonance dips matched between convergence passes. |
| `conv_freq_threshold_db` | number | `-20` | dB | advanced | Allowed per-resonance frequency shift between passes, as 10*log10(df/f) (-20 dB is about 1 percent). |
| `conv_depth_threshold_db` | number | `-20` | dB | advanced | Allowed per-resonance depth change between passes, in dB of the linear-magnitude difference. |
| `conv_mesh_refine_factor` | number | `1.26` | factor | advanced | Mesh division factor applied each convergence pass. Caution: 1.26 is about cbrt(2), roughly doubling cell count per pass; larger values explode the cell count. Below 1 the step would coarsen instead of refine. |
| `conv_max_passes` | integer | `8` | count | advanced | Maximum number of convergence passes before stopping. |

#### Radiation (NF2FF)

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `enable_nf2ff` | boolean | `true` | bool |  | Compute the 3D radiation pattern, gain, and directivity via a near-field to far-field transform. |
| `skip_geometry_export` | boolean | `false` | bool |  | Skip writing geometry.vtk / geometry.vdb when a radiation or 3D-field run would normally request it. |
| `nf2ff_num_freqs` | integer | `51` | count |  | Number of frequency points for the radiation-pattern output. The solver's NF2FF buffers hold 512 points, a fixed backend contract. |

#### Surface currents

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `enable_surface_currents` | boolean | `false` | bool |  | Record surface current density on a conductor layer (time-domain frames plus a frequency-domain DFT). |
| `surface_current_layer` | string | `` |  |  | Conductor layer name to capture surface currents on. |
| `surface_current_interval_auto` | boolean | `true` | bool |  | Pick the time-domain capture stride automatically for 10 samples per period at f_max. Caution: When on, this overrides the manual surface_current_interval. |
| `surface_current_interval` | integer | `50` | steps |  | Save a time-domain surface-current frame every N steps when the auto stride is off. |
| `surface_current_num_freqs` | integer | `63` | count |  | Number of frequency points for the surface-current DFT. The solver's DFT buffers hold 512 points, a fixed backend contract. |
| `surface_current_view_domain` | string | `frequency` |  | enum: frequency / time, ui-only | Saved viewer preference for surface currents: frequency-domain or time-domain. |

#### 3D field export (VTK / VDB / FFT)

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `save_vtk` | boolean | `false` | bool |  | Write full 3D field frames during the run. Caution: Produces very large files and is disk-bound; enable only when you need full-volume fields. |
| `export_e_mag` | boolean | `true` | bool |  | Write the E-field magnitude \|E\| (V/m) as a frame per step when field export is on. The default field-export content. Caution: Turn it off only to export H or energy alone; with all three field types off there is nothing to write. |
| `export_h_mag` | boolean | `false` | bool |  | Also write the H-field magnitude \|H\| (A/m) as a frame per step. H is time-centered to the E level before the magnitude. |
| `export_energy` | boolean | `false` | bool |  | Also write the total field energy density U (J/m^3) as a frame per step, combining the aligned E and H at each cell. |
| `vtk_dump_interval` | integer | `50` | steps |  | Write a field frame every N steps when field export is on and the auto stride is off. |
| `vtk_dump_interval_auto` | boolean | `false` | bool |  | Floor the field-export stride to the Nyquist-safe value derived from f_max. Caution: When on, this overrides the manual vtk_dump_interval. |
| `field_export_range_enabled` | boolean | `false` | bool |  | Limit field frames to a step window; the solver still runs every step but skips writes outside it. Caution: Off by default, so frames are written at the export stride for the entire run when save_vtk is on. |
| `field_export_range_start` | integer | `0` | steps |  | First step (inclusive) written when the field-export range is enabled. |
| `field_export_range_end` | integer | `10000` | steps |  | Last step (inclusive) written when the field-export range is enabled. |
| `export_vdb` | boolean | `false` | bool |  | Write OpenVDB volumes instead of VTK for field export (much smaller, imports into Blender/EmberGen/Houdini). |
| `vdb_threshold_frac` | number | `0.001` | fraction |  | VDB sparsification cutoff as a fraction of peak \|E\|; voxels below it collapse to the pruned background (0 disables). Caution: Very small values keep more of the wave tail and enlarge files. |
| `field_fft_enabled` | boolean | `false` | bool |  | Accumulate a frequency-domain DFT of the field during the run and emit per-frequency slices. |
| `field_fft_num_freqs` | integer | `63` | count |  | Number of frequency bins for the full-field FFT (a frontend post-process; bins cost memory and time, not validity). |

#### Backend selection

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `use_cpu_backend` | boolean | `none` | bool |  | Run on the CPU (OpenMP) backend. None defers to the saved app preference; the GUI auto-enables this when no CUDA GPU is found. |

#### Mode and quality

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `sim_mode` | string | `auto` |  | enum: auto / advanced | Simulation mode: auto derives the mesh from quality, advanced uses the explicit mesh and boundary fields. |
| `quality_preset` | integer | `1` | level |  | Legacy integer quality level (0-3); kept in sync with auto_quality. |
| `auto_quality` | number | `1` | level |  | Continuous auto-mode quality from 0.0 to 3.0, interpolating cells per wavelength, PML, and convergence criteria. |
| `auto_min_feature_x_mm` | number | `0` | mm |  | Smallest feature the mesh resolves in X. Set it and the mesh refines X to resolve it; measured copper below it is reported as a warning, not silently meshed. 0 means no feature-size constraint on X, so measured copper only warns and the cell stays at the cells-per-wavelength and dielectric baseline. Caution: Per-axis and independent; setting one axis does not tighten the others, which can leave an anisotropic mesh. |
| `auto_min_feature_y_mm` | number | `0` | mm |  | Smallest feature the mesh resolves in Y. Set it and the mesh refines Y to resolve it; measured copper below it is reported as a warning, not silently meshed. 0 means no feature-size constraint on Y, so measured copper only warns and the cell stays at the cells-per-wavelength and dielectric baseline. |
| `auto_min_feature_z_mm` | number | `0` | mm |  | Smallest feature the mesh resolves in Z. A pure resolution floor: the mesh refines Z to resolve it. 0 means no feature-size constraint on Z, so the cell stays at the dielectric and cells-per-wavelength baseline. Stackup layers are always resolved and never excluded. |

#### Editor preferences

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `editor_grid_mm` | number | `1` | mm | ui-only | Editor snap-grid spacing. |
| `snap_enabled` | boolean | `true` | bool | ui-only | Whether the editor snaps to the grid. |

#### Result-viewer UI state

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `fv_field_domain` | string | `time` |  | enum: time / frequency, ui-only | Field viewer domain preference: time-domain animation or frequency slices. |
| `fv_viewer_mode` | string | `currents` |  | enum: fields / currents, ui-only | Field viewer active mode. |
| `fv_cmap` | string | `coolwarm` |  | ui-only | Field viewer colormap name. |
| `fv_log_scale` | boolean | `false` | bool | ui-only | Field viewer linear vs log scale (fields only). |
| `fv_field_scale_mode_idx` | integer | `2` |  | ui-only | Field viewer scale mode index (0 frame-based, 1 local, 2 global). |
| `fv_field_ref_frame` | integer | `0` |  | ui-only | Field viewer reference frame index for scaling. |
| `fv_show_geometry` | boolean | `true` | bool | ui-only | Field viewer geometry overlay visibility. |
| `fv_geom_opacity` | integer | `100` | percent | ui-only | Field viewer geometry overlay opacity in percent. |
| `fv_field_frame_idx` | integer | `0` |  | ui-only | Field viewer current frame index. |
| `fv_sc_freq_idx` | integer | `-1` |  | ui-only | Surface-current viewer frequency index (-1 defaults to centre). |
| `fv_sc_time_idx` | integer | `0` |  | ui-only | Surface-current viewer time-frame index. |
| `fv_sc_scale_mode_idx` | integer | `2` |  | ui-only | Surface-current viewer scale mode index (0 ref-based, 1 local, 2 global). |
| `fv_sc_ref_idx` | integer | `0` |  | ui-only | Surface-current viewer reference index for scaling. |
| `fv_sc_cmap` | string | `inferno` |  | ui-only | Surface-current viewer colormap name. |
| `fv_rad_freq_idx` | integer | `-1` |  | ui-only | Radiation tab frequency index (-1 defaults to centre). |
| `fv_rad_viewer_mode` | string | `polar` |  | enum: polar / 3d, ui-only | Radiation viewer mode. |
| `fv_rad_show_geometry` | boolean | `true` | bool | ui-only | Radiation viewer geometry overlay visibility. |
| `fv_rad_show_pattern` | boolean | `true` | bool | ui-only | Radiation viewer 3D pattern visibility. |
| `fv_rad_geom_opacity` | integer | `100` | percent | ui-only | Radiation viewer geometry opacity in percent. |
| `fv_rad_pattern_opacity` | integer | `85` | percent | ui-only | Radiation viewer pattern-surface opacity in percent. |
| `fv_rad_auto_position` | boolean | `true` | bool | ui-only | Radiation viewer auto-computes the pattern origin. |
| `fv_rad_auto_scale` | boolean | `true` | bool | ui-only | Radiation viewer auto-computes the pattern scale. |
| `fv_rad_pos_x_mm` | number | `0` | mm | ui-only | Radiation viewer manual pattern origin X. |
| `fv_rad_pos_y_mm` | number | `0` | mm | ui-only | Radiation viewer manual pattern origin Y. |
| `fv_rad_pos_z_mm` | number | `0` | mm | ui-only | Radiation viewer manual pattern origin Z. |
| `fv_rad_pattern_scale` | number | `1` | factor | ui-only | Radiation viewer manual pattern scale factor. |
| `fv_rad_split_view` | boolean | `true` | bool | ui-only | Radiation viewer shows polar and 3D side by side. |
| `fv_rad_split_sizes` | array | `[]` |  | ui-only | Radiation viewer splitter pixel widths. |
| `fv_rad_active_view` | string | `polar` |  | enum: polar / 3d, ui-only | Radiation viewer active sub-view when not split. |
| `fv_rad_camera_position` | array | `[]` |  | ui-only | Saved radiation viewer 3D camera (9 floats: position, focal point, up vector). |
| `fv_sparams_split_view` | boolean | `true` | bool | ui-only | S-parameter tab shows Smith and magnitude side by side. |
| `fv_sparams_active_view` | string | `magnitude` |  | enum: magnitude / smith, ui-only | S-parameter tab active sub-view when not split. |
| `fv_sparams_split_sizes` | array | `[]` |  | ui-only | S-parameter tab splitter pixel widths. |

#### Advanced-mode shadow values

| Setting | Type | Default | Units | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `adv_dx_mm` | number | `0.2` | mm | advanced, ui-only | Advanced-mode shadow value. Mirrors 'dx_mm': FDTD cell size in X. Smaller cells raise accuracy, memory, and runtime. |
| `adv_dy_mm` | number | `0.2` | mm | advanced, ui-only | Advanced-mode shadow value. Mirrors 'dy_mm': FDTD cell size in Y. |
| `adv_dz_mm` | number | `0.2` | mm | advanced, ui-only | Advanced-mode shadow value. Mirrors 'dz_mm': FDTD cell size in Z. Must resolve the substrate thickness (aim for several cells per dielectric layer). |
| `adv_air_around_mm` | number | `10` | mm | advanced, ui-only | Advanced-mode shadow value. Mirrors 'air_around_mm': Air gap in the XY plane from the meshed board edge to the side boundaries; a per-face spacing overrides it on that face. |
| `adv_air_above_mm` | number | `10` | mm | advanced, ui-only | Advanced-mode shadow value. Mirrors 'air_above_mm': Air gap above the topmost stackup plane to the upper boundary; a per-face top spacing overrides it. |
| `adv_air_below_mm` | number | `10` | mm | advanced, ui-only | Advanced-mode shadow value. Mirrors 'air_below_mm': Air gap below the bottom stackup plane to the lower boundary; a per-face bottom spacing overrides it. |
| `adv_auto_air_enabled` | boolean | `true` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'auto_air_enabled': Size the air margins automatically from the wavelength at f_min instead of using the fixed air_*_mm values. |
| `adv_auto_air_sides_factor` | number | `0.25` | fraction | advanced, ui-only | Advanced-mode shadow value. Mirrors 'auto_air_sides_factor': Side air margin as a fraction of the wavelength at f_min when auto air is on. |
| `adv_auto_air_above_factor` | number | `0.25` | fraction | advanced, ui-only | Advanced-mode shadow value. Mirrors 'auto_air_above_factor': Above air margin as a fraction of the wavelength at f_min when auto air is on. |
| `adv_auto_air_below_factor` | number | `0.25` | fraction | advanced, ui-only | Advanced-mode shadow value. Mirrors 'auto_air_below_factor': Below air margin as a fraction of the wavelength at f_min when auto air is on. |
| `adv_air_mode` | string | `cells` |  | advanced, ui-only | Advanced-mode shadow value. Mirrors 'air_mode': How the advanced air margin is specified: a per-face cell count, the explicit air_*_mm values, or auto-sized from the wavelength at f_min. |
| `adv_air_cells` | integer | `6` | cells | advanced, ui-only | Advanced-mode shadow value. Mirrors 'air_cells': Air cells on every face when air_mode is cells. The export resolves the count against the face's own cell size (the coarser in-plane cell laterally, dz above and below). |
| `adv_pml_thickness_mm` | number | `4` | mm | advanced, ui-only | Advanced-mode shadow value. Mirrors 'pml_thickness_mm': Physical thickness of the PML absorber, the authoritative depth when pml_mode is manual. In the cells and auto modes the export derives the depth (pml_cells_manual x finest cell, or auto_pml_factor x wavelength) on every path and this field is display state. The PML is added outside the air margin (it extends the domain), so it never overlaps the geometry; thickness only increases total cell count. |
| `adv_auto_pml_enabled` | boolean | `true` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'auto_pml_enabled': Compute pml_thickness_mm automatically from the wavelength at f_min. |
| `adv_auto_pml_factor` | number | `0.1` | fraction | advanced, ui-only | Advanced-mode shadow value. Mirrors 'auto_pml_factor': PML thickness as a fraction of the wavelength at f_min when auto PML is on (0.10 means lambda/10). The realized depth is floored at the CFS-CPML cell minimum with a printed line. |
| `adv_pml_mode` | string | `cells` |  | advanced, ui-only | Advanced-mode shadow value. Mirrors 'pml_mode': How PML depth is specified: a cell count (pml_cells_manual), auto-sized from the wavelength at f_min, or the explicit pml_thickness_mm. |
| `adv_pml_cells_manual` | integer | `8` | cells | advanced, ui-only | Advanced-mode shadow value. Mirrors 'pml_cells_manual': PML absorber depth in cells per face when pml_mode is cells; the export pins every face to exactly this count on every path. A face declared PML needs at least one absorber cell to be one. |
| `adv_max_steps` | integer | `1000000` | steps | advanced | Hard step cap applied when 'Limit max steps' is enabled; honored on the GUI and CLI run paths. |
| `adv_max_steps_enabled` | boolean | `false` |  | advanced | When set, the run stops at adv_max_steps; honored on the GUI and CLI run paths. |
| `adv_ringdown_end_crit` | number | `1e-05` | ratio | advanced, ui-only | Advanced-mode shadow value. Mirrors 'ringdown_end_crit': Energy ratio (relative to peak) at which the run stops. Smaller means a longer, more converged run. Auto mode derives it from quality; advanced mode honors the value as written. The floor is -120 dB: the control is logarithmic and zero has no dB representation. |
| `adv_nf2ff_num_freqs` | integer | `51` | count | advanced, ui-only | Advanced-mode shadow value. Mirrors 'nf2ff_num_freqs': Number of frequency points for the radiation-pattern output. The solver's NF2FF buffers hold 512 points, a fixed backend contract. |
| `adv_save_vtk` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'save_vtk': Write full 3D field frames during the run. |
| `adv_vtk_dump_interval` | integer | `50` | steps | advanced, ui-only | Advanced-mode shadow value. Mirrors 'vtk_dump_interval': Write a field frame every N steps when field export is on and the auto stride is off. |
| `adv_vtk_dump_interval_auto` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'vtk_dump_interval_auto': Floor the field-export stride to the Nyquist-safe value derived from f_max. |
| `adv_export_vdb` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'export_vdb': Write OpenVDB volumes instead of VTK for field export (much smaller, imports into Blender/EmberGen/Houdini). |
| `adv_vdb_threshold_frac` | number | `0.001` | fraction | advanced, ui-only | Advanced-mode shadow value. Mirrors 'vdb_threshold_frac': VDB sparsification cutoff as a fraction of peak \|E\|; voxels below it collapse to the pruned background (0 disables). |
| `adv_field_fft_enabled` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'field_fft_enabled': Accumulate a frequency-domain DFT of the field during the run and emit per-frequency slices. |
| `adv_field_fft_num_freqs` | integer | `63` | count | advanced, ui-only | Advanced-mode shadow value. Mirrors 'field_fft_num_freqs': Number of frequency bins for the full-field FFT (a frontend post-process; bins cost memory and time, not validity). |
| `adv_field_export_range_enabled` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'field_export_range_enabled': Limit field frames to a step window; the solver still runs every step but skips writes outside it. |
| `adv_field_export_range_start` | integer | `0` | steps | advanced, ui-only | Advanced-mode shadow value. Mirrors 'field_export_range_start': First step (inclusive) written when the field-export range is enabled. |
| `adv_field_export_range_end` | integer | `10000` | steps | advanced, ui-only | Advanced-mode shadow value. Mirrors 'field_export_range_end': Last step (inclusive) written when the field-export range is enabled. |
| `adv_auto_mesh_enabled` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'auto_mesh_enabled': Advanced-mode toggle for the mesh convergence study. |
| `adv_conv_source` | string | `S11` |  | advanced, ui-only | Advanced-mode shadow value. Mirrors 'conv_source': Which S-parameter the convergence study tracks. |
| `adv_conv_num_resonances` | integer | `1` | count | advanced, ui-only | Advanced-mode shadow value. Mirrors 'conv_num_resonances': Number of deepest resonance dips matched between convergence passes. |
| `adv_conv_freq_threshold_db` | number | `-20` | dB | advanced, ui-only | Advanced-mode shadow value. Mirrors 'conv_freq_threshold_db': Allowed per-resonance frequency shift between passes, as 10*log10(df/f) (-20 dB is about 1 percent). |
| `adv_conv_depth_threshold_db` | number | `-20` | dB | advanced, ui-only | Advanced-mode shadow value. Mirrors 'conv_depth_threshold_db': Allowed per-resonance depth change between passes, in dB of the linear-magnitude difference. |
| `adv_conv_mesh_refine_factor` | number | `1.26` | factor | advanced, ui-only | Advanced-mode shadow value. Mirrors 'conv_mesh_refine_factor': Mesh division factor applied each convergence pass. |
| `adv_conv_max_passes` | integer | `8` | count | advanced, ui-only | Advanced-mode shadow value. Mirrors 'conv_max_passes': Maximum number of convergence passes before stopping. |
| `adv_enable_surface_currents` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'enable_surface_currents': Record surface current density on a conductor layer (time-domain frames plus a frequency-domain DFT). |
| `adv_surface_current_layer` | string | `` |  | advanced, ui-only | Advanced-mode shadow value. Mirrors 'surface_current_layer': Conductor layer name to capture surface currents on. |
| `adv_surface_current_interval_auto` | boolean | `true` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'surface_current_interval_auto': Pick the time-domain capture stride automatically for 10 samples per period at f_max. |
| `adv_surface_current_interval` | integer | `50` | steps | advanced, ui-only | Advanced-mode shadow value. Mirrors 'surface_current_interval': Save a time-domain surface-current frame every N steps when the auto stride is off. |
| `adv_surface_current_num_freqs` | integer | `63` | count | advanced, ui-only | Advanced-mode shadow value. Mirrors 'surface_current_num_freqs': Number of frequency points for the surface-current DFT. The solver's DFT buffers hold 512 points, a fixed backend contract. |
| `adv_mesh_grading_enabled` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'mesh_grading_enabled': Use finer cells in the board region and coarser cells out toward the air and PML. |
| `adv_mesh_grading_x_mult` | number | `1` | factor | advanced, ui-only | Advanced-mode shadow value. Mirrors 'mesh_grading_x_mult': Board-region refinement factor in X (1.0 means no refinement). |
| `adv_mesh_grading_y_mult` | number | `1` | factor | advanced, ui-only | Advanced-mode shadow value. Mirrors 'mesh_grading_y_mult': Board-region refinement factor in Y. |
| `adv_mesh_grading_z_mult` | number | `2` | factor | advanced, ui-only | Advanced-mode shadow value. Mirrors 'mesh_grading_z_mult': Board-region refinement factor in Z. |
| `adv_mesh_grading_margin_mm` | number | `1` | mm | advanced, ui-only | Advanced-mode shadow value. Mirrors 'mesh_grading_margin_mm': How far the fine-cell region extends beyond the board in every direction. |
| `adv_mesh_grading_x_max_ratio` | number | `1.1` | ratio | advanced, ui-only | Advanced-mode shadow value. Mirrors 'mesh_grading_x_max_ratio': Maximum cell-to-cell size ratio in the X grading transition. At or below 1 no transition can satisfy the ratio and the size jump would violate it. |
| `adv_mesh_grading_y_max_ratio` | number | `1.1` | ratio | advanced, ui-only | Advanced-mode shadow value. Mirrors 'mesh_grading_y_max_ratio': Maximum cell-to-cell size ratio in the Y grading transition. At or below 1 no transition can satisfy the ratio and the size jump would violate it. |
| `adv_mesh_grading_z_max_ratio` | number | `1.1` | ratio | advanced, ui-only | Advanced-mode shadow value. Mirrors 'mesh_grading_z_max_ratio': Maximum cell-to-cell size ratio in the Z grading transition. At or below 1 no transition can satisfy the ratio and the size jump would violate it. |
| `adv_cw_enabled` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'cw_enabled': Inject a continuous sinusoid at cw_freq_hz instead of a Gaussian pulse. Advanced-mode only: export refuses an auto-mode project with CW enabled. |
| `adv_cw_freq_hz` | number | `2.4e+09` | Hz | advanced, ui-only | Advanced-mode shadow value. Mirrors 'cw_freq_hz': Continuous-wave excitation frequency, in Hz, used when cw_enabled is on. |
| `adv_sparam_early_stop` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'sparam_early_stop': Stop early once the S-parameters stop changing by sparam_delta_crit_db across consecutive checks. |
| `adv_sparam_delta_crit_db` | number | `-40` | dB | advanced, ui-only | Advanced-mode shadow value. Mirrors 'sparam_delta_crit_db': Threshold on the change in S-parameters between ringdown checks for the optional early stop (0 disables). |
| `adv_sparam_min_steps` | integer | `10000` | steps | advanced, ui-only | Advanced-mode shadow value. Mirrors 'sparam_min_steps': Minimum steps before the S-parameter early stop is allowed to fire (a floor so it cannot stop during source excitation). |
| `adv_enable_nf2ff` | boolean | `true` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'enable_nf2ff': Compute the 3D radiation pattern, gain, and directivity via a near-field to far-field transform. |
| `adv_skip_geometry_export` | boolean | `false` | bool | advanced, ui-only | Advanced-mode shadow value. Mirrors 'skip_geometry_export': Skip writing geometry.vtk / geometry.vdb when a radiation or 3D-field run would normally request it. |

---

## RF calculators

Section: Reference. Canonical page: https://rayrf.com/docs/reference/calculators

Closed-form transmission-line and antenna calculators for sizing a starting geometry before a full FDTD run. They live in a floating overlay in the Edit tab, and the same math is available headless through `rayrf calc`.

### The overlay

Open the overlay with `Ctrl+R` on the Edit tab, or with the **RF Calculators (Ctrl+R)** button in the Stackup section header. Both toggle the same floating panel.

![RF calculators overlay open on the editor](https://rayrf.com/docs/calculators-overlay.png)

The panel floats over the canvas. Drag it by its title bar, and resize it from any edge or corner. A grid of buttons across the top selects the calculator, and the body shows that calculator's inputs, its solved outputs, and a live cross-section preview that redraws as you type. When a value leaves a formula's validity window, an amber warning appears under the form stating the bound and what to change. The calculators are read-only analytic tools, so they stay available when a project is open read-only.

Where a calculator has both an impedance and a width, the two solve for each other. Edit Z0 and it sizes the width, edit the width and it reports Z0. The solved-for field carries a violet highlight.

### Pull from stackup

Each calculator that takes a substrate has a **Pull from stackup** checkbox. Tick it, pick a **Top** and a **Ref** conductor layer, and the calculator fills the substrate height and epsilon_r from the active project between those layers, then locks those two fields. When more than one dielectric slab sits in the gap, the epsilon_r is a thickness-weighted average and the detail line lists the blend. Untick to type the substrate values in directly. See [Stackup and layers](#stackup-and-materials) for how layers are defined.

### Calculators

Eleven calculators ship, in this order. Each solves the closed form named in its walkthrough. Wavelengths are reported as free-space lambda0 and guided lambda_g.

| Calculator | Inputs | Returns |
| --- | --- | --- |
| Microstrip | h, epsilon_r, and Z0 or W | the other of Z0/W, epsilon_eff, wavelengths |
| GCPW | gap S, h, epsilon_r, and Z0 or W | the other of Z0/W, epsilon_eff |
| Stripline | plate spacing b, epsilon_r, copper t, and Z0 or W | the other of Z0/W (IPC-2141A) |
| Patch antenna (inset feed) | frequency, h, epsilon_r, feed target Z0 | patch W and L, inset depth, feed line width |
| lambda Calc (guided wavelength) | frequency, and in PCB mode h, epsilon_r, trace W | lambda0, guided lambda_eff, lambda/4 |
| CPW | gap S, h, epsilon_r, and Z0 or W | the other of Z0/W, epsilon_eff (no bottom ground) |
| Differential pair | h, epsilon_r, spacing S, frequency, and Zdiff or W | the other, plus Z0 even/odd, Zcommon |
| Embedded MS | W, h, epsilon_r, cover height h2, cover epsilon_r, frequency | Z0, epsilon_eff, wavelengths |
| lambda0 free (free-space wavelength) | frequency | lambda0, half, quarter |
| lambda/4 xfmr (quarter-wave transformer) | Z1, Z2, frequency, h, epsilon_r | Zt = sqrt(Z1 Z2), length lambda_g/4, and microstrip widths W1, Wt, W2 |
| Smith chart | R, X, system Z0 | Gamma magnitude and phase, VSWR, return loss, mismatch loss |

The patch calculator sizes a rectangular patch and its inset feed. It reports the inset depth that transforms the radiating-edge resistance to the target impedance, and the width of the microstrip feed line at that impedance. When the target exceeds the edge resistance no inset matches it, and the calculator says so.

### The expression strip

A single expression line sits at the bottom of the panel. Type arithmetic (`+ - * / **` and parentheses, numbers only) and press Enter to evaluate. The two buttons take the square root and cube root of the current result, so `sqrt(Z1*Z2)` is entered as `50*75` then the root button. A rejected expression shows `err`.

### Step-by-step breakdown

Each calculator footer links to **Step-by-step breakdown on RayRF**, which opens the matching walkthrough at rayrf.com/calculators for that structure with the formula, its derivation, and the accuracy band of the model.

### CLI equivalent

`rayrf calc <kind>` runs the same formulas headless and prints a table, or `--json` for a machine-readable report. The kinds are `microstrip`, `stripline`, `cpw`, `gcpw`, `diff`, `embedded`, `patch`, `lambda`, `lambda-diel`, `qwt`, and `smith`. Inputs are given in GHz, mm, and ohms. Omit `--w` and pass `--z0` to solve a width from a target impedance.

```
rayrf calc patch --er 4.4 --h 1.6 --freq 2.45
rayrf calc qwt --z1 50 --z2 75 --freq 2.4 --er 3.66 --h 0.5
```

The full flag list for `calc` is on the [CLI reference](#cli-reference).

---

## Keyboard shortcuts

Section: Reference. Canonical page: https://rayrf.com/docs/reference/keyboard-shortcuts

Every keyboard shortcut in RayRF, grouped by area. Drawing, constraint, and canvas keys act on the Edit tab only, and every single-letter key is ignored while a text field or spin box has focus so typing a value never arms a tool.

### Application

These work from any tab.

| Key | Action |
| --- | --- |
| `Ctrl+N` | New project |
| `Ctrl+O` | Open project |
| `Ctrl+S` | Save |
| `Ctrl+Shift+S` | Save As |
| `Ctrl+Z` | Undo |
| `Ctrl+Y` | Redo |
| `Ctrl+Shift+Z` | Redo |

### Drawing tools

Each key selects the matching tool in the Edit tab toolbar. The Arc, More Ports, Import PNG, and Script Shape tools have no key and are reached from the toolbar.

| Key | Tool |
| --- | --- |
| `Esc` | Select (also cancels the active tool) |
| `R` | Rectangle |
| `C` | Circle |
| `P` | Polygon |
| `F` | Via |
| `Q` | Point port |
| `A` | Rectangular port |
| `M` | Ruler |

### Constraint and dimension tools

Each key arms a pick tool that then waits for you to click the anchors, edges, or rims it needs.

| Key | Tool |
| --- | --- |
| `D` | Dimension |
| `I` | Coincident |
| `H` | Horizontal |
| `V` | Vertical |
| `T` | Tangent |
| `N` | Angle |
| `K` | Fix |
| `E` | Equal |

### Editing the selection

These act on the current selection in the Edit tab canvas. Cut and paste are blocked in viewer mode, copy stays available.

| Key | Action |
| --- | --- |
| `Ctrl+A` | Select all shapes |
| `Delete` | Delete the selection |
| `Backspace` | Delete the selection |
| `Space` | Rotate the selection 90 deg clockwise |
| `X` | Mirror the selection horizontally |
| `Y` | Mirror the selection vertically |
| `Ctrl+C` | Copy |
| `Ctrl+X` | Cut |
| `Ctrl+V` | Paste |

While placing a port, `Space` or `R` rotates the port ghost 90 deg instead of rotating a selection.

### Canvas, grid, and layers

| Key | Action |
| --- | --- |
| `S` | Toggle snap to grid |
| `Shift+S` | Cycle the view mode |
| `-` | Shrink the grid spacing one step |
| `=` | Grow the grid spacing one step |
| `+` | Grow the grid spacing one step |
| `1` | Select layer 1 (through `9` for layer 9) |

### RF calculators

| Key | Action |
| --- | --- |
| `Ctrl+R` | Toggle the [RF calculators](#rf-calculators) overlay |

### S-parameter markers

These work on the [S-parameters](#s-parameters-and-smith-chart) plot.

| Key | Action |
| --- | --- |
| `M` | Toggle marker placement |
| `Esc` | Cancel marker placement |
| `Delete` | Delete the selected marker |
| `Backspace` | Delete the selected marker |

---

## File formats and outputs

Section: Reference. Canonical page: https://rayrf.com/docs/reference/file-formats

Every file RayRF reads or writes: the project document, the exported solver case, and the artifacts a run produces. Exact filenames matter, so this page lists them.

### Project files

A project is one JSON document with the `.rfsim` extension. It holds the whole project: the layer stackup, primitives, domain, FDTD settings, parametric variables and constraints and generators, embedded conductor bitmaps, imported reference traces, the editor thumbnail, saved view state, and (unless externalized) the last run's simulation results.

The document carries a `schema_version`, currently 8. A file written by a newer build refuses to open and reports the file and build versions rather than silently dropping the fields it does not understand. Legacy `.emflux` project files still open.

### Results sidecar

A project can keep its heavy run results out of the `.rfsim` in a sidecar file next to it with the same base name and the `.rfsimout` extension, so `Patch.rfsim` pairs with `Patch.rfsimout`. The point is version control: commit the `.rfsim`, gitignore the `.rfsimout`. Only the simulation results and imported reference traces move to the sidecar. Geometry, settings, and plot settings stay in the `.rfsim`.

Externalization is set two ways:

- Per project, from the "Separate geometry and settings from results" option in Project Settings. The choice is saved in the `.rfsim` and is the default for every save.
- Per run, from the CLI: `rayrf run --externalize-results project|run|off`. `off` keeps results inline, the others write the sidecar.

If the sidecar cannot be written, the save falls back to inline results, so a save never loses data. On open, a sidecar that is missing, corrupt, from a newer build, or renamed from a different project is skipped and the project still opens.

### Exported solver case

Before a run, the project is serialized into a case directory under `_sim_export/<case>/`. This is the solver input, and it is what a remote submit uploads. It contains `files.json` (the case manifest, schema version 3), one PNG bitmask raster per conductor and dielectric layer plus via and area-port masks, and `_license.token`, the signed machine-bound token the backend checks before it runs. The internal `files.json` fields are not part of the documented interface and have no `rayrf schema` target.

### Run outputs

A run writes to `<project_dir>/_sim_result/<case>/` by default, or to the directory given by `--out-dir`. The backend runs in that directory (in a `backend_run/` subdirectory when `--out-dir` is set) and writes its own raw files there. The post-processed artifacts, named after the case, are controlled by the CLI export flags. GUI runs and CLI runs place these files the same way.

The whole headless CLI, including every `run`, requires a Pro license.

Post-processed artifacts, in the output directory:

| Artifact | Filename | When |
| --- | --- | --- |
| S-parameter table | `<case>.csv` | Always |
| S-parameter plot | `<case>.png` | Always |
| Touchstone | `<case>.s1p` or `<case>.s2p` | `--export-touchstone`, see below |
| Smith chart | `<case>_smith.png` | `--export-smith` |
| Polar pattern | `<case>_polar.png` | `--export-polar`, with radiation data |
| Mesh preview | `<case>_mesh.png` | `--export-mesh` |
| Mesh preview grid | `<case>_mesh.vtk` | `--export-mesh-vtk` |
| 3D radiation | `<case>_rad3d.png` | `--export-rad3d`, with radiation data |
| 3D radiation surface | `<case>_rad3d.vtp` | `--export-rad3d-vtk` |
| Surface-current heatmap | `<case>_currents.png` | `--export-currents` |
| Radiation pattern table | `<case>_rad_pattern.csv` | `--export-rad-pattern` |
| Run manifest | `run_manifest.json` | Always |

The S-parameter table is one long-form row per trace and frequency. Its columns are `param,f_MHz,real,imag,mag,mag_dB`, so the frequency column is in MHz.

The radiation pattern table concatenates the per-frequency backend patterns into one flat CSV. Its columns are `freq_hz` followed by the backend pattern columns: `freq_hz,theta_deg,phi_deg,D_total_dBi,D_theta_dBi,D_phi_dBi`.

Raw files the backend writes into the run directory:

| Artifact | Filename | When |
| --- | --- | --- |
| Geometry | `geometry.vtk` or `geometry.vdb` | NF2FF or field export on |
| Port time series | `port_signals_raw.csv` | Always |
| Per-port voltage | `port_NN_voltage.csv` | Always |
| Per-port reflection | `port_NN_s11.csv` | Always |
| Radiation summary | `nf2ff_summary.csv` | NF2FF on |
| Radiation per frequency | `nf2ff_pattern_fNN.csv` | NF2FF on |
| Surface-current index | `jsurf_meta.json` | Surface currents on |
| Surface-current geometry | `jsurf_geometry.bin` | Surface currents on |
| Surface-current time frame | `jsurf_td_NNNN.bin` | Surface currents on |
| Surface-current frequency frame | `jsurf_fd_NN.bin` | Surface currents on |
| 3D field frame | `step_NNNN.vtk` or `step_NNNN.vdb` | `save_vtk` on |
| 3D field FFT slice | `step_fft_NNNN.vtk` | Full-field FFT on |
| Field frame index | `frames.json` | 3D field export on |

The `port_signals_raw.csv` columns are `t_s,V1,I1`. The raw port time series is also embedded in the project as compressed `.npz` blobs so a saved project can be reprocessed without the run directory.

#### Touchstone

Touchstone export writes measured values as measured and discloses any filled value in the file. A one-port run writes `.s1p`, a two-port run writes `.s2p`. A run with three or more ports is skipped with a printed message, since the headless exporter writes `.s1p` and `.s2p` only. For a two-port run, an unsimulated `S12` is filled from `S21` and an unsimulated `S22` from `S11`, and each fill is written into the file as a comment:

```
! S12 not simulated: filled with S21 (reciprocity assumption)
! S22 not simulated: filled with S11 (symmetry assumption)
```

#### OpenVDB field export

When `save_vtk` and `export_vdb` are both on, 3D field export writes OpenVDB volumes (`step_NNNN.vdb` and `geometry.vdb`) instead of VTK. `vdb_threshold_frac` sets a sparsification cutoff as a fraction of peak `|E|`: voxels below it collapse into the pruned background, and 0 disables pruning. The result imports into DCC tools that read OpenVDB volumes. VDB carries `|E|` only, so it cannot be combined with the H or energy field types.

Field export writes `|E|` by default. `export_h_mag` adds `|H|` and `export_energy` adds total energy density, one frame each per step, so a VTK run can write up to three volumes per frame. See [Field viewer](#field-viewer) for viewing frames.

### Imports

The editor and the S-Parameters tab read these files.

| Import | Where | File types |
| --- | --- | --- |
| Conductor mask | Edit tab | PNG (`*.png`) |
| Reference S-parameters | S-Parameters tab | Touchstone `.s1p` to `.s8p`, `.sNp` |
| Reference trace | S-Parameters tab | CSV (`*.csv`, `*.txt`) |

A PNG import scales the raster to a conductor pattern on a chosen layer. A Touchstone or CSV import becomes a dashed reference overlay stored in the project, so the source file can be deleted afterward. The `dxf` primitive holds an imported outline rasterized as a conductor pattern.

---

## FDTD tips

Section: Reference. Canonical page: https://rayrf.com/docs/reference/fdtd-tips

The mistakes that silently invalidate an FDTD run, and the judgment calls the rest of these docs leave out. Each tip states what the program does and why. The neutral feature pages describe the controls, this page tells you how to use them.

### Give every PEC and PMC face an explicit spacing

This is the one that costs the most and shows the least. In auto mode the air margin between the geometry and each boundary is sized for a PML absorber. A PEC or PMC face is a hard wall with no absorber, and if you leave it on that inherited margin the geometry can end up touching or sitting inside the wall, which reflects energy back into the design and corrupts the S-parameters. Set a real clearance on each hard-wall face. In the GUI, set the face to Custom, tick its spacing checkbox, and enter the gap in the spacing field. From the CLI, use the per-face flag on `run`:

```
rayrf run --project board.rfsim --per-face-bc z_lo=PEC:2.0
```

The `:MM` is a fixed gap from the geometry to that face, realized exactly and overriding the air margin there, and any per-face setting forces `bc_all` to Custom. A gap of `0` is legal and puts the wall flush, which is what you want for an infinite ground plane under the board. A Custom-mode case that reaches export with a hard-wall face and no spacing is refused with an error naming the face, so a missed gap stops the run rather than silently corrupting it. The PEC and PMC box presets keep the derived margins instead. See [Boundaries](#boundary-conditions).

### Watch the frequency units: Hz, GHz, and MHz

The band is stored one way, shown another, and overridden a third, so a factor of 1e6 slips through often. The project and spec store `freq_min_hz` and `freq_max_hz` in Hz. The GUI frequency fields show and accept GHz. The `run` and `reprocess` flags (`--freq-min`, `--freq-max`, `--fmin`, `--fmax`) take MHz. The same band is `5.8e9` in a spec, `5.8` in the GUI, and `5800` on a run flag. Get the exponent wrong and the whole band moves, along with the mesh resolution and the PML sizing that both key off it.

### A run is only valid if it rang down

Ring-down is the convergence test. The excitation is a pulse, and the S-parameters and radiation pattern are only trustworthy once the field energy in the box has decayed to `ringdown_end_crit` (an energy ratio relative to peak, smaller means a longer and more converged run). `max_steps` is a hard cap on backend timesteps. If the cap stops the run before ring-down, the results are not physically converged, and both the manifest (`ringdown_reached`) and the results badge in the UI say the run was cut short. If a run keeps hitting the cap, either raise `max_steps` or loosen `ringdown_end_crit` toward a larger ratio, and if you want a tighter result make the criterion smaller (auto mode derives it from quality, advanced mode uses the value as written). The criterion is evaluated every `ringdown_check_steps` timesteps (default 250). Each evaluation costs a full-domain energy pass and, on the CPU backend, bounds the solver's internal blocking. A 250-step cadence kept measured check overhead below 1% on both backends while limiting stop overshoot to 250 steps. The optional S-parameter early stop (`sparam_early_stop`) ends the run once |dS11| between consecutive ring-down checks holds below its threshold. It saves steps on a clean resonator but can stop a slowly settling structure a little early, so leave it off for a full-length reference run.

### CW mode has no S-parameters and no radiation

Continuous-wave excitation (`cw_enabled`) injects a steady sinusoid instead of a pulse. A steady drive never rings down, so the run has no convergence point and ends only at `max_steps`, with both early-stop paths disabled. It produces no meaningful S-parameters or radiation pattern. Use it only to record field animations for the [field viewer](#field-viewer). It is an advanced-mode feature, and an auto-mode project with CW on is refused rather than run as a pulse.

### Keep the mesh cells comparable across axes

Reciprocal structures rely on a mesh that is not lopsided. Setting one per-axis minimum feature and leaving the others at zero, or hand-setting `dx`, `dy`, and `dz` far apart in advanced mode, produces a strongly anisotropic mesh that can break reciprocal symmetry and skew the S-parameters, most visibly for planar ports. `rayrf estimate` names it: when the effective cell sizes differ by more than 3x it prints a "Mesh is strongly anisotropic" advisory. Keep the cells within roughly a factor of three of each other unless the anisotropy is deliberate. A minimum feature set on only some axes draws its own warning for the same reason.

### Paint every dielectric layer or make it air

A dielectric layer with nothing painted on it is treated as air and its epsilon is ignored. `validate` and `run` warn on this, since running the layer as air is occasionally intended. If the layer is a real substrate, paint a board-sized rectangle across it so it carries its permittivity. If the gap really is meant to be air, set the layer's type to air so the intent is explicit rather than inferred from an empty dielectric.

### In advanced mode, let dz divide the substrate

Auto mode snaps `dz` so the stackup planes land on grid lines. Advanced mode uses `dz` exactly, and if it does not divide the substrate height the conductor stackup can land between grid lines and shift the resonance. `validate` warns about a non-dividing `dz`. Either pick a `dz` that divides every dielectric thickness, or use [auto mode](#auto-mode-and-quality) and let it snap for you. When you set the cell sizes by hand, resolve the finest feature you care about and put several cells through the thinnest substrate, and keep `dy` near `dx` unless one direction really is finer, which the anisotropy tip above covers.

### Full-field export is heavy, so window it

`save_vtk` and `export_vdb` write a 3D field frame per captured step across the whole volume. That is large and disk-bound, and a long run fills a drive fast. Enable it only when you need the volume data. Narrow the capture with `field_export_range` to a step window, and on VDB raise `vdb_threshold_frac` to prune the weak wave tail (very small values keep more of the tail and enlarge the files). When you only need S-parameters, the run-line kill switches force capture off regardless of the saved settings: `--no-raw-fields`, `--no-nf2ff`, and `--no-surface-currents`.

### Validate before you wait for a full run

A full run can take a while, so catch the cheap mistakes first. In the GUI, the mesh preview on the Simulate tab draws the resolved grid over the geometry before any solve, so a feature that falls between cells or a boundary sitting on the structure shows up at a glance, and the Notices panel lists the same blocking errors and advisory warnings a run would raise. Headless, three commands do the same without a backend: `rayrf validate --project FILE` runs the blocking pre-flight gate plus the advisory checks above, `rayrf estimate --project FILE` derives the mesh and prints the cell count, VRAM, boundary sizing, and ring-down so you see the size before you commit to it, and `rayrf run --project FILE --dry-run` applies your overrides, validates, exports the case, and prints the estimate, then stops before launching the solver. See the [CLI reference](#cli-reference).

### Quality slider versus a convergence study

These solve two different problems. The quality slider (0.0 to 3.0) sets the resolution targets in one move, interpolating cells per wavelength, PML depth, and ring-down criterion between the presets, and raising it refines the whole mesh uniformly. A convergence study (`auto_convergence_enabled`) instead repeats the run over successive passes, refining the mesh each pass until the tracked resonances stop moving between passes, then reports whether it converged or hit the pass cap. Reach for a study when a result sits near a sharp resonance and you need evidence that it has stopped shifting with mesh density, which a single higher-quality run does not give you. It costs one backend pass per refinement, so spend the passes on a validation or a final number, not on routine iteration where a `medium` or `high` quality run is enough.
