Skip to content

Architecture

santisoutoo edited this page Jul 24, 2026 · 1 revision

Architecture

One core, two frontends: the CLI and the MCP server are two windows onto the same control/observe API.

flowchart TB
    subgraph frontends["Frontends"]
        CLI["CLI REPL<br/>(human)"]
        MCP["MCP server<br/>(LLM agent)"]
        BENCH["Benchmark harness<br/>(a320-bench)"]
    end

    API["Control / Observe API<br/><code>set · get · step · run · read_ecam · inject_failure · snapshot</code>"]

    subgraph core["Sim core — Rust (via PyO3)"]
        HARNESS["Persistent harness + tick loop"]
        REG["Variable registry<br/>(inputs / outputs by name)"]
        FAIL["Failure injection<br/>(FBW FailureType)"]
        FBW["Vendored FlyByWire crates<br/><code>systems</code> + <code>a320_systems</code><br/>elec · hyd · pneu · fuel · APU · press"]
    end

    CLI --> API
    MCP --> API
    BENCH --> API
    API --> HARNESS
    HARNESS --> REG
    HARNESS --> FAIL
    REG --> FBW
    FAIL --> FBW
Loading

Reused vs built

The systems logic is reused untouched from FlyByWire; this repo builds everything around it. The analogy: FBW provides the engine block (the systems logic), and this project builds the chassis (headless harness), the dashboard (CLI/MCP), and wires the ignition (UpdateContext + variable registry).

Reused from FlyByWire All systems logic: hydraulic pressurization, electrical bus feeding, APU start sequence, warning-flag logic, failure models
Built here Native standalone build, persistent tick loop, variable registry, world-boundary inputs (UpdateContext), the homegrown ECAM rule engine and engine spool, the control/observe API, CLI, MCP server, scenario suite + scoring

Notably, there is no FWC in the vendored Rust (so the ECAM is a homegrown rule engine, see Failures-and-ECAM) and no engine model (so a first-order N2 spool is homegrown). Both are "ours" and tagged as such.

The layers, bottom to top

Layer Path What it is
Core core-rs/ Rust crate a320_sim_core: Sim API, runtime/tick loop, variable registry, controls/failures/ECAM catalogs, engine model, and the vendored FBW submodule (vendor/aircraft)
Bindings bindings/ PyO3 extension a320_sim (maturin), independent workspace, #[pyclass(unsendable)]
CLI cli/ a320_cli REPL (stdlib cmd + readline)
MCP mcp/ a320_mcp server (FastMCP, stdio)
Bench bench/ a320_bench harness (scenario loader, episode runner, recorder, scorer)
Scenarios scenarios/ benchmark dataset (YAML + JSON schema)

The API contract

The clean control/observe surface is Sim in core-rs/src/api.rs, mirrored 1:1 by the binding, CLI, and MCP:

Method Purpose
set(control, value) actuate a control (friendly name or raw LVAR); validates range
get(vars) -> map read variables (unknown name is an error, not a silent 0)
step(dt_ms) / run(seconds, rate) advance the simulation
set_environment(alt, ias, oat, qnh) set the UpdateContext (outside world)
snapshot() -> map full state dump
list_variables() / list_controls() / list_failures() discovery
inject_failure(id) / clear_failure(id) / active_failures() failures
read_ecam() -> [Warning] active warnings, worst-first
sim_time() the simulated clock

Errors are ApiError (UnknownControl, BadValue, UnknownFailure) → Python exceptions (SimError base, with UnknownControlError / BadValueError / UnknownFailureError). Only trivial FFI types cross the boundary; no panics cross. See Controls-and-Variables.

Key constraints

  • Compile native, never WASM. If the native build pulls msfs / *_wasm / wasm-bindgen, that's a decoupling bug (D-005). A cargo tree guard enforces it in CI.
  • The FBW vendor is pinned (13bce4b) and never patched — the "patches to vendored code" log is empty on purpose. Benchmark reproducibility depends on the pin.
  • GPLv3 is inherited from linking the FBW crates.
  • unsendable core — the aircraft uses Rc/RefCell, so Sim must be touched from a single thread; the MCP server's sync tools and blocking advance follow from this (D-010, D-015).

For the reasoning behind each of these, see Design-Decisions.

Clone this wiki locally