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)#
- 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. - 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.luanamespace 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
StackSizesSectionover with the function indices shifted by the removed intrinsic imports (checked by function name), keepsGuardPages; - Binaryen:
binaryen@132.0.0from npm (WASM_OPToverrides).
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.luaunmodified 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'sinit.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 thesim/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 asL: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) toCduText(avionics/src/du/CduSources.ts); EXEC lights, MSG light. - Keys: the cockpit's CDU buttons (
H:LU737_LAMINAR_B738_BUTTON_FMC1_<KEY>_PRESS / _RELEASE→ commandlaminar/B738/button/fmc1_<key>); every command the scripts handle is reachable the same way. - Rate:
L:LU737_FMS_HZ(default 20); readoutsL: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#
- XLua host in the systems module (C/C++): Lua state, XLua's
init.luawith the C glue (fromtools/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-Planesim/datarefs map to MSFS simvars. - Scripts:
B738.a_fmsplus what it needs (calc,anav,LATLON; others as the survey lists), read from the package (embedded or\work), compiled at load. - 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, theidx_*indexes; procedures per airport on demand) natively instead of its X-Plane file parsers; cached in\workper AIRAC. Table-equality tests against Zibo's own parsers on the same data. - zibomod's path / VNAV code (not Lua):
calc_next_turn,fms_turn,calc_path,ap_vnav,vnav_calc_speed,vnav_inittranslated with the existingtools/repipeline and checked against the oracle; they turn the FMS's legs into the LNAV / VNAV commands. - 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. - Oracle comparison: CDU scenarios (key sequences) run in the oracle and here, lines compared; then a phase-1 flight with LNAV / VNAV.