Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ec04c1d
Merge pull request #304 from TwinFan/Next
TwinFan Mar 27, 2026
decc011
Merge pull request #305 from TwinFan/Next
TwinFan Mar 27, 2026
39c8816
v4.4.0 Airplanes.live - Merge pull request #310 from TwinFan/Next
TwinFan Apr 28, 2026
5af794d
chore: ignore local helper and editor artifacts
nebukadnezar May 12, 2026
98f7f71
feat/Ground: Add tunables for ground heading stability
nebukadnezar May 12, 2026
511bc67
feat/Ground: Hard-set pitch and roll on the ground
nebukadnezar May 12, 2026
cbd27fa
feat/Ground: Stationary heading freeze and hysteresis in CalcHeading
nebukadnezar May 12, 2026
51d319b
feat/Ground: Rate-limit per-frame heading change on the ground
nebukadnezar May 12, 2026
c517eb5
feat/Ground: Holding timeout suppresses trivial feed updates
nebukadnezar May 12, 2026
df0f1e3
feat/Ground: Lower Bezier activation threshold for taxi turns
nebukadnezar May 12, 2026
22e4d4b
feat/Ground: Detect pushback and keep nose pointed at gate
nebukadnezar May 12, 2026
a2decf9
fix/Ground: Set ground pitch to 0 and add diagnostic logging
nebukadnezar May 12, 2026
a1e0423
fix/Ground: Retune dance-suppression thresholds from real feed data
nebukadnezar May 12, 2026
51a1ced
fix/Ground: Trust feed-provided heading at slow ground speed
nebukadnezar May 12, 2026
1102320
fix/Livery: Tail-number detection in FDStaticData::airlineCode
nebukadnezar May 12, 2026
658dc6b
fix/Ground: De-rotate smoothly on landing
nebukadnezar May 12, 2026
6aad04c
fix/Ground: Track motion direction at high ground speed
nebukadnezar May 12, 2026
9477671
fix/Ground: Use leg-average speed not current speed for track-heading
nebukadnezar May 12, 2026
4528131
fix/Ground: Skip SnapToTaxiways while in ground-holding
nebukadnezar May 13, 2026
82042be
fix/Weather: Reject placeholder QNH=1013 responses from RealTraffic
nebukadnezar May 13, 2026
21c0a36
fix/Channels: Reset validity when channel checkbox is re-enabled
nebukadnezar May 13, 2026
d861869
fix/Ground: Cross-check feed heading vs track to catch EHS staleness
nebukadnezar May 13, 2026
f9941c3
fix/Ground: Smooth altitude blend on liftoff (no more pop-up)
nebukadnezar May 13, 2026
6fb0b1f
feat/RT: Lower default traffic-request wait floor to 2s
nebukadnezar May 14, 2026
a28ae93
fix/Ground: Apply heading cross-check before SnapToTaxiways
nebukadnezar May 14, 2026
486b916
feat/Ground: Centripetal Catmull-Rom spline for position + heading
nebukadnezar May 14, 2026
cee1c1c
fix/Channel: Enable TCP keepalive on curl handles
nebukadnezar May 14, 2026
600904f
fix/Channel: Forbid curl connection reuse to stop persistent error 55
nebukadnezar May 14, 2026
78b58db
feat/Ground: Spline rendering quality - reparam, smoothing, fallbacks
nebukadnezar May 14, 2026
5336ce3
fix/Parked: Make RealTraffic parked traffic show, persist and face right
nebukadnezar May 14, 2026
908ca73
fix/Apt: Gate startup-loc snapping on airport-layout availability
nebukadnezar May 16, 2026
95a4189
wip/Pushback: State machine + heading-source selection (still incompl…
nebukadnezar May 16, 2026
411ac33
feat/RT: Use v6 operator-livery field for livery matching
nebukadnezar May 16, 2026
1c9715c
fix/Gate: Durable handoff eviction + skip re-seed for departed aircraft
nebukadnezar May 16, 2026
d36ba8c
feat/Climb: Smooth takeoff/climb altitude + liftoff-blend ground lock
nebukadnezar May 16, 2026
25c9e33
fix/Pitch: Cap rotate pitch at 10deg + keep LIFT_OFF in pitch overrid…
nebukadnezar May 17, 2026
cf8c6c4
revert/RT: Drop posTime -= PosAge adjustment, use raw TimeStamp (idx 10)
nebukadnezar May 17, 2026
7a072fa
feat/PB: Gate-parked aircraft pushback rendering — full pipeline rework
nebukadnezar May 17, 2026
8c9f16b
feat/PB: Safety-valve exit + placeholder-hex duplicate prune
nebukadnezar May 17, 2026
2322092
fix/CI: Guard empty-string FSC env vars in CMakeLists
nebukadnezar May 17, 2026
e844b33
Merge pull request #312 from nebukadnezar/master
TwinFan May 17, 2026
d3a7ba8
fix: Wait for weather thread during shutdown
TwinFan May 17, 2026
b3ad75d
chore: Replace ROTATE_PITCH_MAX_DEG with pMdl->PITCH_FLARE
TwinFan May 17, 2026
844d40c
chore: Replace TOUCHDOWN_HOLD_PITCH with new pMdl->PITCH_HOLD_TOUCHDOWN
TwinFan May 18, 2026
b6fc692
chore: Added "Debug/Log Diagnostics" to Setting
TwinFan May 18, 2026
6ea6f7c
chore: Added PITCH_ROTATE to FlightModel and use for Rotate phase
TwinFan May 18, 2026
2170313
chore: v4.5.0 and release notes
TwinFan May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ xcuserdata/

# MS Office Temporary Files
~$*

# Local assistant artifacts — never commit
CLAUDE.md
.claude/
*claude*
*Claude*
.aider*
25 changes: 19 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ endif()
set(CACHE{CMAKE_BUILD_TYPE} TYPE STRING VALUE "RelWithDebInfo")

project(LiveTraffic
VERSION 4.4.1
VERSION 4.5.0
DESCRIPTION "LiveTraffic X-Plane plugin")
set(VERSION_BETA 0)

Expand Down Expand Up @@ -57,25 +57,38 @@ endif()
# For local builds, set environment variables before running cmake

# Production environment
if(DEFINED ENV{FSC_PROD_CLIENT_ID})
#
# The checks below reject an env var that is DEFINED but EMPTY (in
# addition to the obvious not-defined case). GitHub Actions exposes
# repository secrets as env-var assignments unconditionally — when a
# fork PR doesn't have the secret configured, GA passes the env var
# as the empty string, NOT as undefined. Without the `STREQUAL ""`
# guard, the cmake fall-through to the default `"3"` / `"INOP"` stub
# values never fires, the cmake substitution at line ~320 then emits
# bare `-DFSC_PROD_CLIENT_ID=` (no value), the preprocessor expands
# `FSC_PROD_CLIENT_ID` to nothing in `Src/LTFSCharter.cpp`, and the
# Linux CI build fails with "expected primary-expression before ','".
# This affects every fork PR; the empty-string guard makes the
# default-fallback path correct in both unset and empty-string cases.
if(DEFINED ENV{FSC_PROD_CLIENT_ID} AND NOT "$ENV{FSC_PROD_CLIENT_ID}" STREQUAL "")
set(FSC_PROD_CLIENT_ID "$ENV{FSC_PROD_CLIENT_ID}" CACHE STRING "FSCharter production client ID")
else()
set(FSC_PROD_CLIENT_ID "3" CACHE STRING "FSCharter production client ID")
endif()
if(DEFINED ENV{FSC_PROD_CLIENT_SECRET})
if(DEFINED ENV{FSC_PROD_CLIENT_SECRET} AND NOT "$ENV{FSC_PROD_CLIENT_SECRET}" STREQUAL "")
set(FSC_PROD_CLIENT_SECRET "$ENV{FSC_PROD_CLIENT_SECRET}" CACHE STRING "FSCharter production client secret (base64)")
else()
# Default fallback value, is handled specifically by LiveTraffic code
set(FSC_PROD_CLIENT_SECRET "INOP" CACHE STRING "FSCharter production client secret (base64)")
endif()

# Staging environment
if(DEFINED ENV{FSC_STAGING_CLIENT_ID})
# Staging environment — same empty-string-guard rationale as above.
if(DEFINED ENV{FSC_STAGING_CLIENT_ID} AND NOT "$ENV{FSC_STAGING_CLIENT_ID}" STREQUAL "")
set(FSC_STAGING_CLIENT_ID "$ENV{FSC_STAGING_CLIENT_ID}" CACHE STRING "FSCharter staging client ID")
else()
set(FSC_STAGING_CLIENT_ID "3" CACHE STRING "FSCharter staging client ID")
endif()
if(DEFINED ENV{FSC_STAGING_CLIENT_SECRET})
if(DEFINED ENV{FSC_STAGING_CLIENT_SECRET} AND NOT "$ENV{FSC_STAGING_CLIENT_SECRET}" STREQUAL "")
set(FSC_STAGING_CLIENT_SECRET "$ENV{FSC_STAGING_CLIENT_SECRET}" CACHE STRING "FSCharter staging client secret (base64)")
else()
# Default fallback value, is handled specifically by LiveTraffic code
Expand Down
451 changes: 450 additions & 1 deletion Include/Constants.h

Large diffs are not rendered by default.

119 changes: 119 additions & 0 deletions Include/CoordCalc.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#define CoordCalc_h

#include "XPLMScenery.h"
#include <array>
#include <deque>

// positions and angles are in degrees
Expand Down Expand Up @@ -107,6 +108,124 @@ positionTy CoordPlusVector (const positionTy& pos, const vectorTy& vec);
// returns NaN in case of failure
double YProbe_at_m (const positionTy& posAt, XPLMProbeRef& probeRef);

/// @brief Result of evaluating a Catmull-Rom spline at one parameter
/// @details All values are expressed in the local meters frame centred at
/// the spline's `P1` control point (see `CatmullRomEvalCentripetal`):
/// `x` increases eastward, `y` increases northward. `headingDeg` is
/// the curve's tangent direction at this parameter, converted to
/// the same compass convention LiveTraffic uses elsewhere
/// (0° = north, 90° = east, range [0, 360)).
struct CatmullRomResult {
double xMtr; ///< east offset from P1 in metres
double yMtr; ///< north offset from P1 in metres
double headingDeg; ///< curve tangent direction [0, 360)
};

/// @brief Evaluate a centripetal Catmull-Rom spline through four ground
/// positions at one parameter, returning both the curve point and
/// the tangent direction at that point.
///
/// @details The spline interpolates **exactly through** `P1` and `P2` and
/// uses `P0` and `P3` as context to determine the tangent at the
/// endpoints. The centripetal parameterisation (α = 0.5 in
/// Lee 2009) makes the curve well-behaved at sharp corners — it
/// never produces the cusps or self-intersecting loops that
/// uniform Catmull-Rom can generate at a 90° taxi turn.
///
/// The curve is fit in a local meters frame centred at `P1`,
/// using `Lat2Dist` / `Lon2Dist` for the conversion. This keeps
/// the math Euclidean (avoids cos(lat) accumulating across
/// control points) and the result is returned in the same local
/// frame; callers convert back to lat/lon via `Dist2Lat` /
/// `Dist2Lon` if a geographic position is needed.
///
/// The heading is derived from the curve's tangent
/// (`atan2(dx, dy)`) so the returned heading is by construction
/// aligned with the rendered direction of motion at this point —
/// this is the property that eliminates the "sideways during
/// turn" symptom that linear-chord interpolation produces.
///
/// @param P0 Control point before `P1`. May be a degenerate copy of `P1`
/// (same lat/lon) when no real predecessor is available — the
/// curve degenerates to a quadratic segment with zero tangent
/// at `P1`. Caller is responsible for choosing whether to do
/// this duplication; the function does NOT check for NaN.
/// @param P1 First interpolated control point — the curve passes through
/// this exactly at `u = 0`. Origin of the returned local frame.
/// @param P2 Second interpolated control point — the curve passes through
/// this exactly at `u = 1`.
/// @param P3 Control point after `P2`. May be a degenerate copy of `P2`.
/// @param u Curve parameter in `[0, 1]`; `u=0` returns `P1` (with the
/// local frame's origin), `u=1` returns `P2`.
/// @return Curve point in local meters frame relative to `P1`, and
/// tangent-derived heading at that point in degrees.
///
/// @note Only the lat/lon of the control points are used. Altitude,
/// heading, and timestamps are ignored — the spline is purely a
/// horizontal-plane construction.
CatmullRomResult CatmullRomEvalCentripetal(const positionTy& P0,
const positionTy& P1,
const positionTy& P2,
const positionTy& P3,
double u);

/// @brief Cached arc-length lookup table for a Catmull-Rom spline segment,
/// used to make the rendered animation advance at constant arc-length
/// speed (rather than constant knot-parameter speed).
///
/// @details The native parameter `u ∈ [0, 1]` of `CatmullRomEvalCentripetal`
/// is NOT proportional to arc length on the curve — the spline's
/// arc length per unit `u` varies with local curvature. If the
/// caller advances `u` linearly with time, the rendered position
/// accelerates and decelerates within the segment (visible as a
/// "speed up / slow down" pulsation), and the velocity at the start
/// of leg N+1 does not match the velocity at the end of leg N
/// (visible as a small velocity pop at every segment boundary).
///
/// This LUT subdivides the segment at `N` uniformly-spaced values
/// of `u`, evaluates the curve at each, and accumulates chord-based
/// arc length. The mapping s→u is then queried per frame to turn
/// a time-linear progression (0..1 across the leg duration) into a
/// curve parameter that advances at constant arc-length-per-time.
/// Visually the rendered aircraft now moves at the segment's mean
/// speed (`total_arc / duration`) throughout the segment.
///
/// `N = 16` is a deliberate trade-off: the chord error against the
/// true integral is below 0.1 % on the curvatures we see at airport
/// ground speeds, the build cost is 16 spline evaluations per
/// segment switch (~once per 1-5 s of feed), and the per-frame
/// lookup is a 4-step binary search plus one lerp.
struct CatmullRomArcLut {
static constexpr int N = 16; ///< number of sample sub-intervals; N+1 entries

/// Cumulative arc length at each sample. `sAtU[i]` is the arc length
/// from `u=0` to `u = i / N`. Always `sAtU[0] == 0`.
std::array<double, N + 1> sAtU{};
/// Total arc length of the segment (i.e., `sAtU[N]`). Cached for
/// quick access in the per-frame lookup.
double totalArc = 0.0;
/// True once `Build()` has populated the table for the current segment.
/// Reset to false when the parent segment switches so the next render
/// frame rebuilds with the new control points.
bool valid = false;

/// Sample the spline at `N+1` uniformly-spaced `u` values, accumulate
/// chord lengths between successive samples, and store the running
/// totals in `sAtU`. Must be called whenever the control-point set
/// changes (i.e., at every segment switch in `LTAircraft::CalcPPos`).
void Build(const positionTy& P0, const positionTy& P1,
const positionTy& P2, const positionTy& P3);

/// Given a time-linear progression `f ∈ [0, 1]` across the segment,
/// return the curve parameter `u ∈ [0, 1]` at which the spline has
/// covered `f * totalArc` of arc length. The mapping is inverted by
/// a short binary search across the LUT plus one linear interpolation.
/// If the LUT has zero total arc (e.g., all control points coincided)
/// the function returns `f` unchanged — the spline will collapse to
/// a point anyway, so the choice of parameter is irrelevant.
double UFromArcFraction(double f) const;
};

//
// MARK: Estimated Functions on coordinates
//
Expand Down
3 changes: 3 additions & 0 deletions Include/DataRefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ enum dataRefsLT {
// debug options
DR_DBG_AC_FILTER,
DR_DBG_AC_POS,
DR_DBG_DIAGNOSTIC, ///< Detailed diagnostic log output
DR_DBG_LOG_RAW_FD,
DR_DBG_LOG_WEATHER,
DR_DBG_MODEL_MATCHING,
Expand Down Expand Up @@ -688,6 +689,7 @@ class DataRefs
int bShowingAircraft = false;
unsigned uDebugAcFilter = 0; // icao24 for a/c filter
int bDebugAcPos = false;// output debug info on position calc into log file?
int bDebugDiagnostic = false;///< output detailed diagnostic debug messages that really fill up your Log very fast
int bDebugLogRawFd = false;// log raw flight data to LTRawFD.log
int bDebugWeather = false;///< log weather data for debugging
exportFDFormat eDebugExportFdFormat = EXP_FD_AITFC; ///< Which format to use when exporting flight data?
Expand Down Expand Up @@ -1087,6 +1089,7 @@ class DataRefs
// livetraffic/dbg/ac_pos: Debug Positions for given a/c?
inline bool GetDebugAcPos(const std::string& key) const
{ return bDebugAcPos && key == GetSelectedAcKey(); }
bool ShallLogDiagnostics() const { return bDebugDiagnostic; }

inline bool GetDebugLogRawFD() const { return bDebugLogRawFd; }
void SetDebugLogRawFD (bool bLog) { bDebugLogRawFd = bLog; }
Expand Down
Loading