Technical reference

Pneumatics, air conditioning, pressurization, anti-ice (737-800): specification for the rewrite

docs/specs/pneumatics.md

Milestone M2. Status: draft 2026-11-01 (bleed/duct/packs/isolation/APU bleed and pressurization complete; temperatures partial, §9). Subject: Zibo 2.S1.50 (zibomod lin_x64 + XLua) as run by the oracle (xphost/out/linux/lu737host). Everything below is described in our own words from the decompiled zibomod (Ghidra, tools/re/decompile.py), the disassembly where the decompiler lost arguments or constants, the Lua, the SCI contract/profile and oracle runs. No decompiled code is reproduced; constants, tables, timings and conditions are Zibo's.

Notation as in electrics.md and engine_start_and_dynamics.md: Z: = Zibo dataref (laminar/B738/...), X: = X-Plane dataref/command (sim/...), SCI: = stable cockpit interface state/control. Global names in code are the symbol names in zibomod.xpl so a reader can find the original. dt = sim_period (frame time clamped to (0, 0.1] s, see the engine spec, App. A). Index i: 0 = left/engine 1, 1 = right/engine 2. Helper functions (Rescale, Limit, Anim_value, Anim_linear_value2, B738_set_animation_rate, B738_set_anim_time, generate_rnd) have the exact semantics of the engine spec §5.1; ann_bright is the annunciator brightness factor of electrics.md §4.7 (dir_batbus × generic_brightness_ratio[14] × dim), ann_bright_nodim the same without the DIM factor.

1 Scope and sources#

In scope

  • Bleed sources: engine 5th/9th stage pressures, HP (9th-stage) valve, PRSOV (engine bleed valve), precooler and duct temperature, bleed trip; APU bleed (APU "establish" warm-up, load control valve, start-valve deficit), APU bleed valve; ground air cart (ASU, EFB).
  • Distribution: isolation valve (CLOSE/AUTO/OPEN), leak/load sharing between sources, duct pressure L/R (real and gauge), DUAL BLEED, X-Plane bleed mode.
  • Packs: OFF/AUTO/HIGH switches, pack valves, pack flow (into the cabin model), PACK lights; APU load demand of the packs. Recirculation fans and trim air switches (state only, see below).
  • Pressurization: mode selector AUTO/ALTN/MAN, controller power and failures, FLT ALT and LAND ALT selectors, the automatic schedule (ground / take-off / climb / cruise / descent), outflow valve (auto and manual), cabin altitude / rate / differential pressure integration, ram air doors, AUTO FAIL / OFF SCHED DESCENT / ALTN / MANUAL lights, cabin altitude warning, fuselage over-pressure failure, pressurization_dash display blanking.
  • Anti-ice (bleed side): engine cowl valves, wing anti-ice valves including the ground logic and lift-off trip-off, their lights and the X-Plane cowl-heat output.

Covered only partly (see §9): cabin/duct temperature model and zone-temperature lights (selectors and targets are given, the per-zone thermal model is not), WING-BODY OVERHEAT (only the test path exists in Zibo), de_ice() (wing/engine ice removal, X-Plane ice ratios), window/probe heat (electrical; not pneumatic), the -700 variant air panel (*_700 datarefs).

Not modelled by Zibo 2.S1.50 (nothing to reproduce): the FLT/GND switch (no command and no dataref exist; Zibo derives ground/flight from plane_on_ground and engine N1/N2), recirculation fans and trim air (switch positions only: no logic reads them except state save/load and cold-start initialisation), cabin temperature effect of trim air, pack trip / pack overheat (the PACK light comes only from AC power loss with the switch on, or an X-Plane failure), duct overheat.

