Milestone M2. Subsystem: engine start switches, starter, ignition, start levers, light-off and start EGT, N1/N2 spool-up and spool-down, idle modes, thrust lever → requested N1/throttle, thrust, fuel flow, EGT, oil, vibration, failures/flameout, EEC, reversers (engine side), and the coupling to the simulator's engine model.
Source: zibomod.xpl (Zibo 2.S1.50, Linux build with symbols), read with
tools/re/decompile.py (Ghidra); XLua scripts; 737_80NG.acf; the SCI contract
and Zibo profile; the oracle (xphost). Everything below is described in our own
words; constants, tables and timings are Zibo's. Function and global names are
the symbol names in zibomod.xpl (e.g. Starter, engine_N2_anim), so a reader
can find the original.
Conventions: index i = engine (0 = left/1, 1 = right/2). dt = sim_period
(see Appendix A: frame time clamped to (0, 0.1] s, 0.01 s on the first frame).
Helper functions (Rescale, Limit, Anim_value, B738_set_animation_rate,
Anim_linear_value2) are defined in §5.1; the CFM56 table functions
(cfm56_7b_*, corrected_*) are reproduced by a separate work package and
are used here as black boxes with the arguments given.
1 Scope and sources#
In scope: everything zibomod computes for the two CFM56-7B26 engines except the pure table functions: start switches and their knob animation, the solenoid release, start valves, starter air → N2, ignition (L/R/BOTH, igniter power, auto-relight, windmill and cross-bleed start envelopes), start levers → X-Plane mixture, light-off EGT, hung/hot/wet starts, N1/N2 during start, spool-down and windmilling, idle (ground/flight/approach, EEC modes), thrust lever → throttle use → N1, EGT, fuel flow (indication and to the simulator), oil pressure/temperature, vibration, fan rotation, failures and flameouts, reverser engine side, and the forces written to X-Plane.
Not in scope: the CFM56 tables (separate work package), duct pressure
and bleed valves (pneumatics spec: this spec lists what start needs from it),
fuel tanks/pumps/valves (fuel spec), A/T and N1 limit computation
(B738_N1_thrust_manage_new2, B738_eng_n1_set, B738_eng_out are called
from Glareshield; AFDS/AT spec), EICAS drawing (display spec; it reads the
outputs of §6), sound.
Sources (zibomod function → section): MainFlightLoopCallback (App. A),
Init_engine_param (4.1), Idle_mode_start_flight, Systems_start_flight
(4.2), Idle_mode (4.3), Engine_n1_n2 (4.4), Thr_shift (4.5),
thr_use_to_n1, n1_to_thr_use, Thr_lever_to_use, Eicas_thrust (4.6),
Xbleed_start (4.7), Starter, Gauge_N1_N2_EGT, n1_shutdown,
n2_shutdown (4.8), Engine_calculation, egt_start, calc_start_egt
(4.9), Engine_model (4.10), Systems (start part, 4.11),
CMD_engN_start_*, CMD_eng_start_lft/rgt, CMD_mixtureN_*,
manage_eng_failure, eng_flameout_effect, engine_fail (4.12),
N1_N2_idle (4.13), CMD_reverse_*, reverse_leverN (4.14);
Register_*DR for dataref names; Xplane_datarefs/Read_Xplane_DR for the
X-Plane inputs. XLua: B738.tablet owns laminar/B738/engine/engine_air_start
(EFB ground air cart), B738.a_fms owns laminar/B738/engine_start_auto
(option, default 0). ACF: CFM56 parameters below are not used by Zibo's model
except where noted in §7. Oracle runs: xphost/test/sci/start_spec_eng1_ground.json
(§8).
2 Inputs#
2.1 Cockpit (SCI controls → Zibo commands/datarefs)#
| SCI control (actions) | SCI state | Zibo | Notes |
|---|---|---|---|
ENGINE_1_STARTER / ENGINE_2_STARTER (INC, DEC) |
ENGINE_STARTER1_POS, _POS2 (ENGINE_STARTER2_*) |
cmd laminar/B738/knob/engN_start_right / _left; dref laminar/B738/engine/starterN_pos (0 GRD, 1 OFF, 2 CONT, 3 FLT), starterN_pos2 |
§4.11.1; GRD released by logic at 55.3–58 % N2 (contract §4.3 hold_by_logic) |
| (no SCI control) | – | cmds laminar/B738/rotary/engN_start_grd/off/cont/flt |
direct positions |
ENGINE_START_SOURCE (INC, DEC) |
TOGGLE_SWITCH_ENG_START_SOURCE |
toggle_switch/eng_start_source_right/_left; −1 IGN L, 0 BOTH, 1 IGN R |
default IGN R |
MIXTURE_ENGINE_1/2 (INC/DEC ±0.1), MIXTURE_ENGINE_1_2/_2_2 (TOGGLE 0↔1) |
ENGINE_MIXTURE_RATIO1/2 |
dref laminar/B738/engine/mixture_ratioN; cmds laminar/B738/engine/mixtureN_idle/_cutoff/_toggle/_hold |
start lever CUTOFF 0 / IDLE 1; Zibo tests > 0.5 |
EEC_1/2 (TOGGLE), guard EEC_1/2_GATE |
SIM_COCKPIT2_ENGINE_ACTUATORS_FADEC_ON_0/1, BUTTON_SWITCH_COVER_POSITION_0/1 |
X-Plane sim/cockpit2/engine/actuators/fadec_on[i] (1 NORMAL, 0 ALTN) |
§4.3 |
THROTTLE_ENGINE_1/2, THROTTLE_ALL (INC/DEC) |
ENGINE_THRUST1/2/12_LEVELER |
laminar/B738/engine/thrust1/2/12_leveler (0…1) |
also moved by the A/T |
REVERSE_THROTTLE_1/2/BOTH |
FLT_CTRLS_REVERSE_LEVER1/2/12 |
laminar/B738/flt_ctrls/reverse_lever1/2/12 |
§4.14 |
ENGAGE_/DISENGAGE_REVERSE_1/2/BOTH (PRESS) |
SIM_COCKPIT2_ENGINE_ACTUATORS_PROP_MODE_0/1 |
sim/engines/thrust_reverse_toggle_1/2, …toggle |
handled by Zibo commands CMD_reverse_toggle* |
N1_SET, N1_SET_ADJUST |
TOGGLE_SWITCH_N1_SET_* |
n1_set_source, n1_set_adjust |
AFDS/EICAS spec (B738_eng_n1_set) |
ENGINE_1/2_ANTI_ICE |
ICE_ENG1/2_HEAT_POS |
B738DR_eng1/2_heat_pos |
approach idle, auto-ignition |
| fire handles, bleed switches, packs, isolation, APU bleed | (fire, pneumatics specs) | – | inputs listed in 2.3 |
2.2 Simulator inputs (X-Plane datarefs Zibo reads → MSFS source)#
| X-Plane dataref (Zibo global) | Use | MSFS source (recommendation) |
|---|---|---|
sim/weather/aircraft/barometer_current_pas (simDR_current_pa), sim/weather/barometer_current_inhg |
AmbPR, oil pressure offset | AMBIENT PRESSURE (inHg → Pa) |
sim/cockpit2/temperature/outside_air_LE_temp_degc (simDR_TAT) |
θ, EGT floor | TOTAL AIR TEMPERATURE |
sim/cockpit2/temperature/outside_air_temp_degc (simDR_OAT) |
idle FF correction, EEC soft ALTN | AMBIENT TEMPERATURE |
sim/weather/sigma, sim/weather/rho |
rates, flight idle | AMBIENT DENSITY / 1.225 |
sim/flightmodel/misc/machno, sim/flightmodel/position/true_airspeed |
tables, DynP | AIRSPEED MACH, AIRSPEED TRUE |
sim/flightmodel2/position/pressure_altitude, sim/flightmodel/position/elevation |
tables, windmill, flameout | PRESSURE ALTITUDE, PLANE ALTITUDE (m) |
radio_altimeter_height_ft_pilot, on_ground, groundspeed, IAS pilot |
idle/airborne latch, start envelopes, ground thrust | RADIO HEIGHT, SIM ON GROUND, GROUND VELOCITY, AIRSPEED INDICATED |
sim/flightmodel/weight/m_total, wind speed/direction, magnetic heading |
ground thrust term, windmill | TOTAL WEIGHT, AMBIENT WIND VELOCITY/DIRECTION, PLANE HEADING DEGREES MAGNETIC |
sim/weather/precipitation_on_aircraft_ratio, hail |
rain flameout, light-off rate | AMBIENT PRECIP RATE (scaled) — see §9 |
sim/flightmodel2/engines/N1_percent, N2_percent |
read back of Zibo's own writes; flight start | Zibo's own values (no MSFS read) |
sim/flightmodel/engine/ENGN_running, sim/flightmodel2/engines/engine_is_burning_fuel |
light-off/"running" | must be modelled by the rewrite (§7.1) |
sim/flightmodel/engine/ENGN_thro_use |
Zibo's own throttle use (written by Eicas_thrust) |
internal state |
sim/cockpit2/engine/indicators/fuel_flow_kg_sec |
Zibo's own ENGN_FF_ |
internal state |
sim/cockpit2/engine/indicators/oil_temperature_deg_C, sim/flightmodel/engine/ENGN_oil_quan |
oil temperature base, quantity | must be modelled (§7.2) |
sim/flightmodel2/engines/engine_rotation_speed_rad_sec, …angle_deg |
fan animation | Zibo's own (rot_spd/ang) |
sim/flightmodel/failures/inlet_ice_per_engine (rel_eng_ice) |
ice factor on throttle use | MSFS engine icing if exposed, else 0 |
sim/operation/failures/rel_engfai0/1, rel_engsep0/1, rel_engfir0/1, rel_fadec_0/1, rel_oilpmp0/1, rel_N2_ind0/1, bird strike |
failure states (Zibo writes most of them itself) | internal state |
sim/cockpit2/engine/actuators/fadec_on[i], prop_mode[i], ignition_key[i], igniter_on[i], mixture_ratio[i] |
written and read back by Zibo | internal state |
sim/operation/prefs/startup_running |
cold vs running start | MSFS flight state (cold & dark vs engines running) |
sim/time/paused, sim/time/is_in_replay |
frame gating | SIM PAUSED/replay state |
2.3 Other subsystems (Zibo datarefs/globals)#
| Input | From |
|---|---|
B738DR_real_duct_pressure_L/R (psi), B738DR_apu_bleed_air_valve_pos, B738DR_isolation_bleed_air_valve_pos, B738DR_lpack/rpack_bleed_air_valve_pos, apu_establish_time, ext_air_duct, laminar/B738/engine/engine_air_start |
pneumatics |
B738DR_spar_valve_N_pos, B738DR_eng_valve_N_pos, tank pump status L1/L2/C1/R1/R2/C2, cross_feed_valve, X-Plane fuel_selector_l/r, tank weights |
fuel |
B738DR_dir_batbus_status (battery bus), ac_tnsbus1/2_status, ac_stdbus_status, batbus_status |
electrics |
B738DR_hyd_A/B/stdby_status, fire handle arm, simDR_engN_fire |
hydraulics, fire |
B738DR_flaps handle, cowl anti-ice (ice_inlet_heat_on_per_engine), B738DR_engN_heat_pos |
flight controls, anti-ice |
B738DR_fms_N1_thrust[0..1] (N1 limit ratio), A/T lever writes |
FMS / AFDS |
laminar/B738/failure/engine[0..69] |
EFB failures |
B738DR_sfp/sfp_variant, laminar/B738/engine_start_auto |
options |
3 State#
Per engine unless noted (names = zibomod globals; reset values from §4.1–4.2):
| Group | State |
|---|---|
| Start switch | knob[i] (engineN_starter_pos), engine_starter_tgt[i], B738DR_engineN_starter_pos/_pos2 |
| Valves/ignition | B738DR_start_valveN, engineN_starter_valve, ignition[i], real_ignition[i], engine_on_old2, engine_start_enable_timer, fuel_selector_enable, TMR_energize, egt_redline, TMR_egt_redline, hot_start_warn, eec_hotstart, eng_egt_old |
| Spool | engine_N1_anim, engine_N2_anim, engine_N2_old, B738DR_engN_N1/N2, eng_N1_idx, eng_N2_idx, gauge_n1/n2/egt, was_below50, eng_was_off, was_hung_start, was_hot_start, ENGN_thro_use (as written), T/T95 (per frame) |
| Idle/EEC | idle_mode[i], eec_mode[i], eec_tmr, eec_rst, eec_last_oat/slt, max_thrust_use[i], shift_thr[i], eec_n1_lim_thr_use[i], global idle_n1, idle_n2, TotTR, TotTR_n2, TotTR_egt, AmbPR, aircraft_was_on_air2, TMR_mode_ground_timer, mixture_cur[i] |
| Fuel/EGT/oil | metering_valve, B738DR_fuel_flow_kg_sec, fuel_flow_choke, start_ff, engine_running_old, fuel_valve_open_old, engine_lost, eng_egt_lost_tgt, eng_n2_lost_tgt, saved_egt, motoring_eng_egt, coef_egt, ws_coef_egt, eng_accel_egt, B738DR_engN_egt, B738DR_engN_oil_press, B738DR_engN_oil_temp |
| Random | egt_rnd[i] (±5 °C per flight), eng_diff_stab[0], RNG state |
| Failures | eng1/2_max_timer, eng1/2_flameout_timer, …timer2, TMR_fail_engine, TMR_eng_vib, eng_vib_active, eng_vib_tick, TMR_eng_high_temp, TMR_eng_low_press, eng_flameout_timer/2, B738DR_eng_flameout |
| Vibration/fan | B738DR_engN_vib, rot_spd, rot_ang |
| Reverser | B738DR_reverse_thrust[i], B738DR_reverse_deploy[i], reverse_max_enable/on |
| Simulator-side (must exist in the rewrite) | running/burning flag, mixture, ignition key, igniter, fadec_on, prop_mode, oil temperature/quantity |
4 Per-frame logic in execution order#
Order of the engine functions within a frame (Appendix A.4.4.2 and A.6):
Idle_mode → Engine_n1_n2 → Thr_shift → (Glareshield: A/T moves the
thrust levers) → Eicas_thrust → Xbleed_start → Starter (calls
Gauge_N1_N2_EGT first) → Engine_calculation → Engine_model → … →
Systems (start switches, start valves, ignition, EICAS energise) → … →
manage_eng_failure → eng_flameout_effect → (2 Hz) N1_N2_idle,
engine_fail. Commands (start switches, start levers, reversers) run between
frames, when X-Plane delivers them.
The core idea of Zibo's engine model:
- Zibo owns N1, N2, EGT, fuel flow, thrust, oil pressure. Each frame
Starterwrites N1/N2 into X-Plane's engine (sim/flightmodel/engine/ENGN_N1_,ENGN_N2_),Engine_modeloverrides the engine forces (sim/operation/override/override_engine_forces = 1) and writes the thrust asfaxil_prop/L/M/N_prop, and overrides fuel flow (override_fuel_flow = 1, writesENGN_FF_). - The steady-state N1 follows a commanded "throttle use".
Eicas_thrustturns the thrust lever into an N1 target, rate-limits it in N1 space and converts it into X-Plane'sENGN_thro_use;Gauge_N1_N2_EGTturnsENGN_thro_useback into N1 (thr_use_to_n1) and derives N2 and EGT from the CFM56 tables. X-Plane's own spool dynamics are therefore bypassed: N1 is a deterministic function of Zibo's state (plus the start/shutdown dynamics inStarter). - X-Plane still decides "running":
ENGN_runningandengine_is_burning_fuel(light-off, flameout by fuel starvation or cut-off) come from X-Plane's engine model, driven by Zibo's ignition (ignition_key,igniter_on), mixture (mixture_ratio) and N2 (written by Zibo). See §7.
4.1 Flight load: Init_engine_param (first frame after a load)#
Sets the engine model constants (values in §5.3): max_thrust_use[i] = 1.12,
max_rev_thrust_use = 0.69, min_sigma = 0.235, max_sigma = 1.0, the
thr_tab (14 lever positions 0…1) and several n1_idle* tables. The
n1_idle*/thr_tab tables and the functions Calc_request_n1,
Calc_request_thrust, n1_thruse_norm, thruse_n1_norm,
min_idle_thrust have no caller in 2.S1.50 (dead code): do not port them.
The thrust model type B738DR_curve02_type is 4 (static initial value; only
the developer tuning file or zibomod/engine_model/curve02_type changes it),
so only branch 4 of Engine_model is live.
4.2 Flight start: Idle_mode_start_flight (A.3)#
Depending on X-Plane's start-up preference sim/operation/prefs/startup_running:
- Cold (0) and not a reloaded flight (
loaded_flight == 0): start levers CUTOFF (B738DR_mixture_ratio1/2 = 0), X-Plane N1 = N2 = 0, mixture 0,ENGN_running = 0. - Engines running (1): start levers IDLE (1), X-Plane N1 = 19.5 %,
N2 = 58.5 %, mixture 1,
ENGN_running = 1(written to X-Plane). - Reloaded flight (
loaded_flight != 0): start lever i = IDLE if X-Plane's mixture[i] > 0.2.
Then every internal N1/N2 copy is initialised from X-Plane's
N1_percent/N2_percent: engine_N1_anim, engine_N2_anim, engine_N2_old,
B738DR_eng1/2_N1/N2, gauge_n1/n2, eng_N1_idx, eng_N2_idx;
eng_accel_egt = 0. If X-Plane's igniter_on is 0 for either engine, both are
set to 1 (written). Vibration indication initial value from N1 (same
breakpoints as §4.8.8 divided by 100); eng_vib_active[i] = false,
eng_vib_tick = 0.
Systems_start_flight (A.3) also: start switches to OFF (starter_pos = 1,
pos2 = 1, internal knob engineN_starter_pos = 1, engine_starter_tgt = 1),
IGN select B738DR_eng_start_source = 1 (= IGN R), and
eng_diff_stab[0] = 1 − generate_rnd(0, 0.08) while eng_diff_stab[1]
keeps 1.0. (Zibo writes both random values to element 0 — the second store
overwrites the first, so engine 1 gets 1 − r and engine 2 is never
randomised. Reproduce.) egt_rnd[i] (A.3.1) is drawn just before.
4.3 Idle_mode — idle selection, EEC mode, start lever → X-Plane mixture#
- Airborne latch: if radio height > 20 ft and
aircraft_was_on_air2 == 0→aircraft_was_on_air2 = 1. - Ground timer:
T = 2.5 sif the SFP option is fitted (sfp_variantorB738DR_sfp != 0), else5.5 s. If on ground: if never airborne →TMR_mode_ground_timer = 0, ground flag = true; else if the timer is 0 → start it atT. (Annun_timers, insideAnnunciators, counts it down bydtand when it reaches 0 clearsaircraft_was_on_air2; so flight/approach idle is kept forTs after touchdown.) - EEC input health
ok[i] = engine_failure[14 + i] != 1(failure arraylaminar/B738/failures/engine, index 14/15 = "EEC normal-mode input"). - EEC hardware failure
engine_failure[16 + i] == 1→ X-Planerel_fadec_i = 6(failed, written). - X-Plane
fadec_on[i](the EEC switch, SCIEEC_1/2) is forced 0 whenrel_fadec_i == 6; written if changed. - Per engine, EEC mode
eec_mode[i](−1 failed, 0 hard ALTN, 1 NORMAL, 2 soft ALTN, 3 soft ALTN with lever at idle) with timerseec_tmr[i],eec_rst[i]:if rel_fadec_i == 6: eec_mode = -1; eec_tmr = eec_rst = 0 elif fadec_on[i] == 0: eec_mode = 0; eec_tmr = eec_rst = 0 elif eec_mode < 2: eec_mode = 1 if eec_mode == 1: if ok[i]: eec_rst = 0 elif eec_tmr == 0: eec_tmr = 15 else: eec_tmr -= dt; if eec_tmr <= 0: eec_tmr = 0; eec_mode = 2 elif eec_mode >= 2: if eec_mode == 3: eec_rst += dt if eec_rst >= 15: eec_mode = 0; fadec_on[i] = 0 (written); eec_rst = 0 else: eec_rst = 0 if thrust_lever[i] < 0.01: eec_mode = 3 - Idle mode
idle_mode[i](0 ground, 1 flight, 2 approach):
Published asif ground flag: idle_mode = 0 else: idle_mode = 1 if eec_mode >= 0: if not ok[i]: (EEC lost its inputs) if pressure altitude < 15000 ft: idle_mode = 2 elif flap handle ratio < 0.625: (below flaps 15) if cowl anti-ice on (either engine): idle_mode = 2 else: idle_mode = 2 if eec_mode == 1: eec_last_oat[i] = OAT; eec_last_slt[i] = 15 (frozen for soft ALTN)laminar/B738/engine/idle_mode[0..1](B738DR_idle_mode). - Start lever → X-Plane mixture:
mixture_cur[i] = Anim_value(mixture_cur[i], B738DR_mixture_ratio{i+1}, 0, 1, 1)(first-order lag, 1/s), written tosim/cockpit2/engine/actuators/mixture_ratio[i]when it changes. The start lever itself (laminar/B738/engine/mixture_ratio1/2, SCIENGINE_MIXTURE_RATIO1/2) is 0 = CUTOFF, 1 = IDLE; commandsmixtureN_idle(= 1),mixtureN_cutoff(= 0),mixtureN_toggle,mixtureN_hold, and the SCI generic controls write the dataref directly.
4.4 Engine_n1_n2 — ambient ratios and idle N1/N2#
AmbPR = P_amb[Pa] / 101325
TotTR = max(0, (TAT + 273.15) / 288.15)
TotTR_n2 = TotTR ^ 0.45 (N2 correction)
TotTR_egt = TotTR ^ 0.16 (EGT correction)
DynP = 0.5 * rho * TAS^2 ; TotP = P_amb + DynP ; TotPR = TotP / 101325
n2_min = 58.4 ; if any engine idle_mode == 1: 72.1 ; elif any == 2: 74.9
if both idle_mode == 0: (ground idle)
thr = cfm56_7b_ff_thr(650 / ff_corr_coef(alt_p, OAT), alt_p, M)
n2 = TotTR_n2 * cfm56_7b_cn1_to_cn2(cfm56_7b_cn1(thr, M), alt_p, M) / 14460 * 100
idle_n2 = max(n2_min, n2)
else: (flight / approach idle)
same with 440 instead of 650, n2 capped at 79
idle_n2 = max(n2_min, min(79, n2))
cn2 = idle_n2 * 0.01 * 14460 / TotTR_n2 (if TotTR_n2 != 0)
idle_n1 = corrected_cn1_to_n1(cfm56_7b_cn2_to_cn1(cn2, alt_p, M), TotTR)
if idle_n1 < 19.1: idle_n1 = 19.1
idle_n2 = TotTR_n2 * cfm56_7b_cn1_to_cn2(corrected_n1_to_cn1(idle_n1, alt_p, M, 15), alt_p, M) / 14460 * 100
alt_p = X-Plane pressure altitude (ft), M = Mach, P_amb =
sim/weather/aircraft/barometer_current_pas, TAT =
sim/cockpit2/temperature/outside_air_LE_temp_degc, rho = sim/weather/rho.
idle_n1/idle_n2 are single values for both engines (the mode of either
engine raises both). 14460 rpm = 100 % N2.
4.5 Thr_shift — EEC N1 limit and idle throttle#
Per engine; egt_max = max(0, egt_rnd[0], egt_rnd[1]):
if eec_mode < 0: n1_lim = 105
elif reverser engaged (prop_mode == 3): n1_lim = 104
else:
theta = TotTR (eec_mode 1)
if eec_mode == 0: theta = (calc_tat_by_mach(calc_tamb_by_alt(alt_p), M) + 273.15) / 288.15
if eec_mode >= 2: theta = (calc_tat_by_mach(eec_last_oat[i], M) + 273.15) / (eec_last_slt[i] + 273.15)
n1_egt = corrected_cn1_to_n1(cfm56_7b_cegt_to_cn1((948 + egt_max + 273.15) / TotTR_egt, alt_p, M), theta)
cn2_lim = 15183 / theta^0.45 (15183 if the power is 0; theta<0 → theta itself)
n1_n2 = corrected_cn1_to_n1(cfm56_7b_cn2_to_cn1(cn2_lim, alt_p, M), theta)
n1_lim = Limit(min(n1_egt, n1_n2), 90, 104)
eec_n1_lim_thr_use[i] = min(1.12, n1_to_thr_use(n1_lim, -1))
max_thrust_use[i] = eec_n1_lim_thr_use[i]
shift_thr[i] = n1_to_thr_use(idle_n1, i) (throttle-use at idle)
(For eec_mode == 1 the corrected-N1 conversion of n1_egt uses TotTR; the
EGT redline in the limit is 948 °C + the random offset.)
4.6 N1 ↔ throttle-use mapping and Eicas_thrust#
Mapping (thr_use_to_n1, n1_to_thr_use; engine index i, i < 0 = "no
engine"):
ice_fac(i) = max(0.3, 1 − 0.65 * rel_eng_ice[i]^2) (X-Plane inlet ice ratio)
cap(i) = (i < 0) ? 1.12 * ice_fac : max_thrust_use[i] * ice_fac
thr_use_to_n1(u, i) = 19.0758705 + (99.9930954 − 19.0758705) * Limit(u, 0, cap(i))^(1/1.62)
n1_to_thr_use(n, i) = Limit( ((Limit(n, 19.0758705, 108) − 19.0758705) / 80.9172249)^1.62 , 0, cap(i))
(i < 0 uses ice_fac of engine 0 memory layout: the code indexes
rel_eng_ice[-1]; Zibo only calls it with -1 from Thr_shift, where the
value is then capped at 1.12 — see §9.)
Lever → throttle use (Thr_lever_to_use(lever, idle_mode, prop_mode, i)):
a = shift_thr[i]; b = 0.25 * max_thrust_use[i]
if prop_mode == 3 (reverse): Rescale(0.06, a, 1.0, max_rev_thrust_use * ice_fac(i), lever)
elif lever >= 0.25: Rescale(0.25, b, 1.0, max_thrust_use[i] * ice_fac(i), lever)
else: Rescale(0.0, a, 0.25, b, lever)
Eicas_thrust (runs only when the X-Plane ENGN_thro_use, OAT and the
Lua eng*_req_n1 datarefs exist), per engine:
lever = thrust_lever[i] (laminar/B738/engine/thrust1/2_leveler)
if prop_mode[i] == 3: lever = (reverser deploy[i] >= 0.6) ? reverse_lever[i] : 0.06
n1_cmd = thr_use_to_n1(Thr_lever_to_use(lever, idle_mode[i], prop_mode[i], i), i) → eng*_req_n1 = n1_cmd/100
n1_now = thr_use_to_n1(ENGN_thro_use[i], i) (current commanded N1)
sig = 1 / Rescale(0.235, 1.5, 1.0, 1.0, Limit(sigma, 0.235, 1))
if n1_cmd − eng_N1_idx[i] > 0: (accelerating)
t = (prop_mode==3) ? 0.06*sig : (n1_now >= 33) ? Rescale(1089, 0.5, 1600, 0.06*sig, n1_now^2) : 0.5*eng_diff_stab[i]
else: (decelerating)
t = (n1_now >= 33 or prop_mode==3) ? 0.16*sig : 0.25*sig
n1_new = B738_set_animation_rate(n1_now, n1_cmd, 0, 108, t)
ENGN_thro_use[i] = n1_to_thr_use(n1_new, i) (written for both engines)
Also written: eng*_req_n1, eng*_req_pie, thr_lvr* (values from the same
loop), and the N1 limit bugs B738DR_engN_N1_lim = n1_to_thr_use(100 * fms_N1_thrust[i]), _lim2 = n1_to_thr_use(94 * fms_N1_thrust[i]).
B738_set_animation_rate(cur, tgt, lo, hi, t) moves by dt / t per frame
(t seconds per 1 % N1). At sea level (sig = 1): acceleration 2 %/s below
33 % N1 (divided by eng_diff_stab), rising with N1² from 2 %/s at 33 % to
16.7 %/s at 40 % N1 and above; deceleration 4 %/s below 33 % and 6.25 %/s
above (and in reverse). sig is 1 at sigma ≥ 1 and 1/1.5 at sigma ≤ 0.235,
i.e. the times shrink (faster spool) at altitude, except the sub-33 %
acceleration which does not use sig.
4.7 Xbleed_start — in-flight start envelope#
alt = Limit(elevation_m * 3.281, 0, 24000)
if alt >= 24000: xbld_start1 = xbld_start2 = wms = 0
else:
vmin = (alt < 15000) ? 140 : (alt >= 20000) ? 220 : Rescale(15000,140,20000,220,alt)
vmax = (alt < 15000) ? 300 : Rescale(15000,300,24000,281,alt)
if radio_alt <= 50: xbld_start1 = xbld_start2 = 0
elif vmin <= IAS <= vmax:
xbld_start1 = (eng_N1_idx[0] < 17 and eng_N1_idx[1] > 17 and running[1]) ? 1 : 0
xbld_start2 = symmetric
wms = (IAS > vmax) ? 1 : 0 (windmill start possible)
B738DR_xbld_start1/2, B738DR_wms ← values
(Outside vmin…vmax the X-BLD flags keep their previous value.)
4.8 Starter — starter air, N2/N1 dynamics, windmilling, vibration, fan#
Runs only if X-Plane's elevation, starter torque, starter max rpm and N2 datarefs exist.
4.8.1 Ambient-dependent rates (s = Limit(sigma, 0.635, 1)):
r_start = Rescale(0.635, 1.10, 1, 1.20, s) (starter N2 rate, %/s),
r_lightoff = Rescale(0.635, 1.45, 1, 1.55, s) (post-light-off N2 rate, %/s;
× Rescale(0, 1, 1, 0.9, precip) when precipitation on the aircraft > 0.1),
n2_motor = Rescale(0.635, 26.1, 1, 29.6, s) (maximum motoring N2, %).
4.8.2 Starter air → maximum motoring N2 per engine (pneumatics inputs
defined by the pneumatics spec; f(p) = Rescale(10.5, 0, 21.6, 1, p) on duct
pressure in psi):
apu = f(duct_L) * apu_bleed_valve * apu_establish_time
src0 = apu * n2_motor src1 = iso * apu * n2_motor
ext = engine_air_start ? (ext_air_duct > 21.6 ? 1 : Rescale(0,0,21.6,1,ext_air_duct)) : 0
src2 = iso * ext * 29.6 src3 = ext * 29.6
src4 = f(duct_L) * n2_motor * iso src5 = f(duct_R) * n2_motor * iso
load0 = lpack + iso*rpack ; load1 = rpack + iso*lpack (pack valve positions 0..1)
xs0 = iso * start_valve2 ; xs1 = iso * start_valve1 (other starter via isolation valve)
for i in 0,1:
loss = (load_i > 1) ? Rescale(1, 0.33, 2, 0.40, load_i) : Rescale(0, 0, 1, 0.33, load_i)
loss += 0.42 * xs_i
T[i] = max(0, max(src_i, src_{2+i}, src_{4+i}) * (1 − loss)) (engine 0: src0,src2,src4; engine 1: src1,src3,src5)
T95[i] = 0.946 * T[i]
starter_is_running[i] = (T[i] != 0)
B738DR_starter = 1 if any T != 0
iso = isolation valve position, duct_L/R = B738DR_real_duct_pressure_L/R.
4.8.3 Gauge_N1_N2_EGT() (steady-state targets, per engine):
if eng_N2_idx[i] <= 58: (start / sub-idle)
gauge_n1 = thr_use_to_n1(shift_thr[i], i)
cn1 = corrected_n1_to_cn1_tat(idle_n1, TAT, 15)
gauge_n2 = TotTR_n2 * cfm56_7b_cn1_to_cn2(cn1, alt_p, M) / 14460 * 100
gauge_egt = Rescale(0, TAT, 58, TotTR_egt * cfm56_7b_cn1_to_cegt(cn1, alt_p, M) − 273.15, eng_N2_idx)
else:
gauge_n1 = thr_use_to_n1(ENGN_thro_use[i], i)
cn1 = corrected_n1_to_cn1_tat(gauge_n1, TAT, 15)
gauge_n2 = TotTR_n2 * cfm56_7b_cn1_to_cn2(cn1, alt_p, M) / 14460 * 100
gauge_egt = TotTR_egt * cfm56_7b_cn1_to_cegt(cn1, alt_p, M) − 273.15
gauge_egt = Limit(gauge_egt, −56.5, 1200)
(on the very first frame gauge_egt starts at Limit(TAT, −30, 30))
4.8.4 N2 per engine (pos = start switch position, run =
ENGN_running[i], ign_ok = real_ignition[i] != 0 or eng_N2_idx[i] >= 56,
N2a = engine_N2_anim[i], driven[i] = "N2 set by starter/shutdown" flag):
lit = run and ign_ok
case pos == GRD (0):
if not lit:
if T < N2a: N2a = n2_shutdown(N2a, T)
elif N2a >= T95: N2a = Anim_linear_value2(N2a, T, 0, 100, 0.025 * r_start)
else: N2a = Anim_linear_value2(N2a, T95, 0, 100, r_start)
driven = true
else: POST_LIGHTOFF
case pos == FLT (3) and eng_N2_idx >= 11 and IAS > 180: (windmill/assisted start)
if not lit:
if T <= N2a: N2a = n2_shutdown(N2a, T)
else: N2a = Anim_linear_value2(N2a, T, 0, 100, 0.85 * r_start)
driven = true
else: POST_LIGHTOFF
otherwise (OFF, CONT, FLT outside the condition):
if not lit: N2a = n2_shutdown(N2a, 0) (not flagged driven)
else: POST_LIGHTOFF (without setting driven in the "follow gauge" case)
POST_LIGHTOFF:
if eng_N2_idx >= idle_n2 + 3 or not was_below50[i]: N2a = gauge_n2[i]
else: N2a = Anim_linear_value2(N2a, idle_n2 + 3, 0, 100, r_lightoff); driven = true (GRD/FLT cases only)
Then:
- Hung start (failure
engine_failure[23 + i]): if50 < N2a < 54andeng_was_off[i]→N2a = 50,was_hung_start[i] = 1. - If
eng_N2_idx <= 56: if not running →eng_was_off = 1, and clear a latched hung-start ([23+i]) or hot-start ([25+i]) failure (written back to the failure array). Ifeng_N2_idx > 56→eng_was_off = 0. was_below50[i] = 1ifN2a < 50or not running;= 0onceN2a > idle_n2 + 2.8.
n2_shutdown(n, floor) and n1_shutdown(n) (spool-down): below 59.8 % N2
(19.2 % N1) the value follows a time table (seconds since shutdown, ×1.3) —
the current value is converted to an elapsed time on the table, dt is
added, and the value at the new time is returned (tables §5.4). Above it,
N2 is converted to an N1-like value 100*(n/100)^3.11, decreased by
B738_set_animation_rate with 0.012 s/% (≥ 33) or 0.04 s/% (< 33), and
converted back with exponent 1/3.1; N1 above 19.2 % decreases the same way
directly. The result is never below floor.
4.8.5 N1 per engine (target written to X-Plane):
if not run:
if not driven: N1t = n1_shutdown(engine_N1_anim)
else: N1t = 100*(N2a/100)^3.11 ; if N1t < eng_N1_idx: N1t = n1_shutdown(engine_N1_anim)
elif engine_N1_anim < idle_n1 − 0.1 or was_below50:
if N2a <= idle_n2 − 0.3 or was_below50: N1t = 100*(N2a/100)^3.09
else: N1t = corrected_cn1_to_n1(cfm56_7b_cn2_to_cn1(gauge_n2*0.01*14460/TotTR_n2, alt_p, M), TotTR)
else: N1t = gauge_n1
4.8.6 Windmilling floor (only if either engine is not running): Mach
M' = M (0 below 0.01); band index k from 0.1 steps (1…9, 9 for M ≥ 0.8).
For k == 1 (Mach < 0.1) the lowest altitude band uses 0.01 instead of 0
as the first Mach breakpoint for the N1 table, and the head-wind component w = |wind_kt * cos(wind_from + 180 − heading_mag)| * 0.514 / 330 (Mach-equivalent, 0 below 0.01, ≤ 0.1) is used
instead of M' when larger (N1 table only). Altitude h = Limit(3.28 * elevation_m, 0, 45000) interpolates between table rows at 0, 15000, 25000,
35000, 45000 ft (tables §5.5; note the row pairs: 0–15000 uses rows 0/1,
15000–25000 rows 1/2, … and the interpolation is Rescale(0, rowA, X, rowB, h) with X = upper bound, i.e. always from 0 ft). For every non-running
engine: N1t = max(N1t, wm_n1), and if N2a < wm_n2: N2a = wm_n2 (also
stored in engine_N2_anim).
4.8.7 Publish N1/N2:
- Engine vibration event (
eng_vib_active[i], set by bird strike/flameout, §4.12) whileeng_vib_tick <= 0.1:N2a −= generate_rnd(0, 0.3 * N2a)(≥ 0), also into X-Plane's N2 reading.eng_vib_tick -= dt; when ≤ 0 it restarts atgenerate_rnd(2, 3)s. B738DR_engN_N2 = Anim_value(prev, N2a, 0, 108, 1)→eng_N2_idx; writeENGN_N2_ = engine_N2_anim.- Engine separated or failed (
rel_engsep/rel_engfai= 6): N1 target 0 and indicated N1 = 0. B738DR_engN_N1 = Anim_value(prev, N1t, 0, 108, 1)→eng_N1_idx;engine_N1_anim = N1t; writeENGN_N1_ = engine_N1_anim.- Write
acf_starter_torque_ratio = 0.07andacf_starter_max_rpm_ratio = 0.09every frame (keeps X-Plane's own starter negligible). engine_N2_old = engine_N2_anim.
So the indicated N1/N2 (laminar/B738/engine/indicators/N1_percent_1 etc.)
are first-order lags (time constant 1 s) of the values written to X-Plane.
4.8.8 Vibration indication (B738DR_eng1/2_vib, 0…5 units): target
v = Rescale(0,0,10,181,N1) below 10 % N1, Rescale(10,181,37,121,N1) to
37 %, Rescale(37,121,105,147,N1) above (in 1/100 units); during a vibration
event v = min(500, 3 v); indication = Anim_value(100*vib, v, 0, 500, 3) / 100; separated engine → 0.
4.8.9 Fan rotation (laminar/B738/engine/rot_spd0/1,
rot_ang0/1, exterior/fan animation): w = eng_N1_idx / 0.1845273 (rad/s);
if X-Plane's engine_rotation_speed_rad_sec >= w use X-Plane's speed and angle;
else w = 0 below 0.04, rot_spd = Anim_value(rot_spd, w, 0, 45, 0.65),
rot_ang = fmod(rot_ang + dt * 57.3 * rot_spd, 360); separated → speed 0.
4.9 Engine_calculation — fuel metering, fuel flow, EGT, oil#
Per engine, fuel_open[i] = spar_valve_i >= 0.95 and eng_valve_i >= 0.95
(fuel spar and engine fuel valves, fuel spec). First frame: initialise the
memories (metering_valve = 0, coef_egt = ws_coef_egt = 1, …).
Fuel flow indication (laminar/B738/engine/fuel_flow_kg_sec[i]), not in
replay:
if running went 0 → 1 this frame: fuel_flow_choke = 1
if not running: ff = 0
target_valve = (eng_N2_idx > 19 and fuel_open) ? 1 : 0
metering_valve = B738_set_animation_rate(metering_valve, target_valve, 0, 1, 2) (2 s full travel)
if engine separated or not running: ff = 0
elif metering_valve < 0.99 or not running: ff → 0 with Anim_value(k = 1/s) (in 0.01 kg/s units)
elif fuel_flow_choke == 1: (light-off surge)
tgt = max(0.09722, start_ff + 0.01389); ff → tgt at 0.154 s per 0.01 kg/s; when reached: choke = 2
elif fuel_flow_choke == 2:
tgt = max(0.082177, start_ff); ff → tgt at 0.333 s per 0.01 kg/s; when reached: choke = 0
else:
tgt = ENGN fuel flow from X-Plane (fuel_flow_kg_sec, which is Zibo's own ENGN_FF_ written by Engine_model)
if eng_N2_idx < 58.5: if eng_N2_idx > 46.2: tgt = Rescale(46.2, start_ff, 58.5, start_ff + 0.02778, N2)
tgt = max(tgt, 0.082177)
ff = Anim_value(ff, tgt, k = 1/s)
replay: ff = X-Plane fuel flow, fuel_open treated as true.
start_ff[i] = idle fuel flow computed by Engine_model (§4.10).
Fuel available per side for the EGT/flameout logic: X-Plane
fuel_selector_l/r 0 → none; 1/2/3 → tank 1/2/3 must hold > 150 kg; other →
any tank > 150 kg.
Engine lost (fuel starvation): if the engine was running last frame, is
not running now, eng_N2_idx > 85, and fuel is open and available →
engine_lost = 1, eng_egt_lost_tgt = min(1000, EGT + Rescale(57, 100, 101.4, 140, N2)), eng_n2_lost_tgt = eng_N2_idx. Cleared when running or
N2 < 42.
EGT (laminar/B738/engine/eng1_egt/eng2_egt, °C): target E*, rate
coefficient c:
c = Rescale(0, 1, 0.82, 2, Limit(M, 0, 0.82))
if eng_N2_idx > 58: ws_coef_egt = 1
coef_egt, ws_coef_egt clamped to [1,4] and [1,3]
if not running:
E* = TAT, except during motoring between 28 and 29.8 % N2 with N2 rising and fuel closed:
E* = Limit(EGT − egt_rnd + Rescale(28,0,29.7,5,N2), TAT, TAT + 123 − egt_rnd)
saved_egt = 0 ; coef_egt → 1 (Anim_value k=1)
fuel closed: ws_coef_egt −= dt * (N2 < 25 ? 0.01 : 0.03) while > 1 (cools)
fuel open: ws_coef_egt += dt * Rescale(225, 0.25, 625, 0, N2^2) while < 3 (wet start: fuel without light-off)
elif eng_N2_idx >= 56.5 or X-Plane fuel flow >= 0.1: E* = gauge_egt ; saved_egt = 0 ; coef_egt → 1
elif fuel closed or (N2 < 28 and X-Plane FF < 0.0279):
if 28 < N2 <= 29.7 and N2 rising: E* = Limit(EGT − egt_rnd + Rescale(28,0,29.7,4,N2), TAT, TAT + 123 − egt_rnd)
saved_egt = 0 ; coef_egt → 1
elif N2 < 28 or N2 falling: E* = TAT ; saved_egt = 0 ; coef_egt → 1
else (lit, accelerating, N2 28…56.5):
if not saved_egt: motoring_eng_egt = max(TAT, calc_start_egt(N2, EGT)); saved_egt = 1
E* = max(egt_start(N2, motoring_eng_egt, i), EGT − egt_rnd)
E* = max(E*, TAT − egt_rnd, −egt_rnd) + egt_rnd ; E* = max(E*, TAT)
c *= (N2 < 30) ? Rescale(0,1,58,1.2,N2) : (N2 < 58) ? Rescale(0,1.2,58,2,N2) : Rescale(58,2,100,3,N2) ; c = Limit(c, 1, 3.5)
calc_start_egt(N2, …) / egt_start(N2, base, i): piecewise-linear start EGT
rise above the motoring EGT against N2 (tables §5.6). egt_start scales the
table by coef_egt[i], which rises (k = 2/s up, 1/s down, range 1…4) towards
Limit(m * Rescale(ws_lo, 0, ws_hi, ws, N2), 1, 4) with ws = ws_coef_egt,
ws_lo = Rescale(1,28,3,38,ws), ws_hi = Rescale(1,28,3,49,ws),
m = Rescale(0, 1, 1, 4, max(thrust lever, reverse lever)) (opening the
thrust lever during the start raises EGT up to ×4), and m = 3 with the
hot-start failure (engine_failure[25 + i], sets was_hot_start).
EGT update:
if separated: EGT = 0
elif engine_lost == 1:
if EGT >= eng_egt_lost_tgt − 45: engine_lost = 2
else: E* = eng_egt_lost_tgt; X-Plane N2, ENGN_N2_ and engine_N2_anim held at eng_n2_lost_tgt
EGT = Anim_value(EGT, E*, 0, 1000, 0.3)
elif EGT <= E* or ENGN burning fuel:
if N2 >= 56.5 or N2 falling or E* <= TAT or E* < EGT:
k = (EGT <= 180) ? Rescale(100, 0.0009c, 180, 0.0141c, EGT) : Rescale(180, 0.0141c, 900, 0.2c, EGT)
EGT = Animate_physical3(EGT, E*, 0, 1000, eng_accel_egt, 0.1, 100, 0.3, k) (second-order, §5.1)
elif fuel open or N2 <= 28 or N2 > 29.8 or N2 falling: EGT = Anim_value(EGT, E*, 0, 1000, 200)
else: EGT = Anim_value(EGT, E*, 0, 1000, 0.00155 c)
else: (cooling, not burning)
k as above (two ranges); EGT = Anim_value(EGT, E*, 0, 1000, k)
if the second-order branch was not used: eng_accel_egt = 0
first frame: EGT = E*
Oil pressure (laminar/B738/engine/eng1_oil_press, psi), p0 = current_inHg / 2.03602 (ambient psi):
N2 < 20.2: Rescale(0, 0, 20.2, 15.7, N2) − p0
N2 < 30.7: Rescale(20.2, 15.7, 30.7, 20.7, N2) − p0
N2 < 38.2: Rescale(30.7, 20.7, 38.2, 27.7, N2) − p0
N2 < 58.9: Rescale(38.2, 27.7, 58.9, 39.7, N2) − p0
else: 39.7 + 1.3223529 * (N2 − 58.9) − p0
≥ 0; oil pump failed (rel_oilpmp) or engine separated → 0
oil quantity ratio < 0.55: × Rescale(0.05, 0, 0.55, 1, qty) (linear fade to 0 at 5 %)
Oil temperature (°C) from X-Plane's oil temperature T_xp:
T* = (T_xp >= 31) ? 69 + 0.66 (T_xp − 31) : Rescale(0, 0, 31, 69, T_xp),
T* = max(T*, TAT, 0); if burning fuel and oil pressure ≤ 14.5 psi: T* = Rescale(0, 200, 14.5, T*, p) and T = Anim_linear_value2(T, T*, 0, 200, 0.26); else T = Anim_value(T, T*, 0, 200, 0.4); separated → 0.
4.10 Engine_model — thrust and fuel flow to X-Plane (branch 4)#
Writes once: override_engine_forces = 1, override_fuel_flow = 1.
for i:
u = ENGN_thro_use[i]
if u < 0.25 * max_thrust_use[i]:
flt_idle = idle_n1 * (1 + Rescale(sigma_lo, f_lo, sigma_hi, f_hi, sigma)) (flight-idle sigma table, §5.3)
u = Rescale(shift_thr[i], n1_to_thr_use(flt_idle, i), 0.25*max_thrust_use[i], 0.25*max_thrust_use[i], u)
n1c = thr_use_to_n1(u, i)
F = burning[i] * cfm56_7b_thrust(corrected_n1_to_cn1_tat(n1c, TAT, 15), M) (lbf, corrected)
F = (static_thrust / 27000) * (F + 3700) − 3700 ; F = AmbPR * F * 4.44822162 (N)
ff = burning * corrected_ff2(cfm56_7b_fuel_flow(burning * cfm56_7b_thrust(corrected_n1_to_cn1_tat(eng_N1_idx, TAT, 15), M), alt_p, M) * 0.45359237 / 3600, alt_p, OAT)
if ff < 0.0415793 and burning and airborne: ff = 0.0415793 (kg/s floor = 330 lb/h)
ENGN_FF_[i] = ff
start_ff[i] = same chain with idle_n1 instead of eng_N1_idx (idle fuel flow)
on ground (per engine, uses indicated N1 eng_N1_idx):
F += burning * ( N1<20: Rescale(0,0,20,1500,N1) ; N1<40: Rescale(20,1500,40,11800,N1) ;
N1<50: 11800 ; N1<90: Rescale(50,11800,90,0,N1) ; else 0 )
if N1 < 35: F −= F * Rescale(19, Rescale(40000, 0, 70000, 0.89 * Rescale(0,1,5,0,GS_kt), weight_kg), 35, 0, N1)
reverse multiplier R = reverse_coef * (GS_kt < 60 ? 1 : GS_kt >= 80 ? 1.3 : Rescale(60,1,80,1.3,GS_kt))
per engine, d = reverser deploy ratio:
Fx[i] = (d >= 0.5) ? Rescale(0.5, 0, 1, F*R, d) : Rescale(0, −F, 0.5, 0, d) (−F = forward thrust along −x)
faxil_prop = Fx[0] + Fx[1]
fnrml_prop = −faxil_prop * 0.0874886627 (thrust line inclination, ~5°)
fside_prop = 0.06139626 * (Fx[1] − Fx[0])
L_prop = 5.498948495 * fside_prop ; M_prop = −1.69597236585 * faxil_prop ; N_prop = 82.23832144 * fside_prop
write faxil/fnrml/fside/L/M/N_prop and ENGN_FF_[0..1]
(burning = X-Plane engine_is_burning_fuel[i]; GS_kt = groundspeed ×
1.94384449244; weight_kg = total weight. The ground term models taxi
thrust/ground effects of Zibo's taxi tuning; values exactly as given.) The
X-Plane axis convention: faxil positive aft, so forward thrust is negative.
4.11 Systems — start switches, start valves, ignition, EICAS energise#
Runs at step 19 of the systems block (after Starter).
4.11.1 Start switch knob animation (per engine): an internal knob angle
knob[i] (0 GRD, 1 OFF, 2 CONT) moves towards engine_starter_tgt[i] with
B738_set_animation_rate(…, 0.15) (0.15 s per position) and produces the
published starter_pos (0 GRD, 1 OFF, 2 CONT, 3 FLT) and starter_pos2
(OFF-detent helper, 1 at OFF):
if tgt == knob: pos = knob
elif tgt == 0: knob → 0 (range 0..1); pos, pos2 = (knob >= 0.5) ? (1, 1) : (2*knob, 0)
elif tgt == 1:
knob < 0.2: knob → 1 (range 0..1); pos = 5*knob ; pos2 = 0
knob < 1: knob → 1 (range 0..1); pos = 1 ; pos2 = 1
knob < 1.2: knob → 1 (range 1..2); pos = 1 ; pos2 = Rescale(1, 1, 1.2, 0, knob)
else: knob → 1 (range 1..2); pos = Rescale(1.2, 1, 2, 2, knob) ; pos2 = 0
elif tgt == 2: knob → 2 (range 1..2); pos, pos2 = (knob >= 1.5) ? (Rescale(1.5,1,2,2,knob), 0) : (1, Rescale(1,1,1.5,0,knob))
(tgt == 3 has no branch: pos keeps what the command wrote)
Commands (on command begin; engine 2 symmetric): knob/eng1_start_left
(SCI ENGINE_1_STARTER DEC): pos 1 → tgt 0; pos 2 → tgt 1; pos 3 → pos = 2,
pos2 = 0, tgt = knob = 2 at once. knob/eng1_start_right (INC): pos 0 → tgt 1;
pos 1 → tgt 2; pos 2 → pos = 3, pos2 = 0, tgt = knob = 3 at once.
rotary/eng1_start_grd|off|cont|flt: pos = 0|1|2|3, pos2 = 0|1|0|0, tgt =
same (the knob angle is not changed, so the next Systems frame re-animates
pos from the knob for GRD/OFF/CONT). IGN select
toggle_switch/eng_start_source_left/right steps
B738DR_eng_start_source −1 (IGN L) / 0 (BOTH) / 1 (IGN R).
4.11.2 Per engine logic (pos, N2 = eng_N2_idx, lever = start lever,
src = 1 for IGN L, 3 for BOTH, 2 for IGN R; ignition[i] 0 none, 1 left
igniter, 2 right igniter, 3 both):
first frame: engine_on_old2 = burning ; engine_start_enable_timer = 0 ; fuel_selector_enable = 1
starter_engaged = false
pos GRD (0):
starter_engaged = true
if 55.3 < N2 < 58: engine_starter_tgt = 1 ← solenoid release: knob returns to OFF
if lever > 0.5: ignition = airborne ? 3 : src
if N2 < 50: egt_redline = 1 ; TMR_egt_redline = 0
pos OFF (1):
ignition = 0
if burning and lever > 0.5:
if 50 < N2 < 57: ignition = 3
if airborne and 5 < N2 < 54: ignition = 3 (auto-relight)
if engine_start_auto option and (burning or N2 > 25):
if altitude >= 18000 or flaps == 0: if engine anti-ice on: ignition = src
else: ignition = src
pos CONT (2): ignition = 0 ; starter_engaged = true ; if lever > 0.5: ignition = airborne ? (N2 < 54 ? 3 : 0) : src
pos FLT (3): ignition = 0 ; starter_engaged = true ; if lever > 0.5: ignition = 3
engine fire, failure or separation (= 6): ignition = 0
restart lock-out:
if not burning and engine_on_old2 and N2 <= 40: engine_start_enable_timer = 5 ; engine_on_old2 = 0
elif lever < 0.5 and timer != 0: timer −= dt (to 0) ← counts only with the start lever in CUTOFF
if timer == 0: fuel_selector_enable = 1 ; if burning and N2 >= 54: engine_on_old2 = 1
else: ignition = 0 ; if ENGN_running: ENGN_running = 0 (written) ; fuel_selector_enable = 0
4.11.3 Igniter power: left igniter of engine 1 from AC transfer bus 1
(engine 2: transfer bus 2), right igniters from the AC standby bus. If the
battery bus (B738DR_dir_batbus_status) is unpowered: ignition = 2 if the
selection includes the right igniter and AC standby is powered, else 0.
Otherwise remove the unpowered igniter (3 → 2 or 1, 1 → 0, 2 → 0).
4.11.4 EGT start limit and hot start (per engine, ff = EICAS FF
energised flag B738DR_eicas_ffN, egt = indicated EGT):
if ff == 0: TMR_egt_redline = 0 ; egt_redline = 0
elif N2 <= 59 or egt >= 725:
if pos != GRD and N2 < 5 and egt_redline and TMR_egt_redline == 0: TMR_egt_redline = 4
elif egt_redline and TMR_egt_redline == 0: TMR_egt_redline = 2
B738DR_egt_redline1/2 = egt_redline (start-limit red line shown on the EGT gauge)
hot_start_warn = 1 if egt > 585 and egt rising and N2 < 58 and burning;
= 0 if not burning, or egt < 725 and falling (while burning); otherwise unchanged
(above 725 C it stays set) [corrected 2026-11-03 from zibomod Systems]
if hot_start_warn: egt_hotstart = (egt < 725) ? blink : 2 ;
if eng_flameout_timer == 0: eng_flameout_timer = generate_rnd(1, 3)
eec_hotstart (a latch; lever = start lever):
= 0 if EEC failed (6) or airborne or lever == 0 or N2 < 23
= 1 elif hot_start_warn and egt > 710 ; otherwise unchanged
(oracle: wet start aborted at EGT 711 C, ignition off, N2 hangs at 29.6 %, EGT falls)
if eec_hotstart: ignition = 0 (EEC auto-aborts a ground hot start)
B738DR_egt_hotstart1/2 = egt_hotstart
4.11.5 Ground-air start annunciation B738DR_air_starter (0…5; ASU
connected and ext_air_duct > 25): with isolation valve ≥ 0.05: only engine 1
in GRD → 1 (packs off) / 4 (a pack on); only engine 2 in GRD → 2 / 5; both in
GRD → 3; with isolation closed: engine 2 in GRD → 2 (right pack off) / 5.
4.11.6 Ignition to X-Plane:
real_ignition[i] = (N2 >= 25) ? ignition[i] : 0
if airborne and B738DR_wms and pos == FLT: real_ignition[i] = ignition[i] (windmill start)
sim/cockpit2/engine/actuators/ignition_key[i] = real_ignition[i] (written on change)
igniter_on[i] = 1 if real_ignition != 0 ; = 0 if real_ignition == 0 and starter-N2 < 54 ; else unchanged
auto_ignite_on[] forced 0
4.11.7 Start valves (laminar/B738/engine/start_valve1/2, 0…1, drive
the START VALVE OPEN indication and the pneumatic load): if pos == GRD and
the battery bus is powered → open linearly in 2.5 s
(B738_set_anim_time(v, 1, 0, 1, dir 1, 2.5)), else close in 2.5 s;
engineN_starter_valve = Anim_value(·, 0/1, 0, 1, 1) (first-order copy used
by the pneumatics). The duct pressure drop caused by the starters and the APU
bleed deficit while a start valve is open (−25 psi × (1 − apu_establish_time) when start_valve > 0.1) belong to the pneumatics spec.
4.11.8 EICAS energise (oil pressure/temperature/fuel flow readouts appear
when an engine is started): energise[i] = starter_engaged or running or ignition != 0. If energised: TMR_energize += dt up to 4 s; < 1.6 s: oil
pressure, oil temperature, FF readouts off; 1.6…2 s: oil pressure and FF
on; ≥ 2 s: all on. If not energised: TMR_energize −= dt (only while N2 <
6, to 0); < 0.3 s: all off; 0.3…0.7 s: oil temperature off. Flags
B738DR_eicas_oil_press1/2, _oil_temp1/2, _ff1/2.
4.12 Failures and flameout (manage_eng_failure, eng_flameout_effect, engine_fail)#
The EFB/instructor failures live in laminar/B738/failure/engine (70 floats,
1 = active). Engine-related indices:
| Index | Failure | Effect in Zibo |
|---|---|---|
| 0 / 1 | engine 1/2 fire | X-Plane rel_engfir0/1 = 6 |
| 2 / 3 | engine 1/2 failure (flameout) | if running: flicker timer generate_rnd(3, 7) s; X-Plane rel_engfai = 6; after generate_rnd(30, 90) s the failure clears itself (index and X-Plane failure back to 0) |
| 4 / 5 | engine 1/2 severe vibration | only while not running and not failed: with probability 95 % (rnd(0,200) >= 10) a vibration phase of rnd(15, 240) s (eng_vib_active, N2 dips §4.8.7), counted down faster with higher vibration (dt × Rescale(0, r, 3.7, 1, vib) below 3.7 units, × Rescale(3.7, 1, 5, 3, vib) above; r = 0.5 below 50 s left, Rescale(50, 0.5, 150, 0, t) below 150 s, else 0), then (or immediately in 5 %) rel_engfai = 6 |
| 6 / 7 | engine 1/2 separation / seizure | X-Plane rel_engsep = 6; N1, N2 (X-Plane, internal and indicated) forced 0 every frame |
| 8 / 9 | generator 1/2 | X-Plane generator failure (electrics spec) |
| 10 / 11 | oil pump 1/2 | X-Plane rel_oilpmp = 6 → oil pressure 0 |
| 12 / 13 | oil leak 1/2 | X-Plane oil quantity ratio −0.00625/s (written) |
| 14 / 15 | EEC normal-mode inputs | soft ALTN after 15 s, approach idle below 15000 ft (§4.3) |
| 16 / 17 | EEC failure | X-Plane rel_fadec = 6 → EEC mode −1, EEC switch forced OFF |
| 23 / 24 | hung start | N2 stopped at 50 % (§4.8.4); cleared when N2 ≤ 56 and not running |
| 25 / 26 | hot start | start EGT coefficient 3 (§4.9); cleared like the hung start |
| 27 / 28 | bird strike 1/2 | X-Plane rel_bird_strike_engN = 6 |
manage_eng_failure also turns sustained abnormal oil into seizure (index
6/7): oil temperature ≥ 165 °C while burning for 180 s cumulative, or oil
pressure ≤ 13 psi while burning for 300 s cumulative (timers count back down
at 1 s/s when the condition clears).
eng_flameout_effect (bird strike with engine failed and N2 > 45 → clear the
X-Plane bird-strike failure, flicker timer generate_rnd(5, 10) s). While a
flicker timer runs, laminar/B738/engine/eng_flameout[i] (exterior flame /
sound effect) toggles: on for rnd(0.3, 0.7) s, off for rnd(0.5, 2.5) s.
engine_fail(t) (2 Hz, t = accumulated time since the last call ≈ 0.5 s):
- N1 overspeed: timer +=
tabove 103.5 % N1, +=t × Rescale(102.4, 0.5, 103.5, 1, N1)from 102.4 % (engine 2: from 102.1 %, Zibo asymmetry), −=0.33 tbelow; > 450 s → X-Plane engine failurerel_engfai = 6. - Suction-feed flameout (airborne, elevation > 8500 m, not
start_sim_time): a running engine whose side has no operating boost pump on a non-empty tank (L1/L2/C1 for engine 1; with crossfeed open also R1/R2/C2, and symmetric) startsT = Rescale(8500, 120, 12500, 12, elevation_m) + 30 × rnds; while0 < T <= 10the engine is forced off (ENGN_running = 0, written) andB738DR_fuel_selector_l/r = 0. Not running with the start lever in CUTOFF clears the timer. - Heavy rain / hail flameout: precipitation or hail > 0.7, or > 0.2 with
−12 °C < TAT < 0: an unprotected running engine gets
T = rnd(80, 180) × Rescale(0.2, 1.8, 1, 0.7, precip)s (+20 s if within 16 s of the other engine's timer) and is forced off while0 < T <= 10. Protected = start switch CONT/FLT, or ≥ OFF when the auto-start option or OFF itself is selected — in effect only GRD is unprotected (Zibo's condition; engine 2 tests engine 1's switch for the OFF case, and its 16 s test compares its own timer with itself, so it always gets +20 s). Reproduce as written.
4.13 N1_N2_idle (2 Hz)#
When X-Plane's sigma, low_idle_ratio and high_idle_ratio exist, forces
X-Plane's idle ratios to low_idle_ratio = 0.60, high_idle_ratio = 0.67
(re-written whenever they differ). Irrelevant for the MSFS rewrite except as
a statement that X-Plane's idle is not used.
4.14 Reversers (engine side)#
- Commands (SCI
ENGAGE/DISENGAGE_REVERSE_1/2/BOTH,sim/engines/ thrust_reverse_toggle*,…_hold*; ignored whilethrottle_override): toggle: ifprop_mode == 1(normal) and thrust lever < 0.01 →prop_mode = 3, thrust lever 0; ifprop_mode == 3, reverse lever ≤ 0.01 and not max-hold →prop_mode = 1, reverse lever 0. Hold: same engagement, setsreverse_max_enable/on(released on command end). - Reverse lever (
laminar/B738/flt_ctrls/reverse_lever1/2, SCIREVERSE_THROTTLE_1/2): writing it runsreverse_levelN: lever 0 withprop_mode 3→prop_mode 1and X-Plane throttle 0; lever ≥ 0.06 withprop_mode != 3→prop_mode 3; lever clamped to 0.06 while Zibo's own reverser state is not 3; in reverse, X-Planethrottle_ratio = Rescale(0.06, 0, 1, 1, lever). Systems: Zibo's reverser state follows X-Planeprop_mode(stowing always, deploying only on ground or radio height < 10 ft) when hydraulic system A (engine 1; B for engine 2) or standby has pressure and the fire switch is not pulled. SleeveB738DR_reverse_deploy(published aslaminar/B738/engines/thrust_reverser_deploy_ratio): deploy 1.8 s / stow 2.0 s on system A/B, 9.6 s / 12 s on standby only (linear).- Thrust: reverse thrust only through
Engine_model(§4.10, deploy ≥ 0.5,reverse_coef = 0.23, ×1.3 above 80 kt), andThr_lever_to_usein reverse (§4.6,max_rev_thrust_use; Zibo's.datainitial value 0.57 is replaced by 0.69 inInit_engine_param).
5 Constants, tables, timings#
5.1 Helper functions (exact semantics)#
Rescale(x0, y0, x1, y1, x): linear interpolation from (x0,y0) to (x1,y1), clamped toy0/y1outside[x0, x1](works forx1 < x0too; returnsy0ifx0 == x1).Limit(x, lo, hi): clamp (iflo > hiandx > hireturnshi).Anim_value(cur, tgt, lo, hi, k):c = Limit(cur); iftgt >= hi − 0.001andc >= hi − 0.01→hi; iftgt <= lo + 0.001andc <= lo + 0.01→lo; elsec + dt · k · (tgt − c)without overshootingtgt, clamped.Anim_linear_value2(cur, tgt, lo, hi, rate): same end snapping; else moves linearly byrate · dttowardstgt.B738_set_animation_rate(cur, tgt, lo, hi, t):t <= 0→ 1. Iftgt >= hi − 0.001andcur >= hi − 0.01→hi; iftgt <= lo + 0.001andcur <= lo + 0.01→lo; ifcur > hi→hi; ifcur < lo→lo; else moves bydt / ttowardstgtwithout overshoot.B738_set_anim_time(cur, tgt, lo, hi, dir, T): moves bydt/T · |lo − hi|towardstgt(down ifdir == 0, up otherwise), clamped to[lo, hi].generate_rnd(a, b) = a + (b − a) · (rand() % 100000) · 1e−5(Crand(), seeded withsrand(time)on the first frame).Animate_physical3(cur, tgt, lo, hi, a&, p5, p6, p7, p8): second-order EGT follower used in §4.9 (statea=eng_accel_egt[i], called with p5 0.1, p6 100, p7 0.3, p8 the ratek); specified in §10.2 (M3c).- CFM56 building blocks (separate work package):
cfm56_7b_cn1,cfm56_7b_cn1_to_cn2,cfm56_7b_cn2_to_cn1,cfm56_7b_cn1_to_cegt,cfm56_7b_cegt_to_cn1,cfm56_7b_thrust,cfm56_7b_fuel_flow,cfm56_7b_ff_thr,corrected_n1_to_cn1,corrected_n1_to_cn1_tat,corrected_cn1_to_n1,ff_corr_coef,corrected_ff2,calc_tamb_by_alt,calc_tat_by_mach. Call sites and arguments are those in §4.
5.2 Ambient and scaling#
| Quantity | Value |
|---|---|
| N2 100 % | 14460 rpm (corrected N2 = N2 %·144.6/θ^0.45) |
| θ exponents | N2 0.45, EGT 0.16 |
| N2 limit (EEC) | 15183 rpm corrected / θ^0.45 |
| EGT limit (EEC) | 948 °C + max(0, egt_rnd) |
| EEC N1 limit | 90…104 % (104 in reverse, 105 with EEC failed) |
max_thrust_use |
1.12 (then the EEC limit), max_rev_thrust_use 0.69 |
| N1 ↔ throttle | N1 = 19.0758705 + 80.9172249 · u^(1/1.62) |
| Idle N2 floors | ground 58.4 %, flight 72.1 %, approach 74.9 %, flight cap 79 % |
Idle fuel flows into cfm56_7b_ff_thr |
ground 650, flight 440 (divided by ff_corr_coef) |
| Idle N1 floor | 19.1 % |
| sigma clamp | 0.235…1.0 |
| Static thrust | B738DR_static_thrust 27300 lbf (scaling (F+3700)·27300/27000−3700) |
| Reverse coefficient | 0.23 (×1.3 above 80 kt) |
| Ground-idle hold after touchdown | 5.5 s (2.5 s SFP) |
| EEC soft→hard ALTN | 15 s |
| Starter knob | 0.15 s per position; release window 55.3 < N2 < 58 % |
| Start valve travel | 2.5 s |
| Restart lock-out | 5 s with start lever in CUTOFF after the engine ran down below 40 % N2 |
| Igniters to X-Plane | only from 25 % N2 (FLT windmill start: any N2) |
| Metering valve | 2 s full travel, opens at N2 > 19 % with fuel valves open |
| Fuel flow minimum (indication) | 0.082177 kg/s; light-off surge start_ff + 0.01389 (≥ 0.09722) |
| Fuel flow floor to X-Plane | 0.0415793 kg/s when burning and airborne |
| Starter torque/rpm to X-Plane | 0.07 / 0.09 |
| Windmill start envelope | §4.7 |
| Indicated N1/N2 lag | Anim_value k = 1/s |
| Engine model flight-idle increment vs sigma | σ 0.247144: 0.085, 0.310649: 0.035, 0.374827: 0.02, 0.448579: 0.02, 0.533304: 0.02, 0.629423: 0.034, 0.738808: 0.05, 1.0: 0.12 (flt_idle_* datarefs; linear between, clamped) |
Engine model .data defaults |
sfc 0.038, idle_sfc 0.178, sfc_coef 0.008, bypass 0.8, inlet 0.8, shock_mach 0.9, curve02_coef1/2 2.6/2.75 (used by the inactive branches 2/3/else only) |
5.3 Starter#
| Quantity | sigma 0.635 | sigma 1.0 |
|---|---|---|
Starter N2 rate r_start (%/s) |
1.10 | 1.20 |
Post-light-off N2 rate r_lightoff (%/s, ×0.9 in heavy rain) |
1.45 | 1.55 |
Max motoring N2 n2_motor (%) |
26.1 | 29.6 |
Duct pressure factor 0 at 10.5 psi → 1 at 21.6 psi. Ground air (ASU) motoring
N2 29.6 % at ≥ 21.6 psi ext_air_duct. Pack load loss 0…0.33 (one pack) …0.40
(two packs); other starter via the isolation valve 0.42. Approach factor: N2
rises at the full rate to 94.6 % of the target, then at 2.5 % of the rate.
5.4 Spool-down tables (time × 1.3 s)#
| N2 % | 59.8 | 40.7 | 37.5 | 32.1 | 25.9 | 22.3 | 18.6 | 14.8 | 10.4 | 4.2 | 1.7 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| t (s, before ×1.3) | 0 | 6 | 7 | 10 | 15 | 19 | 24 | 31 | 43 | 62 | 70 | 103 |
| N1 % | 19.2 | 12.1 | 11 | 9 | 7 | 5.9 | 4.9 | 4 | 3 | 2 | 1.7 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| t (s, before ×1.3) | 0 | 6 | 7 | 10 | 15 | 19 | 24 | 31 | 43 | 62 | 70 | 106 |
Above the first column: N2 → 100(n/100)^3.11, rate 0.012 s/% (≥ 33) or
0.04 s/% (< 33), back with exponent 1/3.1; N1 the same rates directly. A value
of exactly 0 maps to the end of the table.
5.5 Windmilling N2/N1 (non-running engine), Mach 0.0 … 0.9#
| N1 (fan) % \ Mach | 0 | .1 | .2 | .3 | .4 | .5 | .6 | .7 | .8 | .9 |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 ft | 0 | 7.51 | 10.96 | 13.45 | 16.13 | 18.34 | 20.52 | 22.30 | 21.98 | 22.28 |
| 15000 ft | 0 | 7.13 | 10.41 | 12.77 | 15.32 | 17.43 | 19.50 | 21.19 | 23.13 | 23.45 |
| 25000 ft | 0 | 6.87 | 10.03 | 12.30 | 14.76 | 16.78 | 18.78 | 20.41 | 22.27 | 22.59 |
| 35000 ft | 0 | 6.60 | 9.63 | 11.81 | 14.17 | 16.11 | 18.03 | 19.59 | 21.39 | 21.69 |
| 45000 ft | 0 | 6.34 | 9.25 | 11.34 | 13.60 | 15.47 | 17.31 | 18.81 | 20.54 | 20.82 |
| N2 (core) % \ Mach | 0 | .1 | .2 | .3 | .4 | .5 | .6 | .7 | .8 | .9 |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 ft | 0 | 0 | 3.64 | 6.95 | 10.27 | 12.08 | 15.24 | 17.23 | 19.83 | 20.12 |
| 15000 ft | 0 | 0 | 3.45 | 6.59 | 9.72 | 11.44 | 14.43 | 16.32 | 18.78 | 19.06 |
| 25000 ft | 0 | 0 | 3.32 | 6.33 | 9.34 | 10.99 | 13.87 | 15.68 | 18.04 | 18.31 |
| 35000 ft | 0 | 0 | 3.18 | 6.06 | 8.95 | 10.53 | 13.28 | 15.02 | 17.28 | 17.54 |
| 45000 ft | 0 | 0 | 3.05 | 5.82 | 8.58 | 15.47 | 17.31 | 18.81 | 20.54 | 20.82 |
The 45000-ft N2 row repeats the 45000-ft N1 values from Mach 0.5 on (Zibo data, reproduce). Altitude interpolation as described in §4.8.6; the head-wind Mach replaces Mach in the first band (fan N1 only: the fan windmills in wind on the ground).
Caution (reading of the interpolation): between 15000 and 25000 ft Zibo
interpolates the row pair (15000, 25000) with Rescale(0, row15k, 25000, row25k, h), i.e. the interpolation variable starts at 0 ft, not at the lower
band edge. Reproduce literally.
5.6 Start EGT tables (N2 % → ΔEGT °C)#
| N2 % | 28.1 | 29.3 | 30.1 | 32.1 | 34.1 | 36.1 | 38.1 | 42.1 | 44.1 | 51.1 | 55 | 57 | 58.1 | 59.4 | 59.7 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
calc_start_egt (subtracted to get the base) |
0 | 2 | 30 | 60 | 95 | 133 | 165 | 193 | 219 | 251 | 311 | 335 | 345 | 361 | 366 |
egt_start (added, × coef_egt) |
0 | 2 | 30 | 52 | 74 | 96 | 118 | 162 | 184 | 261 | 305 | 327 | 338 | 349 | 360 |
Below 28.1 %: 0; above 59.7 %: the last value. Motoring EGT approach:
TAT + Rescale(28, 0, 29.7, 5 (not running) / 4 (running, fuel closed), N2),
capped at TAT + 123 − egt_rnd.
5.7 Vibration, oil, EICAS#
Vibration targets (1/100 units): N1 0 → 0, 10 → 181, 37 → 121, 105 → 147; event ×3 (≤ 500); lag k = 3/s. Oil pressure breakpoints (psi above ambient): N2 0 → 0, 20.2 → 15.7, 30.7 → 20.7, 38.2 → 27.7, 58.9 → 39.7, then +1.3223529 psi/%. Oil temperature map 31 °C (X-Plane) → 69 °C, slope 0.66 above; low-oil overheat to 200 °C at 0 psi. EICAS energise 1.6 s / 2.0 s / 4 s (§4.11.8). Start EGT red line timers 2 s / 4 s. Hot start warning above 585 °C rising, auto-abort above 710 °C on ground, 725 °C hard.
6 Outputs#
6.1 Zibo datarefs (read by Lua, displays, SCI)#
| Dataref | Meaning | SCI state |
|---|---|---|
laminar/B738/engine/starter1_pos, starter2_pos, …_pos2 |
start switch 0…3, OFF detent helper | ENGINE_STARTER1/2_POS, _POS2 |
laminar/B738/toggle_switch/eng_start_source |
IGN select −1/0/1 | TOGGLE_SWITCH_ENG_START_SOURCE |
laminar/B738/engine/mixture_ratio1/2 |
start levers | ENGINE_MIXTURE_RATIO1/2 |
laminar/B738/engine/start_valve1/2 |
start valve 0…1 → EICAS START VALVE OPEN | (display) |
laminar/B738/engine/starter, engine_air_starter |
any starter air / ground-air start code 0…5 | – |
laminar/B738/engine/indicators/N1_percent_1/2, N2_percent_1/2 |
indicated N1/N2 (lagged) | (display) |
laminar/B738/engine/eng1_egt, eng2_egt |
EGT °C | (display) |
laminar/B738/engine/eng1/2_oil_press, eng1/2_oil_temp |
oil psi, °C | (display) |
laminar/B738/engine/fuel_flow_kg_sec[0..1] |
indicated fuel flow | (display) |
laminar/B738/engine/eicas_oil_press1/2, eicas_oil_temp1/2, eicas_ff1/2 |
readouts energised | (display) |
laminar/B738/engine/idle_mode[0..1] |
0 ground, 1 flight, 2 approach | (display: no direct SCI) |
laminar/B738/engine/indicators/eng1/2_vib |
vibration units | (display) |
laminar/B738/engine/rot_ang0/1, rot_spd0/1 |
fan angle (deg) / speed | ENGINE_ROT_ANG0/1 |
laminar/B738/engines/thrust_reverser_deploy_ratio[0..1] |
sleeves | (model animation) |
laminar/B738/engines/actuators/prop_mode, laminar/B738/engine/prop_mode_sync |
reverser state | SIM_COCKPIT2_ENGINE_ACTUATORS_PROP_MODE_0/1 (X-Plane name) |
laminar/B738/engine/eng1_req_n1, eng2_req_n1, …_req_pie, thr_lvr1/2 |
commanded N1 / lever (Lua, EICAS) | – |
laminar/B738/engine/eng1/2_N1_lim, _lim2 |
N1 limit bugs in throttle-use units | – |
laminar/B738/engine/fuel_selector_l/r |
0 during a forced flameout | – |
B738DR_egt_redline1/2, B738DR_egt_hotstart1/2, B738DR_xbld_start1/2, B738DR_wms, B738DR_eng_flameout[0..1] |
EGT start limit, hot start blink, X-BLD / windmill start cues, flame effect | – |
laminar/B738/engine/ignition1/2, eng1/2_start_disable |
registered but never written (always 0 in 2.S1.50) | – |
laminar/B738/failure/engine[…] |
cleared by the logic (hung/hot start, timed failure) | – |
zibomod/engine_model/* |
model parameters (writable, §5.2) | – |
6.2 Simulator outputs (X-Plane datarefs Zibo writes → MSFS meaning)#
| X-Plane dataref | When | MSFS equivalent / use in the rewrite |
|---|---|---|
sim/flightmodel/engine/ENGN_N1_, ENGN_N2_ |
every frame (Starter) |
the engine N1/N2 the simulator must show: drive MSFS N1/N2 (§7.3) |
sim/flightmodel/engine/ENGN_thro_use |
every frame (Eicas_thrust) |
internal (commanded throttle use) |
sim/flightmodel/engine/ENGN_FF_ + override_fuel_flow = 1 |
every frame (Engine_model) |
fuel burn: MSFS fuel flow must equal this (§7.3) |
sim/flightmodel/forces/faxil/fnrml/fside_prop, L/M/N_prop + override_engine_forces = 1 |
every frame | thrust force/moment on the airframe (§7.3) |
sim/cockpit2/engine/actuators/ignition_key[i], igniter_on[i], auto_ignite_on[i] = 0 |
on change | light-off model input (§7.1) |
sim/cockpit2/engine/actuators/mixture_ratio[i] |
on change | light-off / fuel-on input (§7.1) |
sim/flightmodel/engine/ENGN_running[i] = 0 |
restart lock-out, flameouts | forces "not running" (§7.1) |
sim/aircraft/engine/acf_starter_torque_ratio = 0.07, acf_starter_max_rpm_ratio = 0.09 |
every frame | none (keeps X-Plane's starter inert) |
sim/aircraft2/engine/low_idle_ratio = 0.60, high_idle_ratio = 0.67 |
2 Hz | none |
sim/cockpit2/engine/actuators/fadec_on[i] |
EEC failures | EEC state (internal) |
sim/cockpit2/engine/actuators/prop_mode[i], throttle_ratio[i] (reverse) |
reverser commands | reverser state; MSFS reverser via THROTTLEn_SET negative range or K events — not used for thrust since forces are overridden |
sim/operation/failures/rel_* (fire, engfai, engsep, fadec, oilpmp, bird strike, generators) |
failures | internal failure state |
sim/flightmodel/engine/ENGN_oil_quan |
oil leak failure | internal oil quantity (§7.2) |
7 X-Plane-provided behaviour to supply, and the MSFS coupling#
7.1 Light-off and "running" (X-Plane's engine logic)#
Zibo never sets ENGN_running = 1 (except at flight start with engines
running); it only drives the inputs X-Plane uses and forces it to 0. In
X-Plane a turbofan becomes running/burning when it has fuel (mixture > 0,
tank selector with fuel, no fuel-starvation), an igniter is on, and the
core turns fast enough; it stops when the mixture goes to 0, fuel runs out
or the engine fails. X-Plane's exact threshold and delay could not be
measured here (the oracle has no X-Plane engine; §9). Because Zibo only
energises the igniters from 25 % N2 (real_ignition, §4.11.6) and its own
N2 is written into X-Plane every frame, the observable rule in Zibo+X-Plane
is:
burning[i] becomes 1 when: mixture_ratio[i] > 0 (Zibo's lagged start lever)
and ignition_key[i] != 0 (i.e. N2 >= 25 %, or FLT windmill start)
and fuel available on that side (fuel spec)
and no engine failure/separation/fire-out
burning[i] becomes 0 when: mixture_ratio[i] == 0, or fuel starvation, or rel_engfai/engsep,
or Zibo forces ENGN_running = 0 (lock-out, flameout timers)
running[i] = burning[i] (X-Plane keeps both equal for a jet)
Recommendation: implement this as a small "SimEngine" model inside
src/core (the part of X-Plane the engine logic depends on), with the
threshold "mixture > 0" taken literally (Zibo's mixture lag reaches 0.63
after 1 s). Verify against X-Plane once (§9: measure light-off delay with a
real X-Plane session) and keep the rule in one function so it can be tuned.
7.2 Oil temperature and quantity#
Engine_calculation derives the indicated oil temperature from X-Plane's
oil temperature (oil_temperature_deg_C, X-Plane's own heat model) and fades
oil pressure with X-Plane's oil quantity ratio (ENGN_oil_quan, starts at
the acf value, decreased only by Zibo's leak failure). The rewrite must
supply both: oil quantity = 1.0 per engine (reduced by the leak failure);
oil temperature: recommended first-order model towards an N2-dependent
equilibrium tuned to X-Plane's behaviour (measure in X-Plane: cold soak →
idle → take-off), or the MSFS GENERAL ENG OIL TEMPERATURE of the MSFS
engine model (which is running in parallel, §7.3). Until measured, use MSFS's.
7.3 MSFS coupling: making MSFS thrust follow Zibo's N1#
Zibo computes thrust in lbf/N itself (Engine_model, §4.10) and replaces
X-Plane's engine forces. In MSFS 2024 the rewrite has three options:
- Own forces (recommended, closest to Zibo): compute
Fx[i]exactly as §4.10 and apply it through the MSFS flight model: set the MSFS engines' thrust to Zibo's value withTURB ENG THRUSTis read-only, so use the 2024ENGINE CONTROL/ external force interface if available to WASM (SimConnectSimObjectforce application is not exposed for the user aircraft in 2024 — to be confirmed in M3), otherwise option 2. - Closed-loop N1 follow (practical default): keep MSFS's turbine engine
model running with an FDE whose static thrust and N1 table approximate
the CFM56-7B26 (
engines.cfg:static_thrust26300 lbf, N1/thrust table fromcfm56_7b_thrust), and drive the MSFS throttle per engine every frame with a PI controller so thatTURB ENG N1:itracks Zibo'sengine_N1_anim(feed-forward:throttle = n1_to_thr_use(N1_zibo)mapped through the FDE's N1-vs-throttle table; feedback on the N1 error with a time constant well below Zibo's spool rates, i.e. MSFS's own spool lag must be shorter than Zibo's: setengines.cfgn1_and_n2_spool_up/spool_timefast). Thrust then follows N1 through MSFS's table; compare the resulting thrust withFxin the differential test and tune the FDE table until they match within 2 %. Reverse: MSFS reverser state fromprop_mode, reverse throttle range from the reverse lever. - MSFS
TURB ENG N1override: writeTURB ENG CORRECTED N1/TURB ENG N1directly (writable in MSFS 2024 for some engine types when the engine is "simulated externally"); only if option 1 is unavailable and option 2 cannot reach the required accuracy.
Independently of the option:
- Combustion/start: start and stop the MSFS engine with the §7.1 rule
(
GENERAL ENG COMBUSTION:ifollows Zibo'sburning[i]; useENGINE_AUTO_START-free paths: setGENERAL ENG STARTER/TURB ENG IGNITION SWITCH EX1and the mixture/fuel valve so MSFS lights exactly when Zibo's rule says; forceTURB ENG N1/N2during the start from Zibo's values so MSFS never runs its own starter dynamics). - Fuel flow: MSFS must burn
ENGN_FF_[i](kg/s) — set the FDE fuel flow scalar to 0 and remove fuel from the tanks in the fuel subsystem with Zibo's flow, or tunefuel_flow_scalarsoTURB ENG FUEL FLOW PPHequals Zibo's flow (verify in the differential test). - Indications never come from MSFS: all displayed engine values are Zibo's (§6.1).
- Exterior fan:
L:LU737_fan_angle_1/2from Zibo'srot_ang0/1(replaces the old gauge integration from MSFS N1).
8 Differential test scenarios#
All scenarios fix dt = 1/30 s, ISA sea level unless stated, set the
simulator inputs every frame (oracle set), and compare the outputs of §6.1
with the values below (tolerance: N1/N2 0.05 %, EGT 1 °C plus the random
offset ±5 °C, fuel flow 0.001 kg/s, timings ±1 frame). Random numbers: the
oracle seeds rand() with time(), so egt_rnd (±5 °C) and
eng_diff_stab[0] (0.92…1) differ per run; the comparison must either read
them back from the oracle (not exported today) or allow ±5 °C on EGT and
compare engine 2 for sub-33 % acceleration timing.
8.1 Ground start on ground air (oracle script, measured)#
xphost/test/sci/start_spec_eng1_ground.json (12/12 checks pass): cold &
dark, battery + GPU, ASU connected (laminar/B738/engine/engine_air_start = 1 held → ext_air_duct 48 psi), isolation valve open (AUTO, packs off),
engine 1 start switch DEC → GRD at t ≈ 13 s; start lever IDLE at t ≈ 28 s;
X-Plane light-off emulated at t ≈ 37.3 s (N2 ≈ 27.3 %) by setting
ENGN_running[0] = engine_is_burning_fuel[0] = 1; start lever CUTOFF at
t ≈ 120 s. Inputs held: sigma 1, rho 1.225, TAT = OAT = 15 °C, 101325 Pa,
29.92 inHg, X-Plane oil temperature 15 °C, oil quantity 1. Measured
(indicated values; xN2 = value written to X-Plane):
| t (s) | Start sw | Start valve | N2 % | N1 % | xN2 % | EGT °C | Oil psi | FF kg/s | ign key | Note |
|---|---|---|---|---|---|---|---|---|---|---|
| 12.4 | OFF | 0 | 0 | 0 | 0 | 17.4 | 0 | 0 | 0 | cold (EGT = TAT + egt_rnd) |
| 17.0 | GRD | 1.0 | 4.21 | 0.01 | 5.36 | 17.4 | 0 | 0 | 0 | N2 +1.20 %/s |
| 27.0 | GRD | 1.0 | 16.20 | 0.35 | 17.36 | 17.4 | 0 | 0 | 0 | |
| 36.3 | GRD | 1.0 | 27.28 | 1.77 | 28.01 | 17.4 | 4.38 | 0 | 2 | at 94.6 % of 29.6 % motoring; IGN R from 25 % |
| 38.3 | GRD | 1.0 | 28.54 | 2.06 | 29.59 | 20.5 | 4.97 | 0.065 | 2 | lit: +1.55 %/s, FF surge |
| 42.3 | GRD | 1.0 | 34.30 | 3.69 | 35.79 | 125.7 | 9.37 | 0.082 | 2 | |
| 48.3 | GRD | 1.0 | 43.60 | 7.72 | 45.09 | 270.8 | 16.13 | 0.082 | 2 | |
| 54.3 | GRD | 1.0 | 52.90 | 14.01 | 54.39 | 415.4 | 21.52 | 0.094 | 2 | |
| 56.3 | OFF | 0.83 | 56.00 | 16.70 | 57.49 | 464.2 | 23.32 | 0.101 | 3 | released at 55.3 %; OFF + 50…57 % → both igniters |
| 58.3 | OFF | 0.03 | 59.10 | 19.73 | 60.59 | 460.0 | 25.26 | 0.098 | 0 | |
| 60.3 | OFF | 0 | 60.43 | 20.48 | 59.78 | 451.3 | 27.03 | 0.092 | 0 | overshoot to idle + 3 then gauge |
| 66.3 | OFF | 0 | 59.78 | 19.27 | 59.78 | 438.1 | 26.16 | 0.082 | 0 | stabilised idle N1/N2 |
| 117.3 | OFF | 0 | 59.78 | 19.27 | 59.78 | 430.5 | 26.16 | 0.082 | 0 | idle EGT settles |
| 121.4 | OFF | 0 | 52.31 | 15.70 | 49.98 | 281.6 | 21.18 | 0 | 0 | 1.3 s after CUTOFF + X-Plane stop |
| 133.4 | OFF | 0 | 30.17 | 8.13 | 29.23 | 178.5 | 5.75 | 0 | 0 | spool-down table |
| 149.4 | OFF | 0 | 18.73 | 4.85 | 18.26 | 149.8 | 0 | 0 | 0 | |
| 177.4 | OFF | 0 | 9.61 | 2.84 | 9.37 | 125.7 | 0 | 0 | 0 |
Oil temperature: 33.4 °C throughout (X-Plane 15 °C mapped by §4.9). EICAS oil
pressure/FF readouts energised 1.6 s after GRD. The oracle without the
ambient set runs at sigma 0, TAT 0 °C, 0 Pa (host does not synthesise
weather): N2 then rises at 1.10 %/s and the oil pressure offset is 0; always
set the ambient inputs in engine scenarios.
8.2 Further scenarios (to script in M3)#
- Start on APU bleed (needs the pneumatics spec's APU bleed conditions:
APU running, APU bleed switch ON,
bleedAir_apu_psi > 8,apu_duct_enable): duct pressure 10.5–21.6 psi scales the motoring N2 (26.1…29.6 %); with both packs on the target drops by 40 %. - Cross-bleed start (engine 2 running at ≥ 75 % N2 via isolation valve,
engine 1 GRD): motoring from
f(duct_R) · n2_motor · iso. - Start lever IDLE before 25 % N2: ignition key stays 0 until 25 %; mixture lag 1/s; light-off at 25 % by the §7.1 rule.
- Hung start (
laminar/B738/failure/engine[23] = 1): N2 stops at 50 %; failure cleared after shutdown below 56 % N2. - Hot start (
[25] = 1):coef_egt→ 3, EGT > 585 rising →egt_hotstartblinks; > 710 °C on ground → EEC abort (ignition 0). - Thrust lever steps at idle → 0.25 → 1.0 → 0, on ground, sigma 1:
N1 command,
ENGN_thro_use, N1/N2/EGT/FF/thrust over time (acceleration 2 %/s below 33 %, 16.7 %/s above 40 %; deceleration 6.25/4 %/s). - Idle modes: flaps 15 / engine anti-ice / airborne → approach idle N2 ≥ 74.9 %; touchdown → ground idle after 5.5 s.
- EEC: EEC switch to ALTN (
fadec_on = 0) → mode 0 N1 limit; EEC input failure[14]→ soft ALTN after 15 s, lever to idle → hard ALTN 15 s later. - In-flight windmill: engine 1 CUTOFF at FL350 M0.78 → N1/N2 follow the
§5.5 floors; FLT start above the X-BLD speed band with
B738DR_wms. - Restart lock-out: shutdown, then start lever IDLE immediately → no ignition for 5 s with the lever back in CUTOFF.
- Reverser: on ground, ENGAGE_REVERSE_1, reverse lever 1.0 → sleeve
1.8 s,
faxil_propsign change, ×1.3 above 80 kt. - Failures: overspeed timer (N1 103.5 % for 450 s), oil leak, engine failure auto-clear (30–90 s), separation → N1/N2 0.
Each runs on the oracle with --sci-test and on the native build of
wasm/src/core with the same inputs; the comparison uses the SCI state names
and the Zibo datarefs listed in §6.1.
9 Open questions#
- X-Plane light-off rule (§7.1): exact N2 threshold, fuel-flow build-up
and delay of
ENGN_running/engine_is_burning_fuelin X-Plane 12 with Zibo's inputs. Needs one measurement in real X-Plane (igniter on at 25 % N2, mixture lag): logENGN_running,ENGN_N2_,mixture_ratioper frame. - X-Plane oil temperature model feeding §4.9 (not part of Zibo).
: resolved in M3c, §10.2.Animate_physical3thr_use_to_n1(u, -1): Zibo readsrel_eng_ice[-1](the word before the array) inThr_shift; in practice the memory holds another float. Decide whether to reproduce (read the neighbouring global) or use 0 ice; the result is capped at 1.12 anyway.- Wind in the windmill N1 band 1 uses a Mach breakpoint 0.01 instead of 0 in the lowest altitude band only — reproduce, but confirm intended.
- Precipitation input scaling: X-Plane
precipitation_on_aircraft_ratio(0…1) vs MSFSAMBIENT PRECIP RATE/state: a mapping must be chosen. - MSFS force injection for option 7.3.1: whether a WASM module can apply
an external force to the user aircraft in MSFS 2024; otherwise option
7.3.2 with an FDE tuned against
Fx. - Random numbers: the oracle seeds with wall-clock time; for frame-exact
differential tests a hook to fix the seed (or export
egt_rnd,eng_diff_stab) would be needed in xphost (not modified here). - One-frame latency (App. A.8):
Starter/Engine_calculationuse the ignition and start valve computed bySystemsin the previous frame; the rewrite must keep the order to stay frame-exact. - Ground thrust term in
Engine_model(adds up to 11800 N at 40–50 % N1 on the ground and removes thrust below 35 % N1 depending on weight and groundspeed): Zibo's taxi tuning for X-Plane's ground model. With MSFS ground physics it may need retuning (fidelity note, not a logic change).
10 Corrections and details found in M3c (implementation, oracle trace)#
Read from the disassembly where the sections above were open or approximate; the rewrite
(wasm/src/core/engine/) follows these and matches the oracle frame by frame
(xphost/test/sci/start_trace.json). The helpers are compared bit for bit with the original
functions (wasm/test/test_engine_zibo.cpp).
10.1 Helpers (§5.1)#
Anim_value: the end snapping compares in double: `(double)tgt >= hi − 0.001 and (double)cur= hi − 0.01
(likewise atlo); otherwisev = sim_period·k·(tgt − c) + c, clipped totgtwhen it overshoots, thenLimit(lo, hi)`.B738_set_anim_time(cur, tgt, lo, hi, dir, T): step= sim_period / T · |lo − hi|;dir == 0: only moves down ifcur > tgt(not belowtgt), then raised tolo; otherwise only moves up iftgt > cur(not abovetgt), then capped athi.n2_shutdown(n, floor): above 59.8 % the N2 is converted to(float)(100·(n·0.01)^3.11)(doublepow), moved byB738_set_animation_rate(x, 0, 0, 108, 33 > x ? 0.04 : 0.012)and converted back with exponent1/3.1. At or below 59.8 %: the elapsed time is interpolated on the table (0 at ≥ 59.8, end time at exactly 0),sim_periodadded, the value read back at the new time (0 at or after the end, 59.8 at time 0). The result is raised tofloor.n1_shutdown(n)is the same with the N1 table and no conversion (B738_set_animation_ratedirectly above 19.2 %); its "time 0" value is 59.8 (copied from the N2 function; unreachable).calc_start_egt(N2, EGT)=EGT − table(N2),egt_start(N2, base, i)=base + scaled table(N2); the table value is the first/last breakpoint outside 28.1 … 59.7 % and theRescaleof the segment inside. Inegt_startthe breakpoints are multiplied bycoef_egt[i]before interpolating (not the interpolated value). The coefficient update:m = Rescale(0, 1, 1, 4, max(thrust lever, reverse lever)), 3 with the hot-start failure (was_hot_startset); targetLimit(m·Rescale(ws_lo, 0, ws_hi, ws, N2), 1, 4);coef = Anim_value(coef, target, 1, 4, 2 if target ≥ coef else 1), thenLimit(1, 4).ff_corr_coef(alt, OAT):d = OAT − calc_tamb_by_alt(alt),Rescale(0, Rescale(−20, 0.956, 20, 1.044, d), 36000, Rescale(−20, 0.946, 20, 1.054, d), alt);corrected_ff2(ff, alt, OAT) = ff_corr_coef(alt, OAT)·ff.thr_use_to_n1(u, i): ice factor(float)(1 − 0.65·(double)(ice²)), at least 0.3; cap1.12·ice(in double) fori < 0, elsemax_thrust_use[i]·ice;u = Limit(u, 0, cap)(0 if ≤ 0); resultu^(1/1.62f)·(99.9930954 − 19.0758705) + 19.0758705(std::pow(float, float)).n1_to_thr_use(n, i):x = (Limit(n, 19.0758705, 108) − 19.0758705)/80.9172…(0 if ≤ 0),Limit(x^1.62, 0, cap).
10.2 Animate_physical3(c, t, lo, hi, a, p5, p6, p7, p8)#
old = a. Desired rate w = (t − c)·p5 / sim_period; if a == 0 and t > c, w is at least
((t − c)·p7·sim_period + c − c) / sim_period. a += (w − a)·(t ≥ c ? p7 : p8)·sim_period,
a = Limit(a, −p6, p6), result r = a·sim_period + c. Then:
- if
t > 550or notplane_on_ground:a = 0, first order instead:r = (t − c)·(t ≥ c ? p7 : p8)·sim_period + c, not pastt; - elif
old ≥ 0anda < 0:a = 0, the same first-order step (when falling, not belowt); - elif
r > t + 100anda > 0:a = (float)(a − sim_period·0.5·a)(double),r = a·sim_period + c; - elif
a < 0andt > r:r = t,a = 0. Finallyrlimited to[lo, hi].
10.3 Engine_calculation (§4.9)#
- Indicated fuel flow, in 0.01 kg/s inside the animations (
ff·100, range 0…1000, result·0.01in double): separated or not running → 0; metering valve< 0.99(double) →Anim_value(…, 0, k 1);choke == 1: target(float)(start_ff + 0.01389), at least 0.09722,B738_set_animation_rate0.154 s per unit,choke = 2when the result equals the target;choke == 2: targetstart_ff(at least 0.082177), 0.333 s,choke = 0on equality; else target X-Plane's fuel flow, below 58.5 % N2:Rescale(46.2, start_ff, 58.5, start_ff + 0.02778, N2)above 46.2 %, at least 0.082177;Anim_value(…, k 1).engine_running_oldis updated after the engine-lost test. - "N2 rising" everywhere in the EGT logic is
engine_N2_anim ≥ eng_N2_old(written to X-Plane last frame;eng_N2_old = engine_N2_animat the end of the function);N2iseng_N2_idx. - EGT target (default TAT): not running: the motoring rise
Limit(Rescale(28, 0, 29.7, 5, N2) + EGT − rnd, TAT, TAT + 123 − rnd)only with fuel closed,28 < N2 ≤ 29.7and N2 rising; running withN2 < 56.5and X-Plane FF< 0.1: (a) fuel closed or (N2 < 28and FF< 0.0279): the same rise with 4 instead of 5 (same conditions), else TAT; (b) elseN2 < 28or falling: TAT; (c) else (lit, rising):motoring_eng_egt = max(TAT, calc_start_egt(N2, EGT))once, targetmax(egt_start(…), EGT − rnd); otherwisegauge_egt.coef_egtrelaxes to 1 (Anim_valuek 1) in every branch except (c). Thenmax(target, TAT − rnd, −rnd) + rnd, at least TAT. ws_coef_egt(not running): fuel open:+= sim_period·Rescale(225, 0.25, 625, 0, N2²)while< 3; fuel closed:−= sim_period·0.03(N2 ≥ 25) or0.01(below) while> 1(double), thenLimit(1, 3). With the start lever at IDLE from the beginning of a start this reaches 3 (wet start): the start EGT triples, hot start, EEC ignition cut-off above 710 °C.- EGT update:
EGT > targetand not burning:Anim_value(EGT, target, 0, 1000, k)withk = Rescale(180, 0.0141c, 900, 0.2c, EGT)above 180 °C,Rescale(100, 0.0009c, 180, 0.0141c, EGT)below (products in double); elifN2 < 56.5, rising,target > TATandtarget ≥ EGT:Anim_valuewithk = 0.00155c(fuel closed,28 < N2 ≤ 29.7, rising) or 200; elseAnimate_physical3(EGT, target, 0, 1000, eng_accel_egt, 0.1, 100, 0.3, k)(samek). The acceleration is reset to 0 when the second-order branch was not used. - Fuel spar and engine valves (computed in
Annunciators, i.e. afterEngine_calculation): with the fire handle in and the hot battery bus powered: spar valve target 1 once the start lever has been above 0.5 for more than 0.5 s (delay counted withsim_period), engine valve target 1 with the lever above 0.5 unlesseec_hotstart; bothB738_set_animation_rate(…, 2).
10.4 Systems start part (§4.11)#
- Knob animation (§4.11.1): the positions are
Rescales, i.e. clamped: toward GRDpos = Rescale(0, 0, 0.5, 1, knob)below 0.5 (else 1); toward OFF from below 0.2pos = Rescale(0, 0, 0.2, 1, knob)— the solenoid release therefore shows OFF on the first frame; from 1.2 … 2Rescale(1.2, 1, 2, 2, knob); toward CONT from 1.5Rescale(1.5, 1, 2, 2, knob).tgt == knob:pos = knob(also 3). The OFF-detent helperpos2differs per engine: engine 1 usesRescale(0, 0, 0.5, 1, knob)(toward GRD) andRescale(0, 0, 0.2, 1, knob)(toward OFF), engine 2Rescale(0.5, 0, 1, 1, knob)andRescale(0.2, 0, 1, 1, knob). The test0.2 > knob(and1.2 > knob) is done in double. igniter_on(§4.11.6) is cleared whenreal_ignition == 0and the indicated N2 (eng_N2_idx) is below 54 %.
Appendix A: per-frame call order of MainFlightLoopCallback#
MainFlightLoopCallback(elapsed, …) is zibomod's only flight loop; it returns
-1.0 (called every frame). Derived from the disassembly of the function
(0x164776, 111 KB) by following the branches around each call, cross-checked
with its decompilation; conditions are given as Zibo tests them. Calls into pure helpers (Rescale, Limit,
Anim_value, string handling, XPLMGet/SetData*) are omitted. Subsystems other
than the engine keep their own internal order; this appendix is the frame
skeleton every subsystem spec refers to.
A.1 Frame prologue (every frame)#
If
first_time == 0:Init_variables()(once per plugin start).Reset
sim_bus_load[0..2] = 0(each subsystem adds its electrical load during the frame; written to X-Plane at step A.4.6).Livery change check; on change
livery_checked = 0; then oncecheck_model(),Compare_crc()(model identification / protection).Interop datarefs of other plugins (JAR ground handling, AviTab, BetterPushback, Honeycomb, SAM jetway power, JBR options, developer tuning). Not part of the systems logic.
B738DR_load_save←laminar/B738/...load_save(EFB "save/load situation" state, values 3 and 4 = loading).If
first_time_loaded == 0(first frame after an aircraft/flight load;XPluginReceiveMessageresets it to 0 on plane-loaded/airport-loaded):- read
simDR_int_ver(X-Plane version); sim_period = 0.01;- force X-Plane
bus_battery_on[] = 1; - read
sim/aircraft2/engine/low_idle_ratioandhigh_idle_ratio; Init_engine_param()(engine model parameters, §4.1);srand(time); readlaminar/B738/release_ver;Read_B738_dat()if the B738X data file is pending.
Else
sim_period = elapsed(the X-Plane frame time).- read
Clamp:
if sim_period > 0.1 → 0.1; else if sim_period <= 0 → 0.01.If any of the handles
sim/time/paused,sim/time/is_in_replayor Zibo's ownoffsetdataref is missing → skip everything up to A.7 (return −1).Read_Xplane_DR()— reads all X-Plane datarefs used by the plugin intosimDR_*globals (engine-related ones listed in §2.2).Read_B738_DR()— reads datarefs of the XLua side (laminar/B738/...owned by Lua) intoB738DR_*globals.
A.2 Per-frame preparation#
- If
load_saveis 3 or 4:pause_load_flt = 1. Iffirst_time_loaded == 0: copy TAS/speed of sound/TAT for the displays; unless loading a situation, set X-Plane IAS mode and MCP speed 100 kt (update_mcp_speed_dial); take the reverser state (B738DR_reverse_deploy/thrust) from X-Plane. init_flght_ctrl(),update_flght_ctrl(),TerrainRadar_warning(),err_debug().- Read FMS datarefs owned by Lua (legs, holds, approach indices, transition
altitude, …) and the current barometer (
powfor pressure altitude).
A.3 Flight start (once per loaded flight)#
If simDR_pause == 0 and first_time_loaded == 0:
egt_rnd[i] = Limit(generate_rnd(…) − 5, −5, 5)for each engine: a random per-engine EGT offset of ±5 °C used by the EGT model (§4.9).Idle_mode_start_flight()(§4.2) — engine state at flight start.- In this order:
Glareshield_load_plane,Glareshield_start_flight,Annunciators_load_plane,Annunciators_start_flight,Anti_ice_load_plane,Anti_ice_start_flight,Comm_load_plane,Comm_start_flight,Chrono_load_plane,Chrono_start_flight,Fire_load_plane,Fire_start_flight,Switches_load_plane,Switches_start_flight,Lighting_load_plane,Lighting_start_flight,Cab_lighting_start_flight,Systems_start_flight,WXR_start_flight,calc_position_startup. - Clear and publish the failure arrays (
laminar/B738/failures/...: electrical, hydraulic, system, pressurisation, engine; 70 floats each).
Then, if first_time_loaded <= 1: first_time_loaded = 1.
A.4 EFB situation state and the systems block#
DR738_state_cmd/state_cmd2handling (EFB "save/restore state" command strings) — not engine-related.- If
B738DR_load_saveis 3 or 4 (a saved situation is being loaded): remove ground equipment objects (disableGPU,disableASU, fuel trucks, cones, de-ice trucks, stairs, catering, belts, buses, ambulance, fire trucks), thenGlareshield_load,Anti_ice_load,Fire_load,Switches_load,Lighting_load,Systems_load(restore the saved panel state;Systems_loadrestores starter positions, N1/N2, mixture), andmemorize_nav_tunertwice. - Else
loaded_flight_timercount-down (-= dtwhile > 0). After a situation load (load_save == 4) or on the frame after the flight start (first_time_loaded == 1):first_time_loaded = 2,direct_N1_req_bug[] = 1,idle_mode_old[] = 0. - If
simDR_replay == 0:If
was_replay != 0(first frame after leaving replay): restore all memorised states (CWS, A/T, F/D, yoke, thrust levers, flaps, gear, speed brake, trim, reversers, hydraulics, slats, spoilers, control surfaces,simDR_eng_N1/N2,B738DR_eng1/2_N1/N2).If
simDR_pause == 0, in this order (the systems block):# Function Subsystem 1 Idle_modeengine: idle mode, EEC/FADEC failure handling, mixture → X-Plane (§4.3) 2 Engine_n1_n2engine: ambient ratios, idle N1/N2 (§4.4) 3 Thr_shiftengine: per-engine throttle offset (idle shift) (§4.5) 4 GlareshieldMCP/AFDS panel, A/T (writes thrust lever positions) 5 Eicas_thrustengine: thrust lever → N1 command → X-Plane throttle (§4.6) 6 Xbleed_startengine: cross-bleed start assist (§4.7) 7 Starterengine: starter air, N2/N1 during start and spool-down, windmilling, vibration, fan rotation (§4.8) 8 Engine_calculationengine: fuel metering, light-off, EGT, fuel flow, oil (§4.9) 9 Engine_modelengine: thrust and fuel flow written to X-Plane (§4.10) 10 EICAS_warningEICAS alerts 11 Annunciatorsannunciators 12 Anti_iceanti-ice 13 Commradios 14 Chronoclocks 15 Firefire protection 16 Switchesswitches 17 Lightinglighting 18 Cab_lightingcabin lighting 19 Systemselectrics, APU, pneumatics, fuel, hydraulics, engine start switches, start valves, ignition, EICAS fuel flow/oil indications (§4.11) 20 WXRweather radar Then, unless loading a situation (
load_save3 or 4):Glareshield_save,Anti_ice_save,Fire_save,Switches_save,Lighting_save,Systems_save(state memory for the next "load"); then, ifsim_bus_load[0..2]differs from X-Plane'sbus_load, write it.
- Else (replay):
was_replay = 1;Idle_mode_replay,Lighting_replay,Systems_replay,Glareshield_replay(follow the recorded X-Plane values). - If not in replay:
PFD_stall_system().
A.5 FMS, displays, AFDS (if simDR_pause == 0)#
- Direct-to track drawing, legs data refresh, FMS path computation
(
fmc_calc, turns, holds, offsets, abeam points,calc_direct_trk, …). - ND:
nd_earth_perf,nd_apt_perf,nd_*_display/nd_*_calc,NDRender_xplane,NDRender_xplane2. ASXP_metar,fms_turn,calc_vortex.
A.6 Flight systems and failures#
ap_speed_accel()(every frame, also when paused).- If
simDR_pause == 0:first_timercounts up to the start-up delay (used byB738_gpu_power).de_ice(),Pressurization().- If not replay:
flight_director_new(),autopilot_servo(),B738_yoke(),lnav_vnav_horiz_vert(),calc_position(). Touchdown_effect();ski_effect()(conditional, ski option).- If not replay:
shift_cg(),stab_filter(),sts(). rst(),manage_failure().- If not replay:
manage_eng_failure(),eng_flameout_effect()(§4.12). - If not replay:
check_g_limit(),B738_ail_spoil_spdbrk(). - Reverser deploy bookkeeping (
B738DR_reverse_deploymax of both).
- If not paused: electrical bus status bookkeeping (battery bus).
- Twice a second:
sim_time1 += dt; whensim_time1 > 0.5:N1_N2_idle()(X-Plane idle ratios, §4.13, also while paused); if not pausedengine_fail(sim_time1)(§4.12),detect_center_line(), and iffirst_timer > 2:B738_gpu_power(); thensim_time1 = 0. - If not paused:
Neutral_shift(); if not replayB738_flight_controls(),code_mem2()(memorise for replay), elsedecode_mem2();B738_vib_flaps();tune_fm_extend(). - If
first_time != 0and not replay:art_control()(artificial stability / handling), plusB738DR_xfirst_time2bookkeeping.
A.7 Epilogue#
first_time = 1.- Pause-after-load: if a situation load requested a pause
(
pause_load_fltandB738DR_pause_load_flt != 0) and the sim is not paused → commandsim/operation/pause_toggleonce. sim_paused_old = simDR_pause;replay_mod_old = simDR_replay;open_gl_active = 1.- Return
-1.0.
A.8 Consequences for the rewrite#
- The engine subsystem runs at A.4.4.2 steps 1–3 and 5–9, with the start
switches/valves/ignition part inside
Systems(step 19), i.e. afterStarterandEngine_calculationin the same frame: the starter and ignition values used byStarter/Engine_calculationare those computed bySystemsin the previous frame (one-frame latency that the rewrite must keep for frame-exact comparison). engine_failandN1_N2_idlerun at 2 Hz, not every frame.- Everything in A.4–A.6 is frozen while X-Plane is paused; MSFS pause must gate the same calls.
sim_periodis clamped to 0.1 s: at frame rates below 10 fps the engine model runs slower than real time, exactly like Zibo.