Skip to content

Controls and Variables

santisoutoo edited this page Jul 24, 2026 · 1 revision

Controls and Variables

The core exposes state as named variables (LVARs). You write a subset of them — the controls — and read any of them. This page explains the two-sided discovery model and one behavior that surprises newcomers: nothing is seeded.

Two halves of discovery

Write side Read side
Curated list_controls() — the actuable cockpit controls, hand-written
Raw set(lvar, …) also accepts raw LVARs list_variables() / snapshot() — the full registry

Controls (list_controls)

The curated control catalog lives in core-rs/src/controls.rs as a hand-written table mapping a friendly name (bat_1, ext_pwr, bus_tie, apu_master, eng_mode, hyd_ptu…) to its underlying LVAR, plus metadata: kind (bool / enum / float), valid_values, a one-line description, a group (by system), and a domain.

set resolves both the friendly name and the raw LVAR — set("bat_1", 1) and set("OVHD_ELEC_BAT_1_PB_IS_AUTO", 1) are equivalent. The MCP tool schema, by contrast, offers only the friendly names (the curated half doing its job): an agent actuates controls a human curated and cannot reach an arbitrary variable.

In the CLI, controls prints the catalog. In the MCP server, list_controls returns it as dicts.

domain: cockpit vs world

Each control is cockpit or world:

  • cockpit — a real control a pilot actuates (a pushbutton, a knob).
  • world — outside state a real simulator would provide and that we fake here — e.g. ext_pwr_avail (whether a ground power unit is plugged in), fuel quantities, ambient conditions.

This distinction matters for the benchmark: a scenario should pre-fix its world state and not offer world controls to the agent, or it measures whether the agent guesses it can alter the outside world rather than whether it knows the procedure. See Scenarios and Design-Decisions (D-009).

Variables (list_variables / snapshot)

The raw registry is the source of truth for valid names: after the aircraft is built, it contains every variable the systems read/write plus the environment. get of an unknown name is an error that names the offender (not a silent 0.0) — discover names with vars <substr> (CLI) or snapshot(contains=…) (MCP). Useful prefixes: ELEC_AC, ELEC_DC, OVHD_ELEC, APU.

Range validation

Validation is layered and lives in the core:

  • If the control is in the catalog, set validates the value against its valid_values — a boolean rejects anything but 0/1, a range control rejects out-of-bounds — before writing.
  • If the name is an uncatalogued raw LVAR, only the weaker guard applies (must be finite and exist in the registry).

Errors surface as ApiError → Python exceptions (UnknownControlError, BadValueError, UnknownFailureError) → one-line CLI messages. See Architecture.

Nothing is seeded

FBW's test bed runs a seed() step that writes each element's programmed initial state (e.g. pushbuttons that start ON). That path is private in the vendored crate, and the project does not patch the vendor — so the persistent runtime starts without seed: every unwritten pushbutton reads its default (0.0 / OFF).

For cold & dark this is the correct state — but it has consequences you will hit:

  • The bus tie starts OFF, so external power or the APU generator won't feed the AC network until you set bus_tie auto. (See Quickstart.)
  • A few panel resting states whose real value is not 0 are seeded once at startup anyway, because their 0 would silently mean something wrong (GEN 1 LINE, the X-BLEED selector, pushback state). These are both seeded and cataloged as controls. See Design-Decisions (D-007, D-012, D-021).

If a subsystem ever depends on a pushbutton whose default-0 leaves it genuinely wrong, the fix is to write that pushbutton explicitly in the start profile — never to patch the vendor.

Clone this wiki locally