fs2port/port/include/aircraft.h
2026-05-13 21:32:05 -05:00

274 lines
11 KiB
C

// 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 <stdbool.h>
#include <stdint.h>
#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))
// 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