Sources (zibomod function → section): Systems (bleed/duct/packs/valves, §4.3–4.6; start valves and ASU annunciation are in the engine spec §4.11), pressure_5th, pressure_9th (§5.2), Pressurisation_system (called from Systems, §4.7), update_press_mode (§4.7.1), Pressurization (X-Plane bleed actuators, §6.3), Anti_ice (§4.2), Annunciators (§4.8), Systems_start_flight, Anti_ice_start_flight (§3.3), command handlers CMD_bleed_air_1/2/apu, CMD_l/r_pack_up/dn, CMD_iso_valve_up/dn, CMD_l/r_recirc_fan, CMD_trim_air, CMD_air_valve_lft/rgt, CMD_air_valve_ctrl_lft/rgt, CMD_flt_alt_press_up/dn, CMD_land_alt_press_up/dn, CMD_trip_reset, CMD_duct_ovht_test, CMD_eng1/2_heat, CMD_wing_heat, CMD_air_temp_ctrl_lft/rgt (§4.1); Xplane_datarefs/Read_Xplane_DR and the Register_*DR functions for dataref names. Dead code, not called anywhere: calc_target_cabin_alt, press_calc_volume/pressure/weight/density/speed (do not port). Lua: B738.a_fms owns Z:failure/pressurisation (array[70], EFB failure page); B738.tablet owns Z:engine/engine_air_start (EFB ground air cart). SCI: data/sci/contract/90_cockpit.json and the Zibo profile 90_cockpit.json (generated names, all kind: raw, value = Zibo's raw value).

2 Inputs#

2.1 Cockpit controls (SCI control → Zibo command → Zibo state)#

All are generic SCI controls; the SCI state carries Zibo's raw value.

SCI control (actions) Zibo command(s) Zibo state (SCI state) Behaviour
ENGINE_1_BLEED_AIR, ENGINE_2_BLEED_AIR (PRESS) toggle_switch/bleed_air_1, _2 toggle_switch/bleed_air_1_pos, _2_pos (TOGGLE_SWITCH_BLEED_AIR_1_POS, _2_POS) 0 OFF / 1 ON toggle on command begin
APU_BLEED_AIR (PRESS) toggle_switch/bleed_air_apu toggle_switch/bleed_air_apu_pos (TOGGLE_SWITCH_BLEED_AIR_APU_POS) toggle
L_PACK, R_PACK (INC/DEC) toggle_switch/l_pack_up/dn, r_pack_up/dn air/l_pack_pos, r_pack_pos (AIR_L_PACK_POS, AIR_R_PACK_POS) 0 OFF / 1 AUTO / 2 HIGH _up = toward OFF, _dn = toward HIGH, one step per begin
ISOLATION_VALVE (INC/DEC) toggle_switch/iso_valve_up/dn air/isolation_valve_pos (AIR_ISOLATION_VALVE_POS) 0 CLOSE / 1 AUTO / 2 OPEN _up toward CLOSE
LEFT_RECIRC_FAN, RIGHT_RECIRC_FAN (PRESS) toggle_switch/l_recirc_fan, r_recirc_fan air/l_recirc_fan_pos, r_recirc_fan_pos toggle; no effect
TRIM_AIR (PRESS) toggle_switch/trim_air air/trim_air_pos toggle; no effect
BLEED_TRIP_RESET (PRESS) push_button/bleed_trip_reset → handler CMD_trip_reset push_button/trip_reset §4.1.2
DUCT_OVERHEAT (PRESS) push_button/duct_ovht_test push_button/duct_ovht_test_pos (PUSH_BUTTON_DUCT_OVHT_TEST_POS) 1 while held (begin/end)
AIR_VALVE_CONTROL (INC/DEC) toggle_switch/air_valve_ctrl_right/left toggle_switch/air_valve_ctrl (TOGGLE_SWITCH_AIR_VALVE_CTRL) 0 AUTO / 1 ALTN / 2 MAN one step per begin, then update_press_mode() immediately (§4.7.1)
AIR_VALVE_MANUAL (INC/DEC, spring) toggle_switch/air_valve_manual_right/left toggle_switch/air_valve_manual (TOGGLE_SWITCH_AIR_VALVE_MANUAL) −1 CLOSE / 0 / +1 OPEN −1 (left) or +1 (right) while held, 0 on end
FLT_ALT_PRESSURIZATION (INC/DEC) knob/flt_alt_press_up/dn X-Plane X:cockpit/pressure/max_allowable_altitude (SCI SIM_COCKPIT2_PRESSURIZATION_ACTUATORS_MAX_ALLOWABLE_ALTITUDE_FT, see §7) ±500 ft, §4.1.3
LAND_ALT_PRESSURIZATION (INC/DEC) knob/land_alt_press_up/dn pressurization/knobs/landing_alt (PRESSURIZATION_KNOBS_LANDING_ALT) ±50 ft, §4.1.3
CONT_CABIN_TEMP, FORWARD_CABIN_TEMP, AFT_CABIN_TEMP (INC/DEC) dataref drive (+/−0.05, 0…1) air/cont_cab_temp/rheostat, fwd_…, aft_… §4.9
AIR_TEMP_SORCE_2 (INC/DEC) toggle_switch/air_temp_source_right/left toggle_switch/air_temp_source (TOGGLE_SWITCH_AIR_TEMP_SOURCE) 0…6 one step per begin; display only
CABIN_TEMP, PASSENGER_CABIN_TEMP, AIR_TEMP_SORCE -700 panel variants (*_700) AIR_*_700 not used on the -800
ENGINE_1_ANTI_ICE, ENGINE_2_ANTI_ICE (PRESS) toggle_switch/eng1_heat, eng2_heat ice/eng1_heat_pos, eng2_heat_pos (ICE_ENG1_HEAT_POS, ICE_ENG2_HEAT_POS) toggle (any value ≠ 0 → 0)
WING_ANTI_ICE (PRESS) toggle_switch/wing_heat ice/wing_heat_pos (ICE_WING_HEAT_POS) toggle; switching ON on the ground also sets wing_heat_ground_set = 1, OFF clears it
CAPTAIN_SIX_PACK, FIRST_OFFICER_SIX_PACK recall – annunciator spec

No SCI control exists for FLT/GND (not modelled by Zibo, §1). There is no SCI state for Z:annunciator/autofail; the AUTO FAIL light of the model is driven by another name or not at all (open question §9).

2.2 Simulator inputs (X-Plane → MSFS source; see §7 for the recommendation)#

Zibo global X-Plane dataref Use
simDR_pressure_altitude (p0) X:flightmodel2/position/pressure_altitude pressurization (ambient)
elevation_ft computed every frame from X:weather/barometer_current_inhg: (1 − (inHg/29.92)^0.190284) · 145366.45 (correction, apu.md §2.2: on XP12 the barometer comes from X:weather/aircraft/barometer_current_pas; the oracle's APU psi stays 0 with only the inHg dataref set) bleed pressures, APU psi, precooler, trip reset
simDR_OAT, simDR_true_TAT, simDR_TAT X:cockpit2/temperature/outside_air_temp_degc and TAT datarefs duct temperature, APU establish time, cabin temperature init
simDR_fpm aircraft vertical speed (fpm) cabin rate in cruise climb
simDR_flaps_ratio, simDR_ground_speed flap ratio, ground speed (kt) ram doors, pack HIGH flow
plane_on_ground, simDR_on_ground[3], simDR_radio_height_pilot_ft gear on ground, RA pressurization mode, wing anti-ice
simDR_apu_status X:cockpit2/electrical/APU_N1_percent APU bleed
simDR_apu_run X:cockpit/engine/APU_running APU duct enable (APU spec)
simDR_engine_on[2] X-Plane engine running flags engine cowl valve
simDR_throttle_used_ratio[2] throttle used wing anti-ice lift-off logic
simDR_rel_eng_ice[2] X:flightmodel/failures/inlet_ice_per_engine COWL ANTI-ICE light
simDR_bus_amps[2] X-Plane bus amps APU EGT load (APU spec)
simDR_startup_running X-Plane "start with engines running" flight-start state
simDR_bleed_trip_off_annun[2] X:cockpit/warnings/annunciators/bleed_air_fail trip if == 6 (never true, §7)
simDR_wing_body_ovht_annun X:cockpit/warnings/annunciators/hvac WING-BODY OVHT if == 1
simDR_failures[153] X:operation/failures/failures (enum, 6 = failed) PACK lights if == 6
simDR_press_max_alt X:cockpit/pressure/max_allowable_altitude FLT ALT (X-Plane only stores it)

2.3 Other subsystems (Zibo globals)#

Global From Use
B738DR_dc_bus1/2_status, B738DR_ac_tnsbus1/2_status, B738DR_batbus_status, B738DR_dir_batbus_status electrics valve power, controllers, gauge, lights
eng_N1_idx[2], eng_N2_idx[2] engine (Zibo N1/N2 %) bleed pressure, pressurization mode
B738DR_engine1/2_starter_valve (first-order 0…1), B738DR_start_valve1/2 engine spec §4.11.7 (previous frame) duct load, PRSOV close, APU deficit
B738DR_apu_start_switch_position, apu_duct_enable APU spec (apu_duct_enable = 1 two seconds after the APU is running and stable, cleared when it stops or shuts down) APU bleed valve
B738DR_engine01/02_fire_ext_switch_pos_arm, B738DR_apu_fire_ext_switch_pos_arm fire close bleed / APU bleed valve when the handle is pulled
B738DR_engine_air_start Lua Z:engine/engine_air_start (EFB ASU) ground air
B738DR_thrust1/2_leveler thrust levers wing anti-ice ground logic
door ratios (door_ratio_*), B738DR_capt/fo_window_ratio, b737_variant, B738DR_73x doors cabin leak
B738DR_pressurisation_failure[0..15] Lua Z:failure/pressurisation (EFB) §5.6
B738DR_brightness_level, B738DR_lights_test annunciators DUAL BLEED light
B738DR_high_alt_ldg, B738DR_high_alt_ldg_on high-altitude landing option cabin altitude warning threshold
loaded_flight, rst_failure_timer, loaded_flight_timer flight load / failure reset gate of Pressurisation_system

3 State#

3.1 Persistent state (per flight)#

State Meaning
reg_5th[2] (bool) 5th-stage regulation latch (HP valve closed)
B738DR_engine1/2_bleed_air_9th_valve_pos HP valve 0…1
B738DR_engine1/2_bleed_air_valve_pos PRSOV 0…1
B738DR_precooler1/2_valve_pos (Z:air/engN_precooler_valve_pos) precooler control valve 0…1
duct_temp_L/R duct temperature °C (−35…350)
eng1_leak, eng2_leak, apu_leak, asu_leak load (psi) attributed to each source, computed this frame, used next frame
apu_establish_time APU bleed warm-up 0…1
bleedAir_apu_psi raw APU bleed pressure available (before the load valve)
air_apu_load APU load-control factor 0…1
apu_mode, both_packs_old, TMR_apu_idle APU load-control mode and timer
ext_air_duct ASU pressure 0…48 psi
B738DR_apu/lpack/rpack/isolation_bleed_air_valve_pos valves 0…1
B738DR_real_duct_pressure_L/R, B738DR_duct_pressure_L/R duct pressure (real; gauge)
bleed_trip_off_annun1/2 bleed trip latch
pack_mode, dual_bleed, apu_protection_disabled bleed mode for X-Plane, DUAL BLEED
l_pack_coef, r_pack_coef pack flow factor 0…1.82
l_pack_no_ac, r_pack_no_ac, ac_tnsbus1/2_status_old2 PACK light latch on AC loss
press_mode, press_mode_status controller in use (0 none, 1 AUTO, 2 ALTN, 3 MAN), power-up ramp
B738DR_pressurization_mode2 schedule phase: 0 ground, 1 take-off, 2 climb, 3 cruise, 4 descent
to_timer, off_timer 1.5 s timers for ground ↔ take-off
mem_landing_alt, mem_landing_alt2, mem_last_alt, mem_cruise_alt, calc_cabin_alt, press_max_alt_old, press_max_alt_old2, land_alt_knob_old2 schedule memory
target_cabin_alt, target_diff_press, tgt_outflow_valve, allow_control, cabin_pressure_diff_old controller
off_sched_desc OFF SCHED DESCENT latch
real_cabin_altitude_actual, real_cabin_vvi, real_cabin_pressure_diff cabin state (the model's truth)
B738DR_outflow_valve outflow valve 0 closed … 1 open
fuselage_depress_tmr over-pressure timer 0…240
ram_door_tgt, B738DR_ram_door1/2 ram air doors
B738DR_eng_inlet_valve[2], B738DR_wing_anti_ice_valve[2] anti-ice valves 0…1
lift_on, wing_heat_enable_left/right, wing_heat_ground_set, wai_overheat_left/right, wai_overheat_timer_left/right wing anti-ice logic
rndm_max_apu_bleed_psi, rndm_max_eng1/2_bleed_psi random per flight (generate_rnd)

FLT ALT (X:cockpit/pressure/max_allowable_altitude) and LAND ALT (Z:pressurization/knobs/landing_alt) are selector values; in the rewrite both are owned state.

3.2 Constants in writable memory#

bleed_valve_min_act_press = 8 psi (initialised data, never written): minimum duct pressure for the wing anti-ice valve. apu_protection_disabled starts at 1.

3.3 Initial values at flight start (Systems_start_flight, Anti_ice_start_flight)#

  • Always: rndm_max_apu_bleed_psi = generate_rnd(45, 46), rndm_max_eng1_bleed_psi = generate_rnd(45, 46), rndm_max_eng2 = eng1 + generate_rnd(0, 4) − 2 (the engine values are not used by the code described here). apu_establish_time = 0. B738DR_air_temp_source = 1, zone rheostats 0.5, -700 controls 0.5/0. X-Plane rel_APU_press failure cleared.
  • Pressurization (always): X:operation/override/override_pressurization = 1 (X-Plane's own pressurization model is switched off; it is also set again in Systems), all controller state above = 0 (press_mode = 0, mode2 = 0, allow_control = 0, timers 0, memories 0), B738DR_outflow_valve = 0, ram doors 0. If on the ground with "engines running" start, or a cold start: cabin altitude = psi_to_ft(ft_to_psi(elevation_ft)) (= field pressure altitude), differential pressure 0, cabin rate 0. The displayed and real_* cabin values are copied from these.
  • Cold & dark (simDR_startup_running == 0): bleed 1 and 2 switches ON, APU bleed OFF, right recirc fan ON, left recirc fan ON on the -800 (OFF on -600/-700/73x variants), trim air OFF. If B738DR_engine_no_running_state == 0 (normal cold start): packs OFF, isolation OPEN; otherwise packs AUTO, isolation AUTO with the isolation valve at 1 (GPU connected). Cabin temperatures = TAT (min 10 °C).
  • Engines running (simDR_startup_running == 1): packs AUTO, isolation AUTO, isolation valve position 0, bleeds ON, APU bleed OFF, recirc as above, trim air ON, cabin temperatures 25/27/26/28/27, pack temperatures 27/28.
  • Anti-ice: engine and wing switches OFF, all valves 0, lift_on = 0, wing_heat_enable_* = 1, wing_heat_ground_set = 0.

4 Per-frame logic in execution order#

Frame skeleton (engine spec App. A.4): Starter (step 7, reads B738DR_real_duct_pressure_L/R, APU and isolation valve positions of the previous frame) → … → Annunciators (step 11) → Anti_ice (step 12) → … → Systems (step 19: §4.3–4.7 in this order, then the temperature model §4.9) → … → Pressurization (A.6.2, X-Plane bleed actuators, §6.3). Everything listed below runs only when X-Plane is not paused and not in replay. Command handlers (§4.1) run when X-Plane delivers the command, i.e. before the flight loop of that frame.

4.1 Command handlers#

4.1.1 Switches: as in §2.1. Toggles act on command begin only; the pack and isolation selectors move one step per begin and stop at the ends; air_valve_manual is −1/+1 while the command is held and 0 at the end; air_valve_ctrl steps and calls update_press_mode() and copies press_mode to Z:pressurization_mode immediately.

4.1.2 Bleed trip reset (on begin, only if the button was released before): button = 1; if a trip is latched on either side, for each side: reset (latch = 0 and X:operation/failures/rel_bleed_air_lft/rgt = 0) unless the X-Plane bleed annunciator of that side is 6, or (elevation_ft > 35000 and that wing anti-ice valve > 0.95), or that engine's air_engine_psi > 220, or duct_temp > 254. Button = 0 on end.

4.1.3 FLT ALT / LAND ALT knobs: on begin: FLT ALT = Limit(roundUp(FLT ALT ± 500, 500), −1000, 42000) (written to X-Plane's max_allowable_altitude); LAND ALT = Limit(roundUp(LAND ALT ± 50, 50), −1000, 13600). While held: after 1 s (CMD_duration accumulates dt while ≤ 1 s) one more step whenever CMD_duration2 has exceeded 0.01 s (it accumulates dt and resets after each step) — i.e. one step every second frame at 30 fps. roundUpToIncrement(x, n) rounds up to the next multiple of n (open question §9 for negative values).

4.2 Anti_ice (bleed part)#

# engine cowl valves
for i: tgt = (eng_heat_pos[i] == 1 and simDR_engine_on[i] == 1 and dc_bus[i+1] powered) ? 1 : 0
       eng_inlet_valve[i] = Anim_value(eng_inlet_valve[i], tgt, 0, 1, 1.5)
       cowl_on[i] = eng_inlet_valve[i] > 0.95
if cowl_on differs from X:cockpit2/ice/ice_inlet_heat_on_per_engine → write it (int[2])

# lift-off detection and wing anti-ice switch trip-off
if lift_on == 0:  if all three gear off the ground and RA > 10 ft: lift_on = 1
elif lift_on == 1:
    if plane_on_ground: lift_on = 0
    elif throttle_used[0] > 0.5 or throttle_used[1] > 0.5:
        if wing_heat_pos == 1 and wing_heat_ground_set == 1: command wing_heat (switch → OFF)
        wing_heat_ground_set = 0
    wing_heat_enable_left = wing_heat_enable_right = 1

# ground logic, per side (left: thrust1, N1[0]; right: thrust2, N1[1])
if plane_on_ground:
    if thrust_lever <= 0.5:
        enable = 1
        if N1 <= 41.3:  if timer == 0: overheat = 0
                        else: timer -= 0.25·dt; if timer < 0: overheat = 0, timer = 0
        elif overheat == 0: timer += dt; if timer > 10: overheat = 1, timer = 10
        else: timer -= 0.25·dt; if timer < 0: overheat = 0, timer = 0
    else: enable = 0, overheat = 0, timer = 0
else: overheat = 0, timer = 0 (both sides)

# wing valves (left uses real_duct_L, right real_duct_R)
if wing_heat_pos == 0 or ac_tnsbus1 unpowered: tgtL = tgtR = 0
else: tgt = 1 unless duct < 8 (bleed_valve_min_act_press) or enable == 0 or overheat
if ac_tnsbus1 == 1: wing_anti_ice_valve[i] = Anim_value(·, tgt, 0, 1, 1.8)   (else frozen)
Z:ice/wing_l_heat_on = valveL > 0.95 and real_duct_L >= 18   (R likewise)
X:cockpit2/ice/ice_surfce_heat_left_on/right_on forced to 0 (written when ≠ 0)

The rest of Anti_ice (window heat, probe heat) is not pneumatic.

4.3 Systems: bleed sources#

Runs right after Electric_bus. All *_leak values are those of the previous frame (§4.5).

4.3.1 APU bleed available

t_est = 500 ; tgt = 0
if APU_N1 > 90: tgt = 1 ; t_est = Rescale(−30, 180, 40, 60, Limit(TAT, −30, 40))
apu_establish_time = B738_set_animation_rate(apu_establish_time, tgt, 0, 1, t_est)
P = rndm_max_apu_bleed_psi · sin(Rescale(10000, 90°, 80000, 180°, elevation_ft)) − apu_leak ; P >= 0
if (start_valve1 > 0.1 or start_valve2 > 0.1) and apu_establish_time < 1:
    P = max(0, P − (1 − apu_establish_time) · 25)
bleedAir_apu_psi (raw) = Rescale(0, 0, 100, P, APU_N1)

(t_est is seconds for the full 0→1 ramp: 180 s at −30 °C, 103 s at 15 °C, 60 s at 40 °C; the decay without APU takes 500 s.)

4.3.2 Engine bleed, per engine (engine i, N1 = eng_N1_idx[i], leak = eng_i_leak)

p5 = max(0, pressure_5th(elevation_ft, N1) − leak)          → Z:air/engineN_5th_psi
p9 = max(0, pressure_9th(elevation_ft, N1) − leak)          → Z:air/engineN_9th_psi
hp_tgt = 0
if p9 >= 8 − leak and p9 < 110 and not reg_5th[i] and p9 != 0:
    hp_tgt = Limit((38 − hp·p9)/p9 + hp, 0, 1)             (regulates hp·p9 to 38 psi)
unless failure HP valve stuck: hp = Anim_value(hp, hp_tgt, 0, 1, 3)
bleed_ok = bleed_switch > 0.95 and fire handle not pulled and engineN_starter_valve == 0
           and dc_bus[i+1] powered and trip latch == 0
if not bleed_ok or p5 < 8 − leak: reg_5th[i] = false
elif p5 >= 38: reg_5th[i] = true
elif p5 <= 34: reg_5th[i] = false                           (34…38: hold)
air_engine_psi[i] = max(p5, hp·p9)
prsov_tgt = (bleed_ok and air_engine_psi >= 8 − leak and air_engine_psi != 0)
            ? Limit((50 − real_duct[i]) / air_engine_psi + prsov, 0, 1) : 0
unless failure PRSOV stuck: prsov = Anim_value(prsov, prsov_tgt, 0, 1, 3)

Note: prsov_tgt uses the duct pressure of its own side (real_duct_L for engine 1, real_duct_R for engine 2), of the previous frame.

4.3.3 Precooler and duct temperature, per engine

k  = Rescale(0, 13300, 41000, 7565, elevation_ft)            (precooler capacity)
m  = 0.0314 · sqrt(max(0, (air_engine_psi + leak + Rescale(0,0,1,10, wing_ai_valve[i])) · 6894.75729) / 0.601)
T_src = OAT ; rate = 0.04
if N1 > 15: rate = 0.9 ; T_src = 580
            if p5 >= 32: T_src = Rescale(0, 340, 1, 580, hp[i]) ; rate = Rescale(0, 0.45, 1, 0.9, hp[0])
T_tgt = (m > 0) ? T_src − precooler·k / (m · 1.04328) : T_src
duct_temp[i] = B738_set_animation_rate(duct_temp[i], T_tgt, −35, 350, rate)
pc_tgt = (air_engine_psi >= 8 − leak and k > 0) ? Limit(m·1.04328·(T_src − 214)/k, 0, 1) : 1
unless failure precooler stuck: precooler = Anim_value(precooler, pc_tgt, 0, 1, 2)

Zibo quirk to keep: the rate for engine 2 is interpolated from engine 1's HP valve (hp[0]).

4.3.4 Ground air (ASU): if engine_air_start == 1: target 48 psi, else ext_air_duct = 0 immediately; ext_air_duct = Anim_linear_value2(ext_air_duct, target, 0, 48, 6) (6 psi/s, 8 s to full).

4.3.5 APU load control (pack demand on the APU)

starter_demand = max(engine1_starter_valve, iso_valve · engine2_starter_valve)
both_off  = l_pack_pos == 0 and r_pack_pos == 0          (code 0)
both_auto = l_pack_pos == 1 and r_pack_pos == 1          (code 1; otherwise code 2)
if starter_demand > 0 or apu_bleed_switch == 0 or a pack is HIGH: load_tgt = 1, apu_mode = 0, TMR_apu_idle = 0
elif both_auto: apu_mode = 1 (TMR_apu_idle = 0 when the code changed)
elif both_off:  apu_mode = 2 (TMR_apu_idle = 0 when the code changed)
else: load_tgt = 1, apu_mode = 3, TMR_apu_idle = 0
both_packs_old = code
apu_mode 1: TMR_apu_idle += dt while < 15 ;
            load_tgt = 1 if bleedAir_apu_psi − apu_leak < 0.1 or TMR < 2.5 ; else (21 + apu_leak)/bleedAir_apu_psi
apu_mode 2: TMR as above ; if bleedAir_apu_psi − apu_leak < 0.1: load_tgt = 1
            elif bleedAir_apu_psi != 0: (38 if TMR < 2 ; 33 if TMR < 9.5 ; else 21) + apu_leak, / bleedAir_apu_psi
            (load_tgt stays 0 when bleedAir_apu_psi == 0)
air_apu_load = Anim_linear_value2(air_apu_load, load_tgt, 0, 1, 0.2 if rising/equal else 0.06)
apu_out = max(0, air_apu_load · bleedAir_apu_psi)
apu_load_egt, apu_fuel_flow: APU spec (uses apu_valve · B738DR_bleedAir_apu_psi of the previous frame)
B738DR_bleedAir_apu_psi = min(41, apu_out)                   → Z:air/apu_psi

4.4 Duct pressures#

s1 = prsov[0] · air_engine_psi[0]  → Z:air/engine1_psi      s2 = prsov[1] · air_engine_psi[1] → Z:air/engine2_psi
sa = apu_valve · min(41, apu_out)                            sx = ext_air_duct
loadL = 4·lpack_valve + 8·wing_ai_valve[0] + 4·eng_inlet_valve[0] + 10·engine1_starter_valve
loadR = 4·rpack_valve + 8·wing_ai_valve[1] + 4·eng_inlet_valve[1] + 10·engine2_starter_valve
        (each term is Rescale(0,0,1,w,valve); the starter term (w = 5) is added twice)
srcL = max(s1, sa) ; srcR = max(s2, sx) ; srcAll = max(s1, sa, s2, sx)
iso  = isolation valve position (actual, previous frame)
ductL_tgt = Rescale(0, srcL, 1, srcAll, iso) ; ductR_tgt = Rescale(0, srcR, 1, srcAll, iso)
if ac_tnsbus2 powered: duct_pressure_L/R (gauge) = Anim_value(·, tgt, 0, 80, 4)
real_duct_pressure_L/R = Anim_value(·, tgt, 0, 80, 4)       (always)

The gauge (Z:indicators/duct_press_L/R, SCI INDICATORS_DUCT_PRESS_L/R) freezes when AC transfer bus 2 is unpowered; the start, anti-ice and pack logic use the real_ values.

4.5 Load sharing (leaks for the next frame)#

L = s1 + sa ; R = s2 + sx ; A = L + R ; loadA = loadL + loadR
isolated: e1 = s1/L·loadL, a = sa/L·loadL (0 if L == 0) ; e2 = s2/R·loadR, x = sx/R·loadR (0 if R == 0)
combined: E1 = s1/A·loadA, Ap = sa/A·loadA, E2 = s2/A·loadA, X = sx/A·loadA (0 if A == 0)
eng1_leak = Rescale(0, e1, 1, E1, iso) + cowl_on[0]          (cowl_on is the int 0/1 sent to X-Plane)
eng2_leak = Rescale(0, e2, 1, E2, iso) + cowl_on[1]
apu_leak  = 1.5 · Rescale(0, a, 1, Ap, iso) + Rescale(0, 5·sv1, 1, 5·(sv1 + sv2), iso)   (sv = engineN_starter_valve)
asu_leak  = Rescale(0, x, 1, X, iso)                         (computed, not used anywhere)

Consequences: the ASU pressure is never reduced by load; the APU pressure drops with packs, wing anti-ice and especially open start valves (up to 10 psi of extra APU leak per starter); engine bleed pressure drops by its share of the load (§4.3.2 subtracts it from p5/p9).

4.6 Bleed mode, DUAL BLEED, valve targets and motion#

apu_on = apu_valve > 0.05 and apu_bleed_switch == 1
(b1, b2 = bleed switch positions; values other than exactly 0/1 match no row)
b1 b2 apu_on → pack_mode, dual_bleed
0  0  0 → 0, 0        1 0 0 → 1, 0        0 0 1 → 4, 0        0 1 0 → 3, 0
1  0  1 → (apu_protection_disabled == 0 ? 1 : 5), 1
0  1  1 → 5 ; dual_bleed = iso_valve > 0.1 ; if dual_bleed and apu_protection_disabled == 0: pack_mode = 3
1  1  0 → 2, 0        1 1 1 → (apu_protection_disabled == 0 ? 2 : 5), 1
X:cockpit2/pressurization/actuators/bleed_air_mode = pack_mode (written on change)
Z:annunciator/dual_bleed = lights_test ? brightness_level : dual_bleed · (apu_valve > 0.95) · brightness_level
apu_protection_disabled = 1 ; if dual_bleed: = 0 if real_duct_L > B738DR_apu_psi + 5,
                                             else (if real_duct_L < B738DR_apu_psi + 1) stays 1

(apu_protection_disabled therefore only matters through the previous-frame value used above.)

Valve targets:

apu_tgt  = 0 if failure APU-bleed-fail, APU fire handle pulled, APU switch OFF, or apu_duct_enable == 0
           else 1 if apu_bleed_switch == 1 and bleedAir_apu_psi (raw) > 8, else 0
lpack_tgt = l_pack_pos > 0.95 and real_duct_L >= 4 and batbus powered      (R: real_duct_R)
iso_tgt  = AUTO: 1 if any of bleed1, bleed2, lpack, rpack switches <= 0.95, else 0 ; CLOSE: 0 ; OPEN: 1
apu_valve, lpack_valve, rpack_valve = Anim_value(·, tgt, 0, 1, 10) unless the matching failure (stuck)
iso_valve = Anim_value(·, iso_tgt, 0, 1, 10) only if ac_tnsbus1 == 1 and not failed

APU fault side effect: with failure 15 (APU bleed) X-Plane rel_APU_press is set to 6 once; APU MAINT light: APU spec.

4.7 Pressurisation_system (inside Systems)#

Runs only if loaded_flight == 0, rst_failure_timer == 0 and loaded_flight_timer == 0; otherwise the X-Plane cabin datarefs are rewritten from the real_* values (hold).

4.7.1 Preliminaries

ram_door_tgt: on ground → 1 ; airborne: flaps == 0 → 0 ; flaps > 0: GS < 123 → 1, GS > 136 → 0 (hold between)
ram_door1 = B738_set_animation_rate(·, tgt, 0, 1, 10) if ac_tnsbus1 powered ; ram_door2 likewise on ac_tnsbus2
copy real_cabin_vvi / real_cabin_pressure_diff to X-Plane if different
update_press_mode():
    AUTO: DC1 → 1, else DC2 → 2, else 0 ; ALTN: DC2 → 2 else 0 ; MAN: dir_batbus → 3 else 0
    press_mode_status = B738_set_animation_rate(status, press_mode != 0, 0, 1, 1)
    if press_mode_status != 1: press_mode = 0                      (1 s power-up delay)
pressurization_dash = (DC1 and DC2 unpowered) or (failure 9 and failure 10)
p0 = pressure altitude ; p_alt = Limit(p0, 0, 50000) ; P_amb = ft_to_psi(p0) ; P_cab = ft_to_psi(real_cabin_alt)
Z:air/cabin_psi = P_cab

4.7.2 Pack inflow (cabin altitude decrease, fpm)

F = Rescale(2.55, 1600, 14.7, 2200, P_amb)                  (max pack effect)
k = 1 below 25000 ; 25000…33000: 1…2 ; 33000…36000: 2 ; 36000…41000: 2…1.8   (by p_alt)
eN = Rescale(34, 1.54·k, 85, 1, N1[N])                       (engine idle boost)
ok1 = Rescale(0, 0, 2, 1, e1 · Z:air/engine1_psi − P_cab)     ok2 likewise
okA = Rescale(0, 0, 2, 1, apu_valve · B738DR_bleedAir_apu_psi − P_cab)
left  = Rescale(0, F·ok1 + 0.5·F·okA, 1, F·ok1 + 0.5·F·okA + F·ok2, iso_tgt)
right = Rescale(0, F·ok2,             1, F·ok1 + 0.5·F·okA,          iso_tgt)
        (iso_tgt = isolation valve *target*; the right side with the valve open gets engine 1 + APU, not engine 2: keep)
l_req = HIGH: 1.82·lpack_valve ; AUTO: 1.45·lpack_valve if (airborne, other pack OFF, flaps 0)
        or (on ground, both engine bleed switches OFF), else lpack_valve ; OFF: 0     (R likewise)
l_pack_coef = B738_set_animation_rate(·, l_req, 0, 1.82, 3)
inflow = l_pack_coef · Limit(−left·lpack_valve, −F, 0) + r_pack_coef · Limit(−right·rpack_valve, −F, 0)
cab1 = real_cabin_alt + inflow/60 · dt ; P1 = ft_to_psi(cab1)

4.7.3 Leakage and outflow capacity

doors = sum of the 12 door ratios (+2 emergency exits on variant 4, + both cockpit windows if B738DR_73x > 0)
leak0 = 600000 if doors > 1 ; 360000 if 0 < doors <= 1 or failure 0 ; else 0
leak  = 1100 + leak0 + (failure 1 ? 12000 : 0) + 225·|P1 − P_amb|
cap   = 5500 + 250·|P1 − P_amb|
if P1 < P_amb: leak, cap negative

4.7.4 Schedule (mode2). A controller is "failed for this mode" if (failure 9 and press_mode 1) or (failure 10 and press_mode 1 or 2). On the ground off_sched_desc = 0.

  • press_mode == 0: target_cabin_alt = 0, mode2 = 0, timers 0 → skip to 4.7.6.
  • controller failed: timers 0, tgt_outflow = outflow (frozen), target 0, mode2 = 0 → 4.7.6.
  • On the ground:
    • mode2 == 0: if (N1 > 60 and N2 > 89) on either engine for > 1.5 s (to_timer, reset when not): mode2 = 1, mem_landing_alt = p0, calc_cabin_alt = Rescale(0, 0, 28000, 8000, Limit(FLT ALT, 0, 28000)), press_max_alt_old = 0; when not: mem_last_alt = 0.
    • mode2 == 1: if (N1 < 50 and N2 < 84) on either engine for > 1.5 s (off_timer): mode2 = 0, mem_last_alt = 0, calc_cabin_alt = 0.
    • mode2 2/3/4 (landed): mode2 = 1, mem_landing_alt = p0, timers and mem_last_alt 0.
  • Airborne with mode2 == 0: mode2 = 2, mem_landing_alt = 0, press_max_alt_old = 0, mem_last_alt = p0.
  • Then: mode2 == 1 and airborne → 2 (mem_last_alt = p0). mode2 == 2 (climb): mem_cruise_alt = FLT ALT; if ft_to_psi(p0) >= ft_to_psi(FLT ALT) + 0.25 (more than 0.25 psi below FLT ALT): if also ft_to_psi(p0) > ft_to_psi(mem_last_alt) + 0.25 (descended 0.25 psi from the highest altitude): descent before FLT ALT → mode2 = 4, mem_landing_alt2 = mem_landing_alt, calc_cabin_alt = cabin alt, land_alt_knob_old2 = LAND ALT, off_sched_desc = 1, mem_cruise_alt = p0. Else (within 0.25 psi of FLT ALT or above) → mode2 = 3, press_max_alt_old = FLT ALT, target_diff_press = Limit(ft_to_psi(target_cabin_alt) − ft_to_psi(FLT ALT), 0, max_diff_psi(FLT ALT)). mode2 == 3 (cruise): FLT ALT increased (vs. last frame) → mode2 = 2, mem_last_alt = p0. Still 3 and the aircraft is more than 0.25 psi below both FLT ALT and mem_last_alt → mode2 = 4, mem_landing_alt2 = LAND ALT, calc_cabin_alt = cabin alt, mem_cruise_alt = p0. mode2 == 4 (descent): if p0 >= FLT ALT or within 0.25 psi of it → mode2 = 3 (memories = FLT ALT / current cabin, target_cabin_alt = cabin alt, mem_last_alt = p0, target_diff_press as above with the cabin altitude, off_sched_desc = 0); elif mem_last_alt < p0 (climbing above the highest altitude) → mode2 = 2, mem_cruise_alt = FLT ALT, off_sched_desc = 0.

4.7.5 Targets and outflow command

mode2 2 and 3 (identical):
    dmax = 7.45 (FLT ALT <= 28000) ; 7.8 (<= 37000) ; 8.35 (above)
    top  = psi_to_ft(ft_to_psi(FLT ALT) − 0.25)
    calc_cabin_alt = max(0, mem_landing_alt, psi_to_ft(dmax + ft_to_psi(Limit(FLT ALT, 0, 41000))))
    target_cabin = Rescale(mem_landing_alt, mem_landing_alt, top, calc_cabin_alt, max(mem_last_alt, p0))
    target_diff  = Limit(ft_to_psi(target_cabin) − ft_to_psi(FLT ALT), 0, dmax)
    if ft_to_psi(target_cabin) − P_amb > target_diff: target_cabin = psi_to_ft(target_diff + P_amb)
    target_cabin = min(target_cabin, 8000)
mode2 0: tgt_outflow = 1, target_diff = 8.35, target_cabin = p0
mode2 1: target_cabin = psi_to_ft(ft_to_psi(mem_landing_alt) + 0.1), target_diff = 8.35
mode2 4: target_diff = max_diff_psi(p0) ; LAND ALT changed → mem_landing_alt2 = LAND ALT
         target_cabin = Rescale(mem_landing_alt2, mem_landing_alt2, mem_cruise_alt, calc_cabin_alt, min(p0, mem_cruise_alt))
mem_last_alt = max(mem_last_alt, p0)

if press_mode < 3 (AUTO or ALTN):
    if mode2 == 0 and the differential pressure crossed or touched zero since last frame
       (old >= 0 >= new or old <= 0 <= new): allow_control = 0
    if allow_control:
        tc = target_cabin ; if mode2 == 3 and p0 > mem_cruise_alt: tc += p0 − mem_cruise_alt
        if ft_to_psi(tc) − P_amb < 0: tc = p0 ; elif > target_diff: tc = psi_to_ft(target_diff + P_amb)
        R = 700 (350 if mode2 <= 1) ; band = 0.05·R
        rate = mode2 3 and p0 > mem_cruise_alt: aircraft V/S
               mode2 0: (real diff >= 0) ? R : −R
               else: Rescale(−band, −R, band, R, tc − real_cabin_alt)
        need = rate − (inflow + leak)
        tgt_outflow = (need and cap have the same sign) ? Limit(need / cap, 0, 1) : 0
    if real_cabin_alt > 14500: tgt_outflow = 0

allow_control is set to 1 at the end of every frame with mode2 != 0; so on the ground the controller is switched off once the cabin has equalised, and the outflow valve then stays where it is (fully open in practice).

4.7.6 Outflow valve, integration, outputs

if failure 2 (outflow stuck): valve unchanged
elif press_mode == 3 (MAN): valve −= 0.05·dt if manual == −1 ; += 0.05·dt if +1 (0…1) ; timers 0
else: valve moves toward tgt_outflow at 0.05/s (20 s full travel)
B738DR_outflow_valve = Limit(valve, 0, 1) ; X:cockpit/pressure/outflow_valve = same
v = (valve·cap + leak)/60 · dt ; cab2 = cab1 + v
if v > 0 and cab2 >= p_alt: cab2 = p_alt ; if v < 0 and cab2 <= p_alt: cab2 = p_alt
if ft_to_psi(cab2) − P_amb > 9.1: cab2 = psi_to_ft(P_amb + 9.1)
if ft_to_psi(cab2) − P_amb < −1:  cab2 = psi_to_ft(P_amb − 1)
vvi = Anim_value(vvi, Limit((cab2 − real_cabin_alt)/dt · 60, −4000, 4000), −4000, 4000, 4)
cabin_alt = cab2 ; diff = Limit(ft_to_psi(cab2) − P_amb, −0.2, 10.2)
→ X:cockpit/pressure/cabin_vvi_actual_fpm, cabin_altitude_actual_ft, cabin_pressure_differential_psi ; real_* = these
displayed (Z:cabin_vvi, cabin_altitude_actual, cabin_pressure_diff) = Anim_value toward the real
    values with k = 16000 / 100 / 30 (i.e. immediate) when dir_batbus powered, toward 0 otherwise
Z:pressurization_mode = press_mode ; Z:cabin_alt = real cabin altitude
over-pressure: diff > 9.2 → fuselage_depress_tmr = B738_set_animation_rate(tmr, 240, 0, 240, Rescale(9.2, 1, 10, 0.1, Limit(diff, 9.2, 10)))
               else decays to 0 at 1 unit/s ; tmr == 240 → failure 0 = 1 (rapid depressurisation)

4.8 Annunciators (Annunciators, before Systems: previous-frame values)#

Light (Z:annunciator/…) Condition (× ann_bright unless noted)
bleed_trip_1/2 latch set if X-Plane bleed_air_fail[i] == 6, or air_engine_psi[i] > 220, or duct_temp[i] > 254; cleared only by TRIP RESET (§4.1.2)
wing_body_ovht_left/right (both the same) X-Plane annunciators/hvac == 1 or wing_body_ovht_test == 1 (OVHT TEST: 3 s timer started while the button is held; the countdown is in Annun_timers, §9)
pack_left X-Plane failure[153] == 6 or l_pack_no_ac (latched when AC transfer bus 2 drops while the L pack switch is not OFF; cleared when the bus returns)
pack_right same with r_pack_no_ac and AC transfer bus 1
dual_bleed §4.6 (uses brightness_level/lights_test, computed in Systems)
cabin_alt real cabin altitude > 10000 ft (> 14000 ft when the high-altitude landing option is selected and on)
off_sched_descent off_sched_desc
altn_press (mode 1 and exactly one of failures 9/10) or (mode 2 and not failure 10)
manual_press selector MAN
autofail (AUTO and mode ≠ 1) or (ALTN and mode ≠ 2) or mode 0 or diff > 8.75 or cabin > 15800 or
ram_door_open1/2 door 0.05…0.95 → 1, > 0.95 → 0.3, else 0
cowl_ice_on_0/1 (COWL VALVE OPEN) valve in transit (0.05 < v ≤ 0.95) → 1 ; v > 0.95 → 0.3 ; then disagreement → 1: (switch ON and v ≤ 0.95) or (switch OFF and v ≥ 0.05). _annun variant uses ann_bright_nodim and 0.5 instead of partial values
wing_ice_on_L/R (L/R VALVE OPEN) same pattern with the wing switch and valve L/R
cowl_ice_0/1 (COWL ANTI-ICE) X-Plane inlet ice ratio > 0.055 and cowl heat on
Z:eicas/eng1_tai, eng2_tai 1 if the inlet-valve/switch disagreement latch (8 s timer, Annun_timers) is set, else 0 (Zibo's "0.5 when dim" branch compares a float 0.3 to the double 0.3 and is never taken)

All these lights are forced to the test brightness by the annunciator light test (annunciator spec). The six-pack AIR COND / ANTI-ICE / OVERHEAD recall logic belongs to the annunciator spec.

4.9 Air conditioning temperatures (partial)#

Targets (−800): CONT CAB rheostat 0 → 24 °C, exactly 0.5 → 23 °C, else Rescale(0, 18, 1, 28, r); FWD: 0 → 18, 0.5 → 24, else the same rescale; AFT: 0 → 18 (Zibo tests 0 twice, so its "25 °C" branch is dead), else the rescale. If both pack valves > 0.1 or the ASU is connected, the pack and zone temperatures follow these targets with B738_set_animation_rate (pack temperature toward the lowest zone target, −30…60 °C; zones ±1 °C from the cont-cab target); the remainder of the model (one pack, no packs, the outputs Z:zone_temp, Z:cabin_temp, zone temperature lights) is not specified yet (§9). It has no effect on the pneumatics or pressurization.

5 Constants, tables, timings#

5.1 Pressure conversions (exact)#

  • ft_to_psi(h) = 1013.25 · (1 − 6.87535e−6 · h)^5.2561 · 0.014503774 (computed in double, result float).
  • psi_to_ft(p) = p > 0 ? (1 − 10^(log10(p · 68.9475729 / 1013.25) / 5.2558797)) / 6.8755856e−6 : 0. Note the exponents differ (5.2561 vs 5.2558797; 6.87535e−6 vs 6.8755856e−6): round trips are not exact; keep both.
  • max_diff_psi(h): 7.45 if h ≤ 28000, 7.8 if h ≤ 37000, else 8.35.
  • roundUpToIncrement(x, n): round up to a multiple of n (used by the knobs).

5.2 Engine stage pressures pressure_5th/9th(alt, N1)#

P = N1 > lo(alt) ? (N1 − lo) · S / (hi − lo) : 0 (no upper clamp), lo/hi linearly interpolated between the rows (alt ≤ 0 uses the first row, > 41000 the last):

alt ft 5th lo 5th hi 9th lo 9th hi
0 21.143 54 7 25
10000 24.952 60 10.5 33
22000 37.143 70 21.125 38
31000 42.048 76 25 43
37000 55.714 82 25 52
41000 60.524 89 31.125 57

S = 46 psi (5th), 36 psi (9th). Example sea level, N1 20 %: 5th 0, 9th 26 psi → duct ≈ 26 psi.

5.3 Thresholds and rates#

Item Value
HP valve regulation / 5th-stage latch 38 psi ; latch on ≥ 38, off ≤ 34 ; 9th valid 8 − leak … 110
PRSOV regulation duct 50 psi ; minimum source 8 − leak ; switch "on" > 0.95
Valve rates (Anim_value k) HP, PRSOV 3 ; precooler 2 ; APU, packs, isolation 10 ; cowl 1.5 ; wing 1.8
Duct pressure Anim_value k = 4, 0…80 psi
APU bleed max 45…46 psi (random) × altitude factor ; output cap 41 psi ; valve needs raw > 8 ; idle regulation 21/33/38 psi + leak ; load ramps 0.2/s up, 0.06/s down
APU start deficit −25 psi × (1 − establish) while a start valve > 0.1
ASU 48 psi, 6 psi/s
Pack valve duct ≥ 4 psi, battery bus
Pack flow factor AUTO 1, AUTO high-flow 1.45, HIGH 1.82, ramp 3 s per unit
Wing anti-ice duct ≥ 8 to open ; "heat on" ≥ 18 psi and valve > 0.95 ; ground overheat after 10 s at N1 > 41.3 % (recovers at 0.25 s/s) ; thrust lever > 0.5 on ground closes
Bleed trip > 220 psi or > 254 °C
Duct temperature source 580 °C (340 with HP closed) ; precooler setpoint 214 °C ; capacity 13300 (SL) … 7565 (41000 ft)
Pressurization rate limit 700 fpm (350 on ground/take-off), proportional band 5 % ; outflow 0.05/s ; leak 1100 fpm + 225/psi ; outflow capacity 5500 fpm + 250/psi ; cabin rate ±4000 ; diff −1…9.1 psi (displayed −0.2…10.2) ; take-off pre-pressurisation 0.1 psi ; max cabin 8000 ft ; outflow forced closed above 14500 ft
Mode timers take-off detect N1 > 60 and N2 > 89 for 1.5 s ; ground N1 < 50 and N2 < 84 for 1.5 s ; mode switch thresholds 0.25 psi
Controller power-up 1 s
Knobs FLT ALT −1000…42000 step 500 ; LAND ALT −1000…13600 step 50 ; auto-repeat after 1 s
Over-pressure > 9.2 psi: 240 s at 9.2 … 24 s at 10 psi → failure 0
Ram doors 10 s travel ; open on ground, or flaps out and GS < 123 kt ; closed flaps up or GS > 136 kt

5.4 Pressurisation failure items (Z:failure/pressurisation[i], value 1 = failed)#

Meaning inferred from the code that reads them (the EFB page text was not checked, §9): 0 fuselage leak (rapid, 360000 fpm), 1 slow leak (12000 fpm), 2 outflow valve stuck, 3 L pack valve stuck, 4 R pack valve stuck, 5 isolation valve stuck, 6 APU bleed valve stuck, 7 engine 1 PRSOV stuck, 8 engine 2 PRSOV stuck, 9 AUTO controller 1, 10 controller 2 (ALTN), 11/12 HP valve 1/2 stuck, 13/14 precooler 1/2 stuck, 15 APU bleed failed (valve closed, APU MAINT).

6 Outputs#

6.1 Zibo datarefs written (read by Lua, the model, the displays)#

Dataref (laminar/B738/…) Global SCI state
indicators/duct_press_L/R B738DR_duct_pressure_L/R INDICATORS_DUCT_PRESS_L/R
indicators/real_duct_press_L/R B738DR_real_duct_pressure_L/R – (engine start input)
air/engine1_psi, engine2_psi, engineN_5th_psi, engineN_9th_psi, engineN_9th_valve, engN_precooler_valve_pos, apu_psi, cabin_psi bleed globals –
air/engine1/bleed_valve_pos, engine2/…, apu/…, l_pack/…, r_pack/…, isolation/bleed_valve_pos valve positions –
air/engine1/starter_valve, engine2/… starter valves (engine spec) –
toggle_switch/bleed_air_1_pos, _2_pos, _apu_pos, air/l_pack_pos, r_pack_pos, isolation_valve_pos, l/r_recirc_fan_pos, trim_air_pos, toggle_switch/air_valve_ctrl, air_valve_manual, air_temp_source, pressurization/knobs/landing_alt, air/*_cab_temp/rheostat switch states the …_POS / raw SCI states of §2.1
push_button/trip_reset, push_button/duct_ovht_test_pos buttons PUSH_BUTTON_DUCT_OVHT_TEST_POS
annunciator/dual_bleed, bleed_trip_1/2, wing_body_ovht_left/right, pack_left/right, cabin_alt, off_sched_descent, altn_press, manual_press, autofail, ram_door_open1/2(_annun), cowl_ice_0/1, cowl_ice_on_0/1(_annun), wing_ice_on_L/R(_annun) §4.8 ANNUNCIATOR_* of the same name (no SCI state for autofail, ram_door_*, *_annun)
outflow_valve, outflow_valve_dig outflow 0…1 OUTFLOW_VALVE
cabin_vvi, cabin_alt, cabin_altitude_actual, cabin_pressure_diff displayed cabin values – (the model reads the X-Plane ones, §6.2)
pressurization_mode, pressurization_mode2, pressurization_dash, ram_door1/2 §4.7 DIGITAL_PRESS_PANEL is Lua-side (§9)
system/eng_inlet_valve[2], system/wing_anti_ice_valve[2], ice/wing_l/r_heat_on, eicas/eng1/2_tai anti-ice –
ice/eng1_heat_pos, eng2_heat_pos, wing_heat_pos switches ICE_*_HEAT_POS
engine/engine_air_starter ASU start annunciation (engine spec §4.11.5) –

6.2 X-Plane datarefs written (and what they mean for MSFS)#

X-Plane dataref Written by MSFS meaning
X:cockpit/pressure/cabin_altitude_actual_ft, cabin_pressure_differential_psi, cabin_vvi_actual_fpm, outflow_valve §4.7.6 these are the SCI states SIM_COCKPIT2_PRESSURIZATION_INDICATORS_CABIN_ALTITUDE_FT, …_PRESSURE_DIFFENTIAL_PSI, …_CABIN_VVI_FPM (X-Plane aliases cockpit ↔ cockpit2): the rewrite outputs its own cabin values there
X:cockpit/pressure/max_allowable_altitude FLT ALT knob SCI SIM_COCKPIT2_PRESSURIZATION_ACTUATORS_MAX_ALLOWABLE_ALTITUDE_FT; rewrite state
X:operation/override/override_pressurization = 1 flight start, Systems none (the rewrite simply owns pressurization)
X:cockpit2/pressurization/actuators/bleed_air_mode §4.6 none
X:cockpit2/bleedair/actuators/* (Pressurization()): pack_center = 0, engine_bleed_sov[2] = bleed switches ≠ 0, apu_bleed = APU valve > 0.05 and switch ON, gpu_bleed = ASU, pack_left/right = switch ≠ OFF, isol_valve_left/right (open unless all four switches on with isolation AUTO, or OPEN; with the ASU connected the right one is always 1; with APU bleed the left one is always 1) A.6.2 none: only X-Plane's own bleed model reads them, and nothing in Zibo reads that model back
X:cockpit2/ice/ice_inlet_heat_on_per_engine[2] §4.2 engine anti-ice to the sim (ice removal)
X:cockpit2/ice/ice_surfce_heat_left_on/right_on forced 0 Zibo removes wing ice itself (de_ice, §9)
X:operation/failures/rel_bleed_air_lft/rgt = 0 trip reset none
X:operation/failures/rel_APU_press = 6 failure 15 none

6.3 Values the engine start uses (contract with the engine spec)#

real_duct_press_L/R (§4.4), isolation/bleed_valve_pos, apu/bleed_valve_pos, ext_air_duct, apu_establish_time, engine_air_start, l/r_pack/bleed_valve_pos (ASU start annunciation), all from the previous frame (Starter runs before Systems). The starter needs ≥ 10.5 psi to turn, full motoring at 21.6 psi (engine spec §4.8). Measured on the oracle (§8): ASU 48 psi on both ducts; APU at sea level, isolation open: packs OFF 41 → 38 → 33 → 21 psi (load schedule, 0.06/s down-ramp), packs AUTO 33 psi (21 + APU leak 1.5 × pack load 8). Start valve opened with both packs AUTO about 60 s after the APU came up: the raw APU pressure (≈ 45.5 − leaks − 25 × (1 − establish)) falls below 8 psi, the APU bleed valve target goes to 0 and the duct collapses to ≈ 5 psi — the starter does not turn (< 10.5 psi). Zibo therefore requires packs OFF and/or an established APU (≈ 100 s above 90 % at 15 °C) for an APU-bleed start; the rewrite must reproduce this, including the APU valve closing.

7 X-Plane-provided behaviour to supply, with a recommendation#

Key finding: Zibo computes the complete pneumatic and pressurization model itself. X-Plane's pressurization model is disabled (override_pressurization = 1) and Zibo writes the cabin altitude, rate, differential and outflow valve into X-Plane's datarefs; X-Plane's bleed-air model is only fed (bleedair/actuators/*, bleed_air_mode) and never read back. The rewrite therefore needs no MSFS pressurization or bleed model. What it needs from the simulator:

Input X-Plane MSFS SimVar (recommended)
Pressure altitude p0 flightmodel2/position/pressure_altitude PRESSURE ALTITUDE (ft)
Ambient pressure for elevation_ft weather/barometer_current_inhg AMBIENT PRESSURE (inHg); compute elevation_ft with Zibo's formula
OAT / TAT cockpit2/temperature/outside_air_temp_degc, TAT AMBIENT TEMPERATURE, TOTAL AIR TEMPERATURE (°C)
Vertical speed simDR_fpm VERTICAL SPEED (ft/min)
Ground speed, flaps, on-ground, RA as listed in §2.2 GROUND VELOCITY, Zibo flap ratio (flight controls), SIM ON GROUND / gear contact, PLANE ALT ABOVE GROUND MINUS CG / RA
Engine running simDR_engine_on engine spec (ENG COMBUSTION)
APU N1, running APU_N1_percent, APU_running APU spec (APU PCT RPM)
Doors door ratios INTERACTIVE POINT OPEN:n / exit open ratio (doors spec)
Inlet ice (COWL ANTI-ICE light) flightmodel/failures/inlet_ice_per_engine ENG ANTI ICE-independent: STRUCTURAL ICE PCT / engine icing SimVar if available; else 0 (light never on)
Failure inputs (bleed_air_fail, hvac, failures[153]) X-Plane failure system none in MSFS: constant 0. bleed_air_fail == 6 never occurs in X-Plane either (the annunciator is 0/1), so the condition is dead

Outputs to MSFS (recommendation): engine anti-ice → K:ANTI_ICE_SET_ENG1/2 from cowl_on[i] (so MSFS inlet icing is removed like X-Plane's); wing anti-ice → MSFS structural de-ice from wing_l/r_heat_on (only if MSFS icing is enabled; Zibo's own de_ice replaces X-Plane's wing heat). MSFS PRESSURIZATION * SimVars are not used (no need to drive them); if the aircraft.cfg has a pressurization section, the MSFS cabin model runs in parallel without effect on the systems.

The oracle host has no X-Plane atmosphere (engine spec): scripts must set sim/weather/barometer_current_inhg (elevation_ft), sim/flightmodel2/position/pressure_altitude and the temperatures themselves.

8 Differential test scenarios#

Compare at fixed dt on the SCI states and the Z:/X: values of §6. Tolerances: valve positions and pressures 1e−4 relative (float vs double in ft_to_psi), random values (rndm_max_apu_bleed_psi) seeded identically or fed from the oracle.

  1. Ground air (oracle script xphost/test/sci/pneu_spec_ground_air.json, 21/21 pass): cold & dark (bleeds ON, APU bleed OFF, packs OFF, isolation OPEN, isolation valve 0 until AC transfer bus 1 is powered), battery + GPU → isolation valve opens; ASU → both real ducts and the gauge 48 psi; packs AUTO → both pack valves open, ASU pressure unchanged; isolation AUTO with bleeds ON and packs AUTO → valve closes, left duct 0, L pack valve closes (< 4 psi), right side 48; L pack OFF → isolation reopens, left duct 48; ASU removed → ducts 0, R pack valve closes.
  2. APU bleed with packs OFF / AUTO (oracle script xphost/test/sci/pneu_spec_apu_bleed.json, 17/17 pass; APU started as in elec_cold_start.json, fuel set so the APU lights): APU bleed ON → valve opens once apu_duct_enable is set; packs OFF: APU psi 41 → 33 → 21, both ducts equal (isolation open); packs AUTO: 33 psi on both ducts; engine 1 start switch GRD ~60 s after APU start: APU valve closes, ducts ≈ 5 psi (printed, §6.3).
  3. Engine start on APU bleed (with the engine spec): start valve open → APU load valve full open, PRSOV of the starting engine closed, duct pressure drop by the starter load.
  4. Engine bleed after start: N1 idle → 9th-stage regulation (HP valve), PRSOV regulating to 50 psi duct, DUAL BLEED with APU bleed ON and bleed 1 ON, isolation AUTO closing when all four switches are on.
  5. Pressurization schedule (needs pressure altitude/ambient pressure as inputs over time): take-off (N1 > 60, N2 > 89 for 1.5 s → mode 1, 0.1 psi pre-pressurisation), climb to FLT ALT (mode 2 → 3), cruise, descent (mode 4 toward LAND ALT), landing (mode 1 → 0, outflow opens); descent before FLT ALT → OFF SCHED DESCENT; FLT ALT raised in cruise → mode 2.
  6. Modes and failures: ALTN (DC bus 1 off), MAN with the manual switch, controller failures 9/10 (ALTN/AUTO FAIL lights), door open (600000 fpm leak), over-pressure timer.
  7. Anti-ice: cowl valve with engine running and DC bus; wing anti-ice on the ground (thrust lever > 0.5 closes; N1 > 41.3 % for 10 s → overheat close), lift-off trip-off of a switch set on the ground; VALVE OPEN light brightness sequence.

8a Implementation notes (M3c, build 6)#

The bleed part (§4.2–4.6, §4.8 bleed lights) is implemented in wasm/src/core/pneu/; the APU bleed (§4.3.1, §4.3.5, APU valve of §4.6) stays in the APU subsystem. Scenarios 1 and 2 of §8 pass on the rewrite with the same checks; their printed values agree until the start valve opens in scenario 2, where the APU valve cycles chaotically (the phase depends on Zibo's random maximum APU pressure). Checked against the disassembly: pressure_5th/9th (row breakpoints and (N1 − lo)·(S/(hi − lo))), ft_to_psi, psi_to_ft (bit for bit, wasm/test/test_engine_zibo.cpp). The demand of the APU load control uses the first-order engineN_starter_valve, the warm-up deficit B738DR_start_valveN (§4.3.1, §4.3.5). For the engine start: engine 1 can only motor from ground air through the isolation valve (src2 = iso·ext·29.6), and the isolation valve does not move without AC transfer bus 1 (battery only: the left duct stays empty). Pressurization (§4.7) is the next step.

9 Open questions and what is not covered yet#

  • Not covered: cabin/zone temperature model beyond the targets (§4.9; Systems after Pressurisation_system), zone temperature lights, de_ice() (wing/engine ice removal and the X-Plane ice ratios), Annun_timers (WING-BODY OVHT test timer, inlet disagreement 8 s timer), window/probe heat, the -700 air panel (*_700), the Lua side of the pressurization panel display (DIGITAL_PRESS_PANEL, FLT/LAND ALT digits), the six-pack recall, Systems_load/save (situation save/restore of the pneumatic state), replay (Systems_replay).
  • roundUpToIncrement for negative values (FLT ALT/LAND ALT below 0): read its body before porting.
  • FLT ALT initial value: X-Plane's max_allowable_altitude comes from the .acf/X-Plane; measure on the oracle (and whether the host aliases sim/cockpit/pressure/max_allowable_altitude with the cockpit2 dataref the SCI state reads).
  • The SCI contract has no state for annunciator/autofail (nor for the RAM DOOR lights); check which names the cockpit model uses for these lights.
  • X-Plane failure index 153 (PACK lights): identify which X-Plane failure it is; constant 0 in MSFS.
  • Failure item names (§5.4) are inferred from the code; confirm against the EFB failure page strings in B738.a_fms.
  • The doubled starter load term (§4.4, confirmed in the disassembly: the Rescale result is added to itself) and the asymmetric isolation-valve pack supply (§4.7.2) are reproduced as found; they look like Zibo slips but must stay for parity.
  • Decompiler caveats checked against the disassembly: double-precision constants (0.1, 0.25, 0.5, 0.95, 1.54, 1.45, 1.82, 9.1, 9.2, 0.055) and the std::abs arguments of §4.7.3. The Systems decompilation used for §4.3–4.6 was produced before initialised-data globals were excluded from constant folding; the two places affected (apu_protection_disabled, bleed_valve_min_act_press) were re-read from the disassembly. A re-check of the whole of §4.3–4.6 against a clean decompilation is advisable before implementation.

10 Implementation (build 31): pressurization#

§4.7 and the pressurization commands are zibomod's own code translated by tools/re/gen_press.sh (wasm/src/core/pneu/press_gen.*, class Press in press.h), the §4.8 pressurization lights in press.cpp, wired in wasm/src/core/systems_press.cpp. Oracle: xphost/test/sci/press_trace.json (host call hooks around Systems → Pressurisation_system), golden wasm/test/data/press_trace_oracle.txt.gz, test press_replay_matches_oracle. Doors and windows are closed (not modelled), the high-altitude-landing option is off.