-
Notifications
You must be signed in to change notification settings - Fork 0
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
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.
| 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 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.
-
Compile native, never WASM. If the native build pulls
msfs/*_wasm/wasm-bindgen, that's a decoupling bug (D-005). Acargo treeguard 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.
-
unsendablecore — the aircraft usesRc/RefCell, soSimmust be touched from a single thread; the MCP server's sync tools and blockingadvancefollow from this (D-010, D-015).
For the reasoning behind each of these, see Design-Decisions.
A320 Systems Twin · GPLv3 · systems model by FlyByWire (vendored, pinned to 13bce4b) · repository
Get started
Operate
Benchmark
Reference