技术文档

M5c: FMS / CDU, plan and architecture decision

此页为项目的英文技术文档原文。 docs/M5C_PLAN.md

Status: 2026-11-05, build 44: Zibo's FMS (B738.a_fms, unmodified) runs in the systems module: CDU pages on the CDU screens, cockpit CDU keys; no navdata yet, no LNAV / VNAV (plan steps 3–4), no keyboard input yet.

Decision (owner, 2026-11-05)#

  1. Zibo's original FMS Lua runs inside the systems module (WASM), on PUC Lua 5.1, under an XLua host written in C/C++. No hand rewrite of the FMS: Zibo's 172,900-line B738.a_fms.lua (and the scripts it depends on) run unmodified, so every CDU page and edge case is Zibo's.
  2. Navdata: MSFS 2024's own (SimConnect facility data), no X-Plane navdata files. A native provider fills Zibo's navdata tables directly (see below).

Why (measurements)#

The M5c survey (docs/M5C_FMS_SURVEY.md) and the spike (tools/fms_spike/):

VM 40-leg route, per frame notes
LuaJIT interpreter (what X-Plane runs: every script starts with jit.off()) 2.2 ms survey, native
PUC Lua 5.1, native 6.6–6.9 ms survey; CDU pages equal to the oracle
fengari (Lua in JS) ~40 ms with 6 legs 17–20× slower than PUC; navdata parse 340 s: ruled out
PUC Lua 5.1 in WASM + Asyncify, XLua glue in Lua 9.8 ms spike, wasmtime
PUC Lua 5.1 in WASM + Asyncify, XLua glue in C 3.8 ms (0.9 ms without a route) spike; CDU output identical
  • About 80 % of a frame was XLua's own Lua glue (init.lua namespace and dataref wrappers), not Zibo's code; the C glue removes most of it. Asyncify costs about 10–25 %.
  • The spike ran the real FMS: RTE with origin / destination, a 40-waypoint route over 9 LEGS pages, PERF INIT executed, no Lua errors.
  • In the same module the AFDS (which reads 174 values the FMS writes) and the FMS share memory: no transport.

Lua errors without setjmp / exceptions#

The MSFS 2024 SDK's wasi-sysroot has no setjmp / longjmp and modules build with -fno-exceptions. Lua 5.1 has a single throw / catch pair (luaD_throw / luaD_rawrunprotected); with LUA_USE_ASYNCIFY a throw starts a Binaryen Asyncify unwind (never a rewind): every instrumented function returns at once up to luaD_rawrunprotected, which is not instrumented, stops the unwind and reports the error, as longjmp would (third_party/lua-5.1.5/README.md). After the link, wasm/tools/asyncify.py:

  • instruments only Lua's functions (onlylist from the Lua objects, wasm/tools/wasm_obj_funcs.py);
  • carries the SDK toolchain's StackSizesSection over with the function indices shifted by the removed intrinsic imports (checked by function name), keeps GuardPages;
  • Binaryen: binaryen@132.0.0 from npm (WASM_OPT overrides).

Checked: native tests (setjmp build) and wasmtime (Asyncify build): errors in Lua and C code, nested pcall, 1,000 errors in a loop, an unprotected error, the state usable afterwards. The shipped (SDK) module itself runs in wasm/tools/fake_msfs_host.py (wasmtime, every MSFS import provided), in CI too. In MSFS: the self-test (below) must pass before the FMS goes in.

