// Aircraft state and flight model. Direct port of the FS2 disassembly: // chunk5 IntegratePhysicsStep, ComputeFlightDerivedValues, // UpdateAutoTrimAndYaw, IntegrateClimbRate, CheckFlightEnvelope, // ResetAircraftSystems, ApplySlewDeltas; chunk2 ApplyWind. // // State uses the FS2 fixed-point conventions: // - position: int32_t (Q16.16) -- high 16 bits = world unit, low 16 // bits = fraction. FS2 stores 24-bit position cells with the low // byte fractional; we extend to 32-bit signed for headroom. // - byte angles: uint8_t (256 == full turn). // - rates / speeds: int16_t (Q8.8) -- low byte fractional, high byte // integer per-frame delta. // - pilot inputs: signed int8_t (-127..+127) for yoke / rudder / trim, // unsigned uint8_t (0..255) for throttle / flaps / mixture, matching // FS2's YokeVertPos / ThrottlePos byte layout. #ifndef AIRCRAFT_H #define AIRCRAFT_H #include #include #include "camera.h" #include "wind.h" // Crash classes mirror FS2 chunk3 `HandleCrashOrSplash` + the // `crash_msg_table` indices and `msg_problem` / `msg_splash` cases. typedef enum CrashTypeE { CRASH_NONE = 0, CRASH_GROUND = 1, CRASH_MOUNTAIN = 2, CRASH_BUILDING = 3, CRASH_SPLASH = 4, CRASH_PROBLEM = 5 } CrashTypeE; // View directions mirror FS2's `ViewDirection` semantics: forward is // the default; left/right/back are 90/180/270 degree yaw offsets; // down is a fixed pitch-down view. typedef enum ViewDirectionE { VIEW_FORWARD = 0, VIEW_RIGHT = 1, VIEW_BACK = 2, VIEW_LEFT = 3, VIEW_DOWN = 4 } ViewDirectionE; // FS2 fixed-point shifts. Position uses the FS2 32-bit position cell // convention (low 16 bits fractional). Rates / speeds use 8.8. #define AC_POS_FRACT_BITS 16 #define AC_POS_FRACT_ONE (1 << AC_POS_FRACT_BITS) #define AC_RATE_FRACT_BITS 8 #define AC_RATE_FRACT_ONE (1 << AC_RATE_FRACT_BITS) // Compose a Q16.16 world coordinate from integer world units. #define AC_WORLD_UNITS(n) ((int32_t)(n) * AC_POS_FRACT_ONE) // Compose a uint8 throttle/flaps/mixture value from a percent 0..100. #define AC_BYTE_PCT(p) ((uint8_t)(((int)(p) * 255) / 100)) // Top-of-envelope forward speed in Q8.8 world-units/frame // (~1.6 wu/frame). audio.c references this to compute its wind hiss // amplitude. #define AC_MAX_FORWARD_SPEED_Q88 410 // Reality-mode instrument failure bits. Mirrors chunk3 // `InstrumentOperationalFlags` (init $FF = all good). The // chunk3 `FailureProcTable` clears one of these bits or sets one of // the engine-fault bits when the reality-mode roll trips, instead of // the immediate crash the port previously triggered. #define AC_FAIL_AIRSPEED 0x01 // bit 0 (FailInstrumentBit0) #define AC_FAIL_VSI 0x04 // bit 2 (FailInstrumentBit2) #define AC_FAIL_ALTIMETER 0x08 // bit 3 (FailInstrumentBit3) #define AC_FAIL_TURN_COORD 0x20 // bit 5 (FailInstrumentBit5) #define AC_FAIL_ATTITUDE 0x40 // bit 6 (FailInstrumentBit6) #define AC_FAIL_HEADING 0x80 // bit 7 (FailInstrumentBit7) #define AC_FAIL_ALL_INSTRUMENTS (AC_FAIL_AIRSPEED | AC_FAIL_VSI | AC_FAIL_ALTIMETER | AC_FAIL_TURN_COORD | AC_FAIL_ATTITUDE | AC_FAIL_HEADING) // Engine fault bits. SetEngineFault01 ORs $03 into $0991, SetEngineFault23 // ORs $0C. Two cylinder banks; either can fail independently. #define AC_ENG_FAULT_LEFT 0x03 #define AC_ENG_FAULT_RIGHT 0x0C // Mapping between aircraft metre-space and FS2 scenery units. // FS2 scenery uses ~feet as its base unit; we round to 3 units/metre // for clean integer math (the true ratio is 3.28 ft/m, so DME and // ground-track come out ~9% short — close enough until we get a // known-leg measurement to refine). #define AC_SCENERY_UNITS_PER_METRE 3 // Nautical mile = 1852 m * 3 units/m. Used by DME so the constant is // consistent with AC_SCENERY_UNITS_PER_METRE -- bumping one without // the other would make airspeed and DME disagree. #define AC_SCENERY_UNITS_PER_NM (1852 * AC_SCENERY_UNITS_PER_METRE) // Recenter threshold: when |worldX| or |worldZ| exceeds this many // metres the aircraftStep transparently slides the anchor and // shrinks the local coords. Keeps the local Q16.16 well clear of its // integer headroom (~32 km) so flight math never sees big numbers. #define AC_RECENTER_THRESHOLD_M 20000 typedef struct AircraftT { // Anchor: absolute FS2 scenery position of the aircraft's // local (worldX, worldY, worldZ) = (0, 0, 0). Same role as // FS2's section base. Updated transparently by the recenter // logic so the local coords stay small. int32_t sceneryOriginX; int32_t sceneryOriginY; int32_t sceneryOriginZ; // Local position relative to the anchor. Q16.16 metres. int32_t worldX; int32_t worldY; int32_t worldZ; // Orientation (byte angles, 256 == full turn). uint8_t pitch; uint8_t bank; uint8_t yaw; // Body-frame rate accumulators in Q8.8 byte-angles per frame. int16_t pitchRate; // +ve = nose up int16_t bankRate; // +ve = right wing down int16_t yawRate; // +ve = nose right // Linear motion. Q8.8 world units per frame. int16_t forwardSpeed; // along body +Z int16_t climbRate; // +ve = climbing // Pilot inputs. int8_t yokeVert; // -127..+127 (FS2 YokeVertPos) int8_t yokeHoriz; // -127..+127 int8_t rudder; // -127..+127 (FS2 RudderPos) uint8_t throttle; // 0..255 uint8_t flaps; // 0..255 (panel slider; no flight effect yet) int8_t trim; // -127..+127 uint8_t mixture; // 0..255 (rich..lean) // Status. bool onGround; bool stalled; bool envelopeWarning; bool crashed; CrashTypeE crashType; // Spin state. Set when a stalled aircraft is also yawing // significantly; the integrator then forces an autorotation // around the spin axis until the pilot applies opposite // rudder + nose-down to break it (= FS2 chunk3 SpinHandler). bool spinning; int8_t spinDirection; // -1 = left, +1 = right uint16_t spinFrameCounter; // Load factor (G) for the high-G / VNE bleed. Q8.8 g per // frame, sampled by the integrator from yokeVert + bank // contributions. FS2's `CheckFlightEnvelope` uses this to // gate VNE / airframe damage. int16_t loadFactor_q88; // Cumulative airframe damage from over-G or over-VNE events. // FS2 chunk3 stores this at $0898 and feeds it into the // reliability dispatch. Port mirrors the byte and forces a // crash when it saturates. uint8_t airframeDamage; // Slip / skid sideslip angle (signed Q8.8 byte-angle). FS2 // drives this from the lateral acceleration; we recompute it // from (yaw rate - turn-coord harmony) so the slip ball reads // from a real signal instead of just bankRate. int16_t sideslip_q88; // Crash-recovery snapshot armed flag. The actual saved state // lives in a static buffer inside aircraft.c (= FS2 $FC00+). bool hasRecoverySnapshot; // Slew mode (FS2 `SlewMode`). bool slewMode; bool showSlewDigits; int8_t slewPitchRate; int8_t slewRollRate; int8_t slewYawRate; int8_t slewAltRate; // Demo mode (FS2 chunk2 `DemoMode64K`). bool demoMode; uint8_t demoState; // mirrors FS2 `DemoModeParam3` // Edit mode (FS2 `EditModeFlag`). bool editMode; // Reality mode (FS2 chunk3 `RealityMode`). bool realityMode; uint8_t reliabilityFactor; uint16_t realityTickCounter; uint8_t failedInstruments; // see AC_FAIL_* bits uint8_t engineFaults; // see AC_ENG_FAULT_* bits // Cockpit toggles overlaid on top of the static panel text. bool lightsOn; bool carbHeatOn; // VOR2/ADF mode toggle. FS2 shares ONE physical instrument bay // between VOR2 (= CDI horizontal slider) and ADF (= rotating // bearing dial). chunk4 `ADFMode` flag selects which one is // active; chunk5 `DrawVOR2IndicatorChanges` and chunk3 // `UpdateADFIndicator` each early-out when the OTHER mode is // selected so only one set of needles/flags/digits paint at a // time. Default false = VOR2 mode (matches chunk4's compiled // initial value of 0 for ADFMode). bool adfMode; // Fuel state. FS2 chunk5 `UpdateFuelTankGauges` shows separate // L/R tanks; we model them in 0..255 byte units (=full..empty). uint8_t fuelLeft; uint8_t fuelRight; // Magneto state. Mirrors FS2 chunk5 `MagnetoState` ($0845): // 0 = OFF, 1 = R only, 2 = L only, 3 = BOTH, 4 = START. // Set by `ApplyMagnetoState` and the 1/2/3 keys. uint8_t magnetos; // Pause flag. FS2's `TogglePause` halts the integrator and // input processing until pressed again. bool paused; // Color / B&W display mode. FS2 prompts at boot for COLOR or // B/W; the choice patches the display kernel via // `ColorModePatch` / `BWModePatch`. We track it as a flag and // gate colour drawing accordingly. true = monochrome. bool monochrome; // Radar view (FS2 chunk4 RadarView). bool radarView; int16_t radarZoom; // Q8.8 metres per pixel // View direction. ViewDirectionE viewDirection; } AircraftT; void aircraftInit(AircraftT *ac); // Per-frame integrator. One call per video frame. `wind` may be NULL // to skip the chunk2 wind+turbulence pipeline. void aircraftStep(AircraftT *ac, WindStateT *wind); void aircraftToggleSlew(AircraftT *ac); void aircraftToggleDemo(AircraftT *ac); void aircraftToggleReality(AircraftT *ac); void aircraftToggleEdit(AircraftT *ac); // Copy aircraft pose into a camera so the renderer can transform the // world from the cockpit point of view. void aircraftSyncCamera(const AircraftT *ac, CameraT *cam); // Effective absolute scenery coordinates of the aircraft (anchor + // local position scaled into scenery units). int32_t aircraftSceneryX(const AircraftT *ac); int32_t aircraftSceneryY(const AircraftT *ac); int32_t aircraftSceneryZ(const AircraftT *ac); // Teleport: place the aircraft at an absolute scenery coordinate. // Used by spawn, region selection, etc. Resets the local coords to // zero and stores the absolute coordinate as the anchor. void aircraftTeleport(AircraftT *ac, int32_t sx, int32_t sy, int32_t sz); void aircraftAddThrottle(AircraftT *ac, int delta); // unit: 0..255 // Crash recovery: arm a snapshot of the current state, then restore it // on demand. FS2 $FC00+ keeps a single image so the pilot can resume // after a crash overlay. void aircraftArmRecovery(AircraftT *ac); void aircraftRestoreRecovery(AircraftT *ac); void aircraftDecayYokeVert(AircraftT *ac, uint8_t k_q8); void aircraftDecayYokeHoriz(AircraftT *ac, uint8_t k_q8); void aircraftDecayRudder(AircraftT *ac, uint8_t k_q8); #endif