Status: M5b-1, 2026-11-04. Subject: zibomod's Glareshield (MCP buttons, AFDS modes, MCP lights, FMA, A/T logic),
ap_speed_accel and flight_director_new (flight director commands) with their callees — 189 functions.
How it is ported#
Unlike the earlier modules (hand ports from the decompiled listing), the AFDS is translated mechanically, then verified against the oracle on every frame:
tools/re/decompile.py: Ghidra's decompiler with every function's prototype (mangled C++ signatures; return type from the unoptimised epilogue; XPLM / libm imports), read-only constants read at the size of the instruction that loads them, globals and calls by symbol name.tools/re/port_cpp.py: the listing becomes a C++ class (wasm/src/core/afds/afds_gen.*): zibomod's globals are members with zibomod's names (type from the instructions that access them, size from the symbol table, initial value from.data), Ghidra's address arithmetic becomes indexing, XPLM calls become hooks.tools/re/gen_afds.shruns both overtools/re/afds_functions.txt. Generated files are not edited by hand.
wasm/src/core/afds/afds.{h,cpp} (hand-written): the hooks (dataref writes and X-Plane commands are recorded;
the three sim commands zibomod itself handles — servos_fdir_off, knots_mach_toggle, take_off_go_around —
loop back to its handlers as in X-Plane), stubs (Read_axis: inputs; VNAV was a stub until build 65: docs/VNAV.md), flight_director_show
by hand (Ghidra split its local array), the frame entry points in zibomod's order (Glareshield after
Idle_mode / Engine_n1_n2 / Thr_shift; later in the frame ap_speed_accel, then flight_director_new before
the servo).
Inputs#
zibomod's own globals: simDR_* (X-Plane datarefs as Read_Xplane_DR copies them), B738DR_* (Lua or zibomod
datarefs, Read_B738_DR), and globals other zibomod code writes (MainFlightLoopCallback's air data and speed
filters, the servo, Read_axis, writable dataref accessors): tools/re/zibomod_reads.json,
tools/re/afds_external.json. The integration in Systems (next step) fills them from MSFS and the other
modules.
Verification#
afds_replay_matches_oracle (wasm/test/test_afds_replay.cpp): xphost/test/sci/afds_trace.json powers the
aircraft on the ground (battery, APU, both APU generators), goes airborne at FL100 / 250 kt and works the MCP:
FD, A/T arm, HDG SEL, ALT HLD, SPEED, CMD A, V/S, LVL CHG, N1, VOR/LOC, CWS A, CMD B, disengage bar, A/T
disengage, FD off. The oracle traces every input and state global of the translated code from the plugin's
memory; inputs are sampled where the code reads them (host call-site hooks: after Glareshield's Read_axis,
before ap_speed_accel, before flight_director_new). The test replays the inputs into the rewrite and compares
all 734 state values after every frame: identical on every frame (golden file
wasm/test/data/afds_trace_oracle.txt.gz). Build 65: a second scenario, VNAV's climb and level-off
(afds_vnav_replay_matches_oracle, docs/VNAV.md); lnav_vnav_horiz_vert (the PFD's GPS / ANP scales, after the
flight director in zibomod's frame) is replayed and checked too.
In the systems module (build 24)#
wasm/src/core/systems_afds.cpp, every frame after the other modules:
- Inputs: air data and attitude from MSFS (calibrated airspeed, TAS, OAT / TAT -> speed of sound, magnetic
heading, pitch, bank, altitudes, V/S, radio height, ground speed, Mach, track, wind, AoA, flaps, on ground,
yoke, trim, baro); buses,
lost_ac_ap, transfer bus sources (electrics); IRS modes and source switch; MCP heading / altitude / course / bank (mcp/); VHF NAV deviations in dots, DME, active frequency, MMR mode and station identified (MSFS NAV +nav/). zibomod's own indicated airspeed filter (ap_speed_accel) is kept, as X-Plane keeps it withoverride_ias. FMS values: Zibo's defaults without a flight plan (M5c). Temporary: hydraulic system A / B pressurised while engine 1 / 2 runs (hydraulics not rewritten yet). - MainFlightLoopCallback parts (
Afds::mainLoopStart/mainLoopEnd, from the listing): TAS / speed of sound / TAT filters, Mach,kspd, the MCP dial in knots while in Mach,plane_on_ground,first_time,first_timer. - Speed dial: shared with
mcp/— the knob moves it there, the AFDS writes it back (e.g. held at 100 without DC buses 1 and 2, zibomodcheck_mcp_speed). - Events (PRESS -> phase 0, RELEASE -> 2;
_2duplicate nodes): FLIGHT_DIRECTOR, FIRST_OFFICER_FLIGHT_DIRECTOR, AUTOTHROTTLE_ARM, HDG_SEL / LVL_CHG / VNAV / LNAV / VORLOC / APP / ALT_HLD / VS / N1 / SPEED_MODE, CMD_A / B, CWS_A / B, AP_DISCONNECT, SPEED_INTERVENTION, CAPTAIN / FO_AP_DISENGAGE, LEFT / RIGHT_A_T_DISENGAGE_BUTTON, LEFT / RIGHT_TO_GA_BUTTON; AP_VERTICAL_SPEED INC / DEC -> zibomodCMD_vert_spd_up / _down(50 fpm steps below 1000 fpm, 100 above; the dial is 0 outside V/S). - Outputs: 168 L:vars
L:LU737_<SCI state>(cockpit model: MCP lights and buttons) or the dataref path (displays: FMA, FD bars, speed / altitude mode, ...),wasm/src/core/afds/afds_outputs.incgenerated bytools/re/afds_io.pyfrom zibomod's dataref accessors (tools/re/zibomod_datarefs.json). The PFDs read 57 of them (AFDS_LVARSinavionics/src/du/PfdSources.ts). - Order: the AFDS runs after the electrics / IRS / NAV of the same frame (zibomod: Glareshield before its electrics), so a bus or IRS change reaches it one frame earlier than in Zibo.
Tests: afds_mcp_buttons_engage_modes, afds_vs_wheel_steps_the_dial (wasm/test/test_afds.cpp).
Autothrottle and servo (M5b-3, build 25)#
- Thrust levers / A/T: zibomod's B738_N1_thrust_manage_new2 owns the levers (already translated). X-Plane's
throttle ratio is MSFS's throttle lever position (
simDR_throttle); zibomod's joystick axis (B738DR_joy_axis_throttle*), by which it sees the pilot take over from the A/T, is the throttle as the pilot last set it (a change of the MSFS throttle we did not command). When zibomod writes the throttle ratio, Systems moves MSFS's throttles (THROTTLE1/2_SET). Engine inputs from the engine module (N1, N2, throttle use, maximum use, idle shift, idle mode); N1 limits as zibomod Eicas_thrust (n1_to_thr_useof 100 % / 94 % of the FMS N1). Temporary until the FMS: N1 limits take-off 95.2 %, climb and active 93.5 %. - AFS speed limit (B738.calc.lua): min(maximum speed from
speeds/, flap placard by the lever, 235 kt gear down); PFD maximum / minimum manoeuvre speeds fromspeeds/. - Servo (
afds/servo.{h,cpp}, our control laws): while the AFDS enables a channel (enable_servo_pitch/_rollwith the AP on) it holds the flight director's pitch / bank (master side): attitude error -> rate command (pitch <= 2.5 deg/s, roll <= 5 deg/s) -> PI on the rate error ->AXIS_ELEVATOR_SET/AXIS_AILERONS_SET, gains scaled with (220 / IAS)^2, stabiliser trim (ELEVATOR_TRIM_SET) offloading the elevator; bumpless engage from the yoke, nothing written when disengaged. Signs assumed (elevator axis negative = pull, aileron axis positive = right roll, trim positive = nose up):L:LU737_AP_ELEV_SIGN/_AIL_SIGN/_TRIM_SIGN= -1 flips one. Build 27: owner's test — the roll diverged on engagement. MSFS's axis events are opposite to its control SimVars (AILERON POSITION / YOKE X positive = right, ELEVATOR POSITION / YOKE Y positive = nose up; AXIS_AILERONS_SET negative = right roll, AXIS_ELEVATOR_SET negative = pull): the aileron command is now negated, the yoke and trim are read in their physical sense (the sign L:vars flip only what is sent), commands limited to bank +-30 deg, pitch -10 .. +20 deg. Testafds_servo_corrects_in_the_right_direction. DiagnosticsL:LU737_AP_SERVO_PITCH_ON / _ROLL_ON / _ELEVATOR / _AILERON / _PITCH_CMD / _ROLL_CMD. Tested against a simple aircraft model (test_servo.cpp: 140 / 220 / 300 kt, half and double control power); the real tuning is MSFS's flight model (owner test).
Level-off and the MCP speed (build 28)#
Owner (build 27): the autopilot climbs through the MCP altitude (LVL CHG and V/S); the MCP speed sometimes jumps to 100 kt. Both were integration gaps around the translated code, which itself matches the oracle; the replay test could not see them because it injects every global zibomod reads, including those the integration forgot.
- Level-off: the flight director's altitude hold / ALT ACQ (
fd_althld) readaltitude_pilot/_copilot, globals MainFlightLoopCallback copies from the X-Plane datarefs every frame (0x1657d4..0x165864, withradio_altitude_pilot,airspeed_pilot;flight_phaseat 0x166964).Afds::mainLoopStartdid not copy them: the FD held altitude 0, commanded +1700 fpm in ALT ACQ / ALT HOLD and the aircraft kept climbing with ALT HOLD on the FMA. Now copied. Closed-loop tests (test_afds_capture.cpp, an aircraft that follows the FD pitch, thrust and gravity on the speed): V/S climb, V/S descent, LVL CHG climb and descent with the A/T level off within 5–11 ft of the MCP altitude and hold it. - MCP speed to 100 kt: above the crossover altitude a LVL CHG or V/S climb switches the MCP to Mach
(
ap_lvl_chg/ap_vs->crossover_altitude->sim/autopilot/knots_mach_toggle) and zibomod reads X-Plane'sairspeed_is_machback right after the command. zibomod's handler returns 1, so X-Plane's own handler then flips that flag and converts its airspeed dial; nothing did that here: the flag read back was the old 0, the MCP held Mach 0.7x as knots andcheck_mcp_speedclamped it to 100 kt.Afds::xplaneKnotsMachTogglenow does X-Plane's part. Same path: TO/GA in Mach,check_mcp_speed's IAS restore. Testafds_lvl_chg_crossover_switches_to_mach. - IAS/MACH C/O button: through zibomod's
CMD_autopilot_co_press(only with the IAS shown, Mach from 0.6), the MCP module follows at once (Systems::afdsChangeOver); before, the MCP module toggled by itself. - Zibo's own 100 kt resets (oracle
xphost/test/sci/afds_mcp_speed_ground.json, testafds_mcp_speed_on_the_ground_as_zibo): LVL CHG pressed on the ground with the MCP altitude away from the field elevation sets the MCP speed to 100 kt (ap_lvl_chg_cmd,plane_on_ground); V/S on the ground reverts to LVL CHG after 0.5 s and does the same; DC bus 1 or 2 unpowered holds it at 100 (check_mcp_speed). As in X-Plane. - Inputs added: flap surface angles (
flap1_deg, the FD's flap rate) fromTRAILING EDGE FLAPS LEFT / RIGHT ANGLE. Audit of the other external globals: the rest are FMS values (M5c), zibomod's own servo (replaced by ours) or unused. - Zibo behaviour seen in the tests: in LVL CHG the A/T stays armed for the first 150 s after lift-off
(
lift_off_150), then N1.
Yoke, CWS and the disconnect lights (build 29)#
- The pilot's controls. zibomod overrides X-Plane's joystick: X-Plane's yoke ratios (
pitch_ratio2,roll_ratio2,joy_axis_*) are the pilot's stick, and the servo flies the surfaces. MSFS has one elevator / aileron for both, so the gauge observes the joystick's axis events (fsEventsRegisterKeyEventHandler:AXIS_ELEVATOR_SET,ELEVATOR_SET,AXIS_AILERONS_SET,AILERON_SET, the throttle axes) and gives them toSystems::pilotAxis, leaving out the events the module sends itself. These are zibomod's joystick axes:- CMD with the yoke moved more than 0.11 from its engage position: CWS P / R (pitch / roll modes off); below
400 ft or 150 ft RA: A/P disconnect (zibomod
check_yoke,ap_system). - CMD pressed with the yoke more than 0.1 off centre: refused, A/P disconnect warning (
ap_cmd_a/_b). - Before build 29 MSFS's YOKE X / Y (the servo's own output) stood for the pilot's yoke.
- CMD with the yoke moved more than 0.11 from its engage position: CWS P / R (pitch / roll modes off); below
400 ft or 150 ft RA: A/P disconnect (zibomod
- CWS (
afds/servo): with the A/P in CWS on an axis, the servo leaves the controls to the pilot while the yoke is out of the detent (0.05) and holds the attitude at the release: the pitch, and the bank, or wings level below 6 deg (the 737's CWS roll). Before build 29 nothing held the attitude in CWS (zibomod's servo does it). - Thrust levers: the pilot's throttle axis, when the gauge sees it, is zibomod's
joy_axis_throttle(the A/T's hardware-lever logic); keyboard throttle changes still count as the pilot's when MSFS's lever moved without the A/T. MSFS still applies the hardware lever's events itself (only observed, not masked). - Disconnect lights (zibomod
Annunciators): red A/P (ap_disconnect), amber A/T (at_disconnect,at_disconnect2), amber FMC (FMC message, not yet: no FMS; or 0.33 s of the test), all at the annunciator brightness. The A/P disconnect test switches (TEST 1 / TEST 2, spring loaded) drive zibomod'sCMD_ap_disconnect_test1/2_up / _dn. Bank angle selector at 25 deg at flight start (Luaflight_start).
ILS approach and autoland (build 70)#
afds_ils_replay_matches_oracle (wasm/test/test_afds_replay.cpp): xphost/test/sci/afds_ils_trace.json
(tools/re/afds_ils_scenario.py), in the approach world with KPDX's ILS IPDX 110.30 for runway 10R
(wasm/test/approach_world.h addKpdxIls). At 2500 ft, 180 kt, 15 nm out and 3 nm north of the localizer,
heading 120 (a 20 degree intercept): both MMRs on 110.30 (Zibo's MMR panel: MODE to ILS, the digits, the
transfer), both courses 100, the bank angle selector 30, FD both, ALT HLD, HDG SEL, CMD A, APP, CMD B; flaps 15 and
160 kt, flaps 30 and 150 kt; the A/P and A/T disconnected at about 80 kt on the rollout.
The oracle has no flight model, so this one is a closed loop through zibomod's own laws: the host flies a
kinematic aircraft whose bank and pitch follow zibomod's flight director (fly: 1 s lag, a coordinated turn, the
flight path angle from the pitch, X-Plane's local velocity for zibomod's track, the touchdown at the runway's
elevation, then 3 kt/s down) and computes X-Plane's receivers from the geometry (ils: the localizer deviation, 1 dot
a degree; the glideslope's, 0.28 degree a dot, positive with the aircraft above the path; the DME; the localizer's
course, to which Zibo's Lua slews the OBS once the localizer is received). zibomod: LOC captured 10.4 nm out, G/S
captured, FLARE armed with LAND 3 at about 1380 ft, the flare at 100 ft, RETARD at 64 ft, the touchdown about 1600 ft
past the threshold, the rollout. The translation: identical on every frame (11648 frames, 732 values).
Found on the way:
- The oracle printed int globals to 9 digits. zibomod keeps its PID states (floats) in int arrays (
fd_pitch: the flare); 10 digit bit patterns lost their last digit, and a replay window restarting from the oracle's state started from a slightly wrong flare PID. Ints are now printed exactly (the host's trace); the old goldens are unaffected (no PID in use at their restarts). - Sign conventions. zibomod's LOC law wants X-Plane's
hdefpositive with the course to the right (the aircraft left of it) andvdefpositive with the aircraft above the glideslope. Our MSFS mapping passesNAV CDIandNAV GSIthrough: FSUIPC documents MSFS's GSI (offset 0C49) as "-119 up to +119 down", positive with the aircraft above, so the senses agree. - Zibo's LOC turns are limited by the MCP bank angle selector (10 degrees at its first position): a 30 degree intercept at 10 degrees overshoots.
afds_ils_approach_and_autoland (wasm/test/test_afds_capture.cpp): the same approach through the systems module
and our servo (the kinematic aircraft follows L:LU737_AP_SERVO_ROLL_CMD / _PITCH_CMD), MSFS's NAV CDI /
NAV GSI from the geometry: LOC captured 8 nm out and then within 0.06 degree, G/S within 0.1 degree to 200 ft,
FLARE armed at 1420 ft with LAND 3, the flare at 63 ft, the touchdown 1860 ft past the threshold at 200 ft/min, the
rollout on the centreline. (Without the A/T engaged in it: no RETARD there.)
Next#
- M5b-2: flight director bars on the PFD; M5b-3: our servo on MSFS's controls (zibomod's
autopilot_servodrives X-Plane's yoke) and the A/T driving MSFS's throttles; VNAV with the FMS (M5c).