Build 44: what runs#

  • XLua host (wasm/src/core/fms/xlua.h, xlua_glue.c, C): one dataref store shared by the script modules, XLua's API, init.lua unmodified with its hot functions in C, commands (begin / held / end, replace / wrap handlers), timers, callouts; every call into Lua protected (an error is logged and counted, the next frame runs, as XLua). Built and Asyncify-instrumented with Lua: a Lua error unwinds through these functions (an uninstrumented C frame on that path would keep running mid-unwind and trap, as in build 42).
  • Scripts embedded in the module (wasm/tools/gen_fms_embed.py → wasm/out/gen/fms_embed.cpp): XLua's init.lua, B738.a_fms; no package files to find. Zibo's aircraft folder (zibomod/Aircraft_Path) is MSFS's \work\ (its caches, saved state).
  • Inputs (wasm/src/core/fms/fms.cpp): the datarefs the scripts find but do not create start from X-Plane's values (data/fms/xplane_dataref_values.lua, the oracle at cold & dark; its file paths blanked); MSFS's values for the sim/ ones (position, air data, time, weights, N1 / N2, baro, NAV frequencies); CDU power = AC transfer bus 1 / 2; IRS / GPS / FMC positions from the IRS modes; every zibomod value our systems publish as L:LU737_<NAME> (19 at present: MCP, AFDS statuses, EFIS, ...). Writes of the FMS to datarefs it does not own stay in the store (ownership table: later).
  • Outputs: CDU lines over the CommBus (LU737_CDU, changed lines, all every 2 s) to CduText (avionics/src/du/CduSources.ts); EXEC lights, MSG light.
  • Keys: the cockpit's CDU buttons (H:LU737_LAMINAR_B738_BUTTON_FMC1_<KEY>_PRESS / _RELEASE → command laminar/B738/button/fmc1_<key>); every command the scripts handle is reachable the same way.
  • Rate: L:LU737_FMS_HZ (default 20); readouts L:LU737_FMS_STATE, _MS, _MS_MAX, _LOAD_MS, _ERRORS, _MEM_KB (docs/OPTIONS.md).
  • Checked: native tests (wasm/test/test_fms.cpp: MENU, IDENT, POS INIT with the sim's time, N1 LIMIT, RTE, scratchpad typing, 0 Lua errors); the shipped module in the fake MSFS host (wasm/tools/fake_msfs_host.py, CI): loads in 395 ms, 4 ms per FMS run (wasmtime), 0 Lua errors, CDU lines on the CommBus, a CDU key through the IO instrument's CommBus path.

MSFS speed (build 43, owner)#

Self-test in MSFS 2024: 10/10, timing loop 1565 ms (the same SDK module in wasmtime: 580–660 ms; clang 18 build 460–540 ms). MSFS runs this module about 2.5× slower than wasmtime. Applied to the spike's numbers (wasmtime, clang 18 build): the FMS without a route about 0.9 ms → ~3 ms per frame in MSFS; a 40-leg route 3.8 ms → ~12 ms per frame. Too much at 60 fps if run every frame, so:

  • the FMS is measured in the sim as soon as it loads (an L:var with its per-frame ms, like the self-test) before anything is tuned;
  • the FMS runs at a fixed rate of its own (X-Plane runs Zibo's scripts at the sim frame rate; the FMS's timers are time-based), first candidate 20 Hz, with the AFDS keeping its per-frame commands;
  • the hot paths found in the spike (XLua glue, dataref access) stay in C.

Lua self-test (owner: please check in the sim)#

Set L:LU737_LUA_SELFTEST_RUN to 1: the gauge runs wasm/src/core/fms/lua_selftest.cpp, one step per frame (10 checks, a timing loop). Results:

L:var expected
L:LU737_LUA_SELFTEST 1 (pass), -1 fail, 0 = the module did not get there
L:LU737_LUA_SELFTEST_PASSED / _TOTAL 10 / 10
L:LU737_LUA_SELFTEST_MS the timing loop in ms (wasmtime, SDK module: about 600; tells the sim's WASM speed)
L:LU737_LUA_SELFTEST_STEP / _NEXT 13 / 13 (if the gauge stops: the last finished step / the one that stopped it)

The console (developer mode) shows LU737: Lua self-test 10/10, timing loop ... ms.

Plan#

  1. XLua host in the systems module (C/C++): Lua state, XLua's init.lua with the C glue (from tools/fms_spike/xlua_native.c), callouts (flight_start, before/after_physics, timers), commands. The dataref bus: zibomod datarefs the FMS finds map to our modules' globals (the AFDS reflects fields by name); datarefs the FMS creates are owned by Lua unless our C++ already owns them (ownership table: IRS mode, MCP course, MMR / radios, options, doors, ...); X-Plane sim/ datarefs map to MSFS simvars.
  2. Scripts: B738.a_fms plus what it needs (calc, anav, LATLON; others as the survey lists), read from the package (embedded or \work), compiled at load.
  3. Navdata provider (MSFS) (design and Zibo's interface: docs/M5C_NAVDATA.md): SimConnect facility list + facility data (airports, runways, VORs / NDBs / ILS, waypoints, airways, procedures) fill Zibo's tables (earth_nav, ils_nav, apt_data, rnw_data, awy_data, the idx_* indexes; procedures per airport on demand) natively instead of its X-Plane file parsers; cached in \work per AIRAC. Table-equality tests against Zibo's own parsers on the same data.
  4. zibomod's path / VNAV code (not Lua): calc_next_turn, fms_turn, calc_path, ap_vnav, vnav_calc_speed, vnav_init translated with the existing tools/re pipeline and checked against the oracle; they turn the FMS's legs into the LNAV / VNAV commands.
  5. CDU: the line datarefs to the CDU screens (CommBus to CduText, avionics/src/du/CduSources.ts), the cockpit keys (LAMINAR_B738_BUTTON_FMC1_*) and keyboard input (owner: yes) to the FMS commands, EXEC / MSG lights.
  6. Oracle comparison: CDU scenarios (key sequences) run in the oracle and here, lines compared; then a phase-1 flight with LNAV / VNAV.