Conversation
v4.3.5 XP 12.4.1 Update
fix/RT: fixed epoch handling, processes buffered a/c
v4.4.0 Airplanes.live
Adds patterns for local-only scratch files and helper scripts that should not be tracked.
Introduces a new "Ground Behavior Stability" block of compile-time constants that will drive a layered fix for the visual "dance" seen when stationary or slow-taxiing aircraft are rendered from jittery 1 Hz feed positions: heading hysteresis, per-frame heading rate limiting, stationary detection, holding suppression, hard-set ground attitude, and pushback detection thresholds. This change only declares the constants — no behaviour is wired up yet. Each value carries an inline rationale comment so the chosen threshold is justified at the point of use.
Forces rendered pitch/roll to GND_PITCH_DEG / GND_ROLL_DEG each frame whenever bOnGrnd is true, with phase exceptions for FPH_ROTATE, FPH_FLARE and FPH_TOUCH_DOWN where the nose is dynamically moving relative to the ground. Also aligns the pitch MovingParam: aircraft spawned on the ground start at GND_PITCH_DEG, and the touch-down phase walks pitch to GND_PITCH_DEG (was 0). Eliminates a class of subtle pitch/roll drift that produced visual "nose-bobbing" and micro-bank in slow-taxi and parked aircraft.
Adds two ground-only filters to LTFlightData::CalcHeading: 1. Stationary freeze: when the slot is on the ground and the derived groundspeed to its neighbour(s) is at or below GND_STATIONARY_GS_KT, reuse the predecessor's already-filtered heading instead of computing one from jittery position deltas. Requires both adjacent segments (or the only available one at deque ends) to be stationary, so a single tight cluster during taxi does not lock the heading. 2. Hysteresis: after the heading has been computed, snap it to the predecessor's value when the difference is below GND_HEADING_HYSTERESIS_DEG. Absorbs the sub-degree noise that ADS-B/MLAT feeds routinely contain even on a straight ground roll. Both filters apply only to on-ground slots; airborne logic is unchanged. Together with the SIMILAR_POS_DIST short-circuit that already existed, these stop the heading sequence at gates from swinging wildly under feed jitter.
Adds a clamp in LTAircraft::CalcAcPos that limits the per-frame change in ppos.heading() to GND_HEADING_MAX_RATE_DPS * dt when the aircraft is on the ground. The check uses HeadingDiff for signed shortest-path semantics across the 360°/0° wrap and re-syncs the heading MovingParam so subsequent frames continue from the clamped value rather than catching up in a snap. Airborne heading propagation is unchanged: in the air, the heading MovingParam is already smoothed via FLIGHT_TURN_TIME, and an extra clamp would make en-route course changes visibly lag. Together with the deque-side stationary freeze and hysteresis, this ensures that even a deliberate large heading jump at slow taxi (e.g., new "to" position with a new heading) walks smoothly to the target instead of snapping.
Adds a per-aircraft holding state machine in LTFlightData: - groundHoldingSinceTs tracks the start of the current stationary- on-ground streak (feed timestamp, not sim time). - bGroundHolding flips to true once the streak exceeds GND_HOLDING_TIMEOUT_S. In AddNewPos, once bGroundHolding is set, incoming positions that fall within GND_HOLDING_TRIVIAL_DIST_M of the latest known position and whose derived groundspeed is below GND_STATIONARY_GS_KT are dropped silently — never reaching posToAdd. Movements that exceed the trivial-distance envelope (potential pushback or taxi start) always go through and reset the holding state. This is the data-layer half of the ground "dance" fix: long-parked aircraft become rock-steady because the noisy feed updates never reach the renderer. Verbose debug logs are emitted on state transitions when GetDebugAcPos is enabled for the aircraft.
Adds GND_BEZIER_MIN_HEAD_DIFF (1°) and uses it in place of the airborne 2.5° BEZIER_MIN_HEAD_DIFF when the aircraft is on the ground. Effect: slow taxi turns of 1–2° per leg are now rendered as a Bezier curve with tangent-derived heading instead of as a polyline whose heading walks via the MovingParam fallback. Combined with the rate-limit clamp and the deque-side stationary freeze, this gives visually fluid ground turns at all sizes without making airborne course corrections enter/exit Bezier mode constantly.
Adds a state-free pushback heuristic to LTFlightData::CalcHeading. When a ground slot derived groundspeed falls between GND_STATIONARY_GS_KT and PUSHBACK_DETECT_GS_MAX_KT and the track direction is at least PUSHBACK_DETECT_HEAD_DIFF_DEG away from the previous heading, the slot heading is locked to the predecessor heading rather than rotated to match the (backward) track. The rendered aircraft then visually moves backwards while keeping its nose pointed at the gate, which is the correct pushback appearance. Detection is purely sample-based: every slot is classified on its own geometry, so no extra per-aircraft state is needed and the behaviour naturally turns off when the tug stops (next slot below stationary threshold) or when the aircraft starts forward taxi (track realigns with heading).
- GND_PITCH_DEG: 2.0 -> 0.0. The 2 deg value looked wrong on common narrow-bodies (tail-dragger appearance). 0 deg matches LiveTraffic pre-existing convention (touch-down already walked pitch to 0). - Adds temporary unconditional diagnostic logging tagged GND_DIAG_ADD, GND_DIAG_CHD, GND_DIAG_HYST, GND_DIAG_FREEZE, GND_DIAG_HOLDIN, GND_DIAG_HOLDOUT, GND_DIAG_DROP in LTFlightData::AddNewPos and CalcHeading. These fire for every on-ground feed update and heading computation regardless of GetDebugAcPos, so per-aircraft pos debug does NOT need to be enabled. Search the log for "GND_DIAG_" to isolate. To be removed once thresholds are tuned.
Diagnostic logs from AA3107 (parked, RealTraffic feed providing a
stable heading) and AA2675 (slow-taxiing, RealTraffic feed providing
no heading) showed three concrete misfits in the previous thresholds:
- Parked aircraft routinely produced derived gs of 0.75-1.05 kt at the
gate purely from feed jitter, just above the 0.5 kt stationary
threshold. Holding therefore exited and small position movements
propagated to the renderer.
- Slow-taxi heading wobbled 5-7 deg per slot due to track-from-pos-
delta on RealTraffic positions without a heading field. The 0.5 deg
hysteresis never engaged.
- A single isolated above-threshold slot ended the holding streak
immediately, requiring another full 30 s of stationary samples
before suppression resumed.
Changes:
- GND_STATIONARY_GS_KT 0.5 -> 1.5 (above gate-jitter band)
- GND_HEADING_HYSTERESIS_DEG 0.5 -> 4.0 (catches slow-taxi wobble)
- GND_HEADING_MAX_RATE_DPS 60.0 -> 12.0 (matches TAXI_TURN_TIME)
- GND_HOLDING_TRIVIAL_DIST_M 7.0 -> 15.0 (covers observed envelope)
- new GND_HOLDING_EXIT_CONSEC = 2 (consecutive non-stationary
slots needed to exit)
Holding state machine in AddNewPos updated:
- Stationary slot resets groundNonStationaryCnt to 0.
- Non-stationary slot increments the counter; only exits holding once
the counter reaches GND_HOLDING_EXIT_CONSEC.
- On exit, groundHoldingSinceTs is reset to the current ts (not 0) so
that if motion ceases again immediately, the next holding promotion
is timed from the resumption of stationarity.
When the data feed (e.g. RealTraffic) supplies an explicit heading value AND the aircraft is on the ground moving below GND_USE_FEED_HEADING_MAX_KT (10 kn), use the feed value directly and skip the position-from-track derivation entirely. The previous behaviour computed heading from atan2 over consecutive lat/lon pairs, which is wrong in two important slow-ground cases: 1. Pushback. The aircraft is moving tail-first, so the track-over- ground points OPPOSITE to the nose. atan2 produced a heading ~180 deg off, which is exactly what was observed for AA1146 and AA2980 during real pushbacks at gates: their feed heading was correctly ~30 deg / ~20 deg but the rendered nose snapped to 270 deg matching the backwards motion. 2. Parked aircraft with feed-fabricated jitter. The "motion" vector between samples is dominated by noise, so the derived heading wanders. Meanwhile the feed-reported heading is usually stable and correct. The new fast-path check runs before all other heading logic in CalcHeading: if on ground and feed heading is non-NaN and derived gs < threshold, accept the feed value and return. Above the threshold the track-derived heading is the better source (it reflects the curve the aircraft is actually flying) so we fall through to the normal logic. A new GND_DIAG_FEEDHDG diagnostic logs each trust event so we can verify the rule fires for pushback/slow-taxi traffic.
Previously, FDStaticData::airlineCode() returned call.substr(0,3) when opIcao was empty. For aircraft whose callsign is actually a tail registration (N552FX, 9K1876, 1I637, etc.) this produced garbage 3-character strings like "N55", "9K1", "1I6" which XPMP2 then tried to match as an airline. No airline ICAO code begins with a digit, so the match always failed and XPMP2 fell back to "any livery of this aircraft class" — producing visibly random liveries. The fix is a simple alpha test: the first three characters of an ICAO airline ATC callsign are always letters (AAL, RPA, QFA). If any of the first three characters is not alphabetic, return an empty string so XPMP2 does type-only matching instead. From a captured session, this affects 7 of 36 wrong-livery outcomes (~20%): N-prefix registrations of small jets, GA piston aircraft, and entries where the feed delivers a registration in the callsign field. Companion change (not in this commit, ships separately): the installed Resources/relOp.txt has been extended with North American codeshare/merger groups (Republic with American Eagle, SkyWest with United Express, Endeavor with Delta Connection, US Airways with American, Continental with United, Northwest with Delta, etc.) so regional carriers without dedicated CSL liveries fall back to their parent mainline's livery rather than a random unrelated airline. That change lives at /Users/balt/X-Plane 12/Resources/plugins/ LiveTraffic/Resources/relOp.txt (the file XPMP2 actually loads at runtime) and has a mirror in the XPMP2 submodule source at Lib/XPMP2/Resources/relOp.txt which has not been committed because it requires a separate commit inside the submodule.
The ground-pitch override in LTAircraft::CalcAcPos was snapping pitch to GND_PITCH_DEG one frame after touchdown. FPH_TOUCH_DOWN is documented as a single-cycle event; the next frame is FPH_ROLL_OUT, which the override did not previously exempt. So even though CalcFlightModel kicked off a smooth pitch.moveTo(GND_PITCH_DEG) on touchdown, the override clobbered the MovingParam's walk on the very next frame — producing a one-frame nose-slam visible to the user. Fix: add FPH_ROLL_OUT to the phase exception list. During roll-out the MovingParam now walks pitch smoothly from PITCH_FLARE down to GND_PITCH_DEG over ~1 second. Once roll-out ends and the aircraft enters FPH_TAXI, the override applies again and pitch is locked at GND_PITCH_DEG (which by then matches the MovingParam value). Roll is still forced flat in all phases including the dynamic-pitch ones — there is no scenario where a wheeled aircraft banks during rotation, flare, touchdown, or roll-out.
Landing rollout and takeoff aircraft were rendered with their nose pointing toward the NEXT slot's reported heading instead of along their actual direction of motion. When the next slot was on a turn- off taxiway (heading 326 deg) and the current slot was at end-of- runway (heading 020 deg), BezierCurve::Define used to.heading() as the end-tangent, arcing the rendered path across the runway corner. Result: aircraft visually "slides off the runway" with its nose ~50 deg off the motion vector. Fix: introduce GND_TRACK_HEADING_MIN_KT (10 kn) as the boundary between "trust the feed heading" (slow taxi / pushback / parked) and "trust the motion vector" (rollout / takeoff / fast taxi). In LTAircraft::CalcAcPos two coordinated changes: 1. At leg setup, when on ground and gs >= GND_TRACK_HEADING_MIN_KT, skip BezierCurve::Define and fall through to the linear-path branch. The linear path already targets `vec.angle` (the bearing from `from` to `to` — the actual motion direction) as the heading goal, which is what an aircraft physically does on the ground at speed: nose along the track. 2. At the half-way-through-leg checkpoint, skip the retarget to `to.heading()` under the same condition. The leg-start setup has already pointed heading at the motion vector; retargeting would restart the same Bezier-clobber problem. Once gs drops below the threshold, the retarget is allowed again so the aircraft can converge on the slot's reported orientation for turn-offs, gate manoeuvres, etc. 10 kn is deliberately the same threshold as GND_USE_FEED_HEADING_MAX_KT so the two rules form one consistent boundary: below 10 kn, the feed heading wins; at or above 10 kn, the motion vector wins. No middle ground.
A previous commit added a bGndFast guard so that at high ground speed the renderer skips the Bezier path between slots and walks heading along the motion vector. The guard used GetSpeed_kt() — the currently-rendered speed at leg-setup time — which is the speed the aircraft is COMING INTO the leg, not the speed during the leg. This produced a regression on takeoff (ADO15 observed): the leg that spans taxi-into-runway has gs entering at ~5 kt and gs leaving at ~12 kt (47 m in 7.84 s). At leg-setup the rendered speed is ~5 kt, so bGndFast was false, Bezier was set up across the 89 deg corner from taxi heading (59 deg) to runway heading (330 deg), and the rendered nose arced through ~14 deg mid-leg while the aircraft positioned onto the runway. The subsequent runway-roll legs at gs > 50 kt did engage track-heading and started walking nose toward the motion vector — but the MovingParam's walk rate (TAXI_TURN_TIME) is too slow to converge before liftoff, so the aircraft was still ~45 deg off motion direction through rotation and into climb-out. Fix: use the leg's average speed vec.speed_kn() = dist/dt instead. The taxi-to-runway leg averages 11.8 kt — above the 10 kt threshold — so track-heading engages from the moment the aircraft enters that transition leg, and the nose walks straight toward the motion vector (which is along the runway) rather than arcing through the corner. By the time the aircraft is at runway-roll speeds, the rendered heading is already aligned with the runway centerline, liftoff happens nose-aligned, and the climb-out continues nose-aligned. Same fix applied to the half-way retarget skip so the two conditions remain consistent.
LTFlightData::SnapToTaxiways inserts taxiway-node waypoints into posDeque with heading=NaN, expecting CalcHeading to fill it in later. For a parked aircraft in bGroundHolding an isolated large position jump from the feed (observed: ACA34 at YSSY, 105 m jump while the RT app showed the aircraft stationary) gets accepted — it exceeds the 15 m trivial-drop threshold — and SnapToTaxiways then synthesises a sequence of intermediate waypoints along the airport taxi graph between the original parking position and the jumped position. CalcHeading on those phantom slots sees hdg_in=NaN, fails the feed-heading-trust check, fails the stationary check (the synthetic inter-waypoint derived gs is ~7 kt), and falls through to vector- from-pos-delta. The resulting headings reflect the taxiway geometry (302° → 256° → 244° in the observed case), not the aircraft's nose direction. The renderer walks the aircraft through the phantom path and the parked aircraft visually dances along a non-existent taxi route. Fix: skip the snap loop entirely when bGroundHolding is true. The glitched position still enters posDeque, but it is now interpolated as a single linear segment (a one-time visual wobble at worst) and no waypoints with their rotating headings are inserted. When the aircraft genuinely begins to taxi, AddNewPos's GND_HOLDING_EXIT_CONSEC counter clears bGroundHolding and snap-to-taxiway resumes for subsequent slots.
When RealTraffic's weather endpoint sometimes returns a stripped-down
response — no ICAO, no METAR, QNH=1013 — for a query location whose
real weather it previously delivered correctly. Observed at YSSY: a
valid {"ICAO":"YSSY","QNH":1034,"METAR":...} response followed ~60 s
later by a {"QNH":1013, no ICAO, no METAR} response for the same
query coordinates. The 1013 is RealTraffic's standard-pressure
placeholder, not a real reading.
The previous code in PreProcessWeather accepted these placeholders
unconditionally. rtWx.QNH got overwritten from 1034 back to 1013.25,
and BaroAltToGeoAlt_ft stopped applying the local-pressure correction.
At YSSY (real QNH 1034) the correction is +560 ft per 1500 ft of baro
altitude; without it, landing aircraft render ~500 ft below true MSL
and touch down on the surrounding terrain a mile short of the runway
threshold.
Fix: in PreProcessWeather, treat a response as a placeholder and skip
the SetWeather call when ALL of:
* no ICAO/airport identifier
* no METAR text
* QNH ~ 1013.25 (within 0.5 hPa)
* we already hold a confirmed non-standard QNH
The last condition lets a genuine 1013-hPa reading still arrive as a
first weather update; we only reject placeholders when they would
overwrite a previously confirmed non-standard value.
A channel that hit too many consecutive network errors gets marked bValid=false. While invalid, LTChannel::shallRun() returns false and LTFlightDataAcMaintenance() never restarts its thread — even if the user toggles the channel's enable checkbox off and back on, because the toggle only flips bChannel[ch] and leaves bValid alone. The intended recovery path was the hidden "Restart Stopped Channels" button in Settings → Basic (which calls LTFlightDataRestartInvalidChs). Most users won't notice that button and will instead reach for the channel's own checkbox, which silently does nothing — the thread never resumes even though the UI now claims the channel is enabled. Fix: in DataRefs::SetChannelEnabled, when bEnable=true, look up the LTChannel and call SetValid(true) on any channel that is currently invalid. This makes the off-then-on toggle behave the way the user expects: a re-enable revives an invalidated channel and the next maintenance tick (~2 s later) starts its network thread. SetValid(true) also resets errCnt, so the channel gets a fresh CH_MAC_ERR_CNT budget before it could go invalid again. If the underlying failure is persistent (bad credentials, server still down, …) the channel will simply re-invalidate on the next attempt and the user gets immediate visible feedback that something is wrong with the configuration — preferable to silent inaction.
A tester reported aircraft "driving sideways" during parts of taxi routes — after leaving the gate, before starting the taxi to take off, and on parts of the taxi-to-gate after landing. Investigation showed the symptom is concentrated on TURNS, not straight segments. Root cause: the heading field that LiveTraffic trusts at slow ground speed (`GND_USE_FEED_HEADING_MAX_KT = 10 kn`) is sourced from Mode S Enhanced Surveillance (EHS), which typically only updates every ~10 s and is unavailable entirely in regions without enhanced interrogation coverage. Between EHS updates the value is held constant by the receiver. During a taxi turn at 5–10 kn the aircraft can change direction by 60° or more inside one 10 s freshness window — and the held value lags. The renderer was trusting the stale value, so the nose stayed at the pre-turn direction while the body progressed along the new direction. Fix: cross-check the feed heading against the position-derived track (bearing from the predecessor slot to this one — always "now") inside the on-ground feed-heading branch of LTFlightData::CalcHeading. Three bands gated by the new GND_FEED_TRACK_AGREE_DEG = 30° constant: |Δ| < 30° agree trust feed (straight taxi or fresh EHS) 30° ≤ |Δ| ≤ 150° disagree fall through (feed has lagged in turn) |Δ| > 150° opposite trust feed (pushback) Unchanged at fast ground speeds: above 10 kn the track-heading regime in LTAircraft::CalcAcPos already owns the rendering. Unchanged when stationary: the track is meaningless under positional jitter so feed still wins. Unchanged in pushback: the >150° band detects the reversed track explicitly. Diagnostic GND_DIAG_FEEDHDG log line is extended with the track angle and a short reason string so future regressions are easy to attribute from a Log.txt capture.
Reported symptom: aircraft taking off "jump into the air" at rotation — the rendered altitude teleports from runway level to a few hundred feet in a single frame rather than walking up gradually as the aircraft rotates and lifts off. Cause: while the aircraft is on the ground, CalcPPos's `if (bOnGrnd)` branch clamps ppos.alt_m to terrainAlt_m every frame. The very next frame after CalcFlightModel decides the aircraft is airborne (`bOnGrnd` flips from true to false, phase becomes FPH_LIFT_OFF), that clamp stops applying and ppos.alt_m takes on its raw linearly- interpolated value between the last on-ground slot and the next airborne slot. Feed samples during takeoff are often 5-15 s apart, so by the time the renderer reaches the first airborne slot the interpolation has ppos.alt_m already at 100-500 ft above the runway. The aircraft visibly teleports up to that altitude in one frame. Fix: introduce LIFTOFF_BLEND_TIME_S = 1.5 s (Constants.h) and a new liftoffBlendStartTs member on LTAircraft. CalcFlightModel records the sim time on the frame `bOnGrnd` transitions true→false. For the next 1.5 s, CalcPPos's airborne branch lerps ppos.alt_m from terrainAlt_m toward the raw interpolated value using a cubic smoothstep easing (f(t) = t² (3-2t), C¹-continuous at both ends — no kink at start or finish of the blend). After 1.5 s the blend is complete and the aircraft renders at its full interpolated altitude as normal. 1.5 s coincides roughly with the pitch.max() walk driven by FPH_ROTATE, so the visual rotation and the altitude lift play out together. The cubic smoothstep avoids the linear-ramp "track up the slope" look and gives a natural ease-in/ease-out climb-out.
RealTraffic supports 2 s polling in regular operation; the previous 8 s `RT_DRCT_DEFAULT_WAIT` floor was capping the per-aircraft sample gap at ~10-20 s even when the server's RRL response would otherwise allow faster polling. The per-response `rrl` value supplied by RealTraffic is the authoritative rate limit (see ProcessFetchedData) and is honoured directly when it is at or above this floor. The floor exists only to defend against the server returning rrl=0 (or no rrl at all), in which case we fall back to this conservative interval. Lowering it to 2 s matches RT's documented minimum. Downstream effect: per-aircraft position granularity drops from ~10-20 s gaps to ~2-4 s gaps. The Mode-S EHS heading update interval (~10 s) becomes the dominant lag source rather than the polling floor, so the EHS-staleness cross-check added in commit d861869 engages less often (feed and track will agree more often) and the SnapToTaxiways backward-routing symptom investigated in task #24 becomes much less severe (less inter-sample distance for the synthesised path to mis-route across). No new config knob is exposed: the server-supplied RRL still drives the actual cadence, this constant is purely the safety floor.
Tester reported aircraft "taxiing backwards" for parts of the route (observed: DAL973, RPA5716 at YSSY). Root cause: LTAptSnap uses pos.heading() to decide which direction along a taxi edge to route the aircraft (TaxiEdge::startByHeading / endByHeading in LTApt.cpp, called from several sites: 734, 763, 839, 1298, 1348, 1369, 1401). The feed-supplied heading is sourced from Mode S Enhanced Surveillance (EHS), which updates only every ~10 s and lags noticeably during turns. When SnapToTaxiways processes a slot whose feed heading is stale, the synthesised taxi path between this slot and the next gets routed in the wrong direction along the edge — the rendered aircraft visibly moves backward along its taxiway between feed samples. CalcHeading (since commit d861869) already cross-checks the feed heading against the position-derived track and falls through to the track-derived value when the two disagree by 30–150°. But CalcNextPos ran SnapToTaxiways BEFORE the CalcHeading loop, so snap was reading the raw feed heading, bypassing the cross-check. Fix: in SnapToTaxiways, call CalcHeading on each on-ground slot immediately before LTAptSnap. This applies the full on-ground heading filter chain — stationary freeze, feed/track cross-check, pushback detect, hysteresis — so snap sees a heading that reflects the actual direction of travel rather than the stale EHS value. The existing post-snap CalcHeading loop in CalcNextPos stays in place. Its role is to fill in the heading of the intermediate waypoints that SnapToTaxiways itself synthesises (inserted with heading=NaN, then derived from the bearing between consecutive waypoints once the path geometry is known). The recent RT 2 s polling change (6fb0b1f) reduces but does not eliminate this symptom — even at 2 s feed samples, if the EHS lag exceeds 2 s (which can still happen) snap could route wrong. This fix addresses the underlying cause regardless of polling rate.
Replaces the linear chord interpolation between two consecutive ground
position slots with a centripetal Catmull-Rom spline fit through four
control points: P0 = the most-recently-retired `from`, P1 = current
from, P2 = current to, P3 = the slot after `to` if available. The
rendered position comes from the spline at the current leg parameter,
and the rendered heading comes from the spline's tangent at the same
parameter (atan2 of dC/dt).
Why this is the right shape of fix
The previous chord-based interpolation produced a visible "sideways"
look during taxi turns: the rendered position cut straight across each
turn corner while the rendered heading walked toward the chord
direction via a rate-limited MovingParam. Because the chord is the
average of the entry and exit headings, the nose always lagged the
actual taxiway tangent by half the per-leg heading change. With
larger feed gaps (or at higher taxi speeds) the lag became visually
objectionable — tester reports for TAP227, TAP215 and others.
A smooth curve through four positions captures the actual arc the
aircraft flew, and its tangent at any parameter is by construction
the direction of motion at that point. Position and heading then come
from the same curve and are aligned by construction — no separate
rate-limit, no chord-vs-arc mismatch.
Centripetal (Lee 2009) parameterisation is specifically chosen over
the uniform or chordal variants because it is the only Catmull-Rom
form that avoids cusps and self-intersecting loops at sharp corners,
which we routinely encounter on runway turn-offs (45-90° in 30-60 m).
Math implementation
New helper `CatmullRomEvalCentripetal` in CoordCalc.{h,cpp}. Operates
on a local meters frame centred at P1 (using existing Lat2Dist /
Lon2Dist helpers) so the math is Euclidean and avoids cos(lat)
accumulation across control points. Knot intervals use sqrt(chord
distance) per the centripetal variant; barycentric (Lee) evaluation
form for both the position and a finite-difference tangent.
Heavily commented inline.
LTAircraft changes
* New `posPrev` member (positionTy, default NaN) stores the slot
being popped during the position switch in CalcPPos so it remains
available as P0 for the next leg's spline.
* In CalcPPos, the non-Bezier branch now picks between spline and
linear:
- Both endpoints on ground → spline (lat/lon + heading from
spline; alt + pitch stay linear because they're not part of
the horizontal-plane curve and get clamped/forced later by
the bOnGrnd block anyway).
- Any other case → linear, as before. This includes cruise
legs, the touchdown / liftoff boundary, and air segments.
* Existing Bezier path (turn.GetPos) still wins when active — that
machinery is unchanged and continues to handle cut-corner setups
elsewhere in the flight model. On the ground, Bezier rarely
fires now because the per-frame heading rate limit and the new
spline both produce smooth results without it.
What this replaces / interacts with
* Per-frame heading rate limit (a1e0423): still in place, still
runs after the spline assigns ppos.heading(). It's a no-op
against spline output because the spline tangent already varies
continuously with the parameter; the clamp only ever fires for
the linear-fallback case now.
* Half-way retarget to to.heading(): unchanged — still skipped at
high ground speed; allowed at low ground speed where the slot
heading from the feed/track cross-check (d861869) is a useful
secondary target. The spline-set heading dominates in practice.
* SnapToTaxiways waypoint insertion: complementary. Snap inserts
extra control points along the taxi graph; the spline then
naturally curves through them, which is exactly the smooth
taxiway-centreline shape we want.
After the RT polling floor dropped to 2 s (commit 6fb0b1f) the log started showing curl error 55 (CURLE_SEND_ERROR) with the message "Connection died, tried 5 times before giving up", at roughly 27 s intervals. Each occurrence cost five send attempts plus a channel error counter tick. Symptom is the classic stale-HTTP-keep-alive case: the previous request's TCP connection is silently dropped by an intermediate NAT/firewall (or by the server) while we are idle between polls, and libcurl finds out only when its next `send()` on the kept-alive socket fails. At an 8 s poll cadence the idle interval was short enough that the connection rarely went stale; at 2 s it should be even less of a problem in principle, but the symptom suggests that some hop in the path declares the socket idle on a sub-30 s timer that the previous cadence happened to dodge by aligning with the server's own keepalive cadence. Fix: enable TCP keepalive at the OS level via curl options in LTOnlineChannel::InitCurl: * CURLOPT_TCP_KEEPALIVE = 1 enable SO_KEEPALIVE * CURLOPT_TCP_KEEPIDLE = 20 first probe after 20 s of idle * CURLOPT_TCP_KEEPINTVL = 10 subsequent probes every 10 s With the OS sending a TCP heartbeat on each idle socket, NATs and servers see the connection as active and don't drop it. The kept- alive HTTP connection then stays usable for the next 2 s poll, eliminating the CURLE_SEND_ERROR cluster. Applies to all online channels (OpenSky / ADSBHub / RealTraffic / etc.) since the option is set in the base-class InitCurl. The non-RT channels poll less frequently so they were unlikely to hit the symptom, but keepalive is a strict improvement for any persistent HTTPS connection that may sit idle for tens of seconds.
TCP keepalive (cee1c1c) did not stop the CURLE_SEND_ERROR / "Connection died, tried 5 times" pattern recurring at ~30s intervals. Whatever is recycling the kept-alive socket does so actively (upstream load balancer, NAT idle-drop, or curl's own connection-cache age limit), and keepalive probes cannot help against an active RST or FIN. Set CURLOPT_FORBID_REUSE so every request opens a fresh TCP+TLS connection and closes it after the response: no kept-alive socket ever survives long enough to go stale. Cost is one TLS handshake per request (~100-300ms with TLS 1.3 resumption), negligible at the 2s RT polling cadence. The keepalive options are kept as belt-and-braces.
Builds on the centripetal Catmull-Rom ground spline (486b916) with a set of fixes for jitter, speed pulsation and segment-boundary jumps surfaced during testing: - Arc-length reparameterisation: a per-segment 16-sample LUT (CatmullRomArcLut) maps the time-linear parameter onto constant arc-length-per-time, so the rendered position no longer speeds up and slows down within a leg. - Cache P3 (posNext) at segment switch instead of reading posList[2] live each frame. A mid-segment feed update no longer shifts the spline geometry underfoot and snaps the rendered position. - Control-point smoothing: pre-smooth the look-ahead endpoint P2 with a [1,2,1]/4 binomial kernel so the spline approximates rather than interpolates noisy feed samples. P1 is left raw so the curve still returns 'from' exactly at u=0, keeping segment joins seamless with the posList.front()=ppos continuity mechanism. - High-speed fallback: above 40kt (takeoff roll, landing rollout) skip the spline for plain linear interpolation. The aircraft tracks a straight line there; the spline only amplifies glitchy feed data and interacts awkwardly with the acceleration-profile parameter. - Near-stationary fallback: below a 5m chord, skip the spline so a noise-dominated tangent cannot wobble the heading of a parked aircraft. - Tolerate sub-1s backward sim-time jumps in NextCycle instead of tearing down and rebuilding the whole aircraft fleet on every frame stutter or brief pause/unpause.
The RealTraffic parked-aircraft feed was almost entirely missing in the sim. Several compounding bugs, fixed together: - Dedup collapsed empty parking-position names. ProcessParkedAcBuffer dedups by RT_PARK_ParkPosName (Jeppesen stand), keeping the newest-timestamp entry per stand. But that field is frequently empty (GA, cargo, remote stands), so every empty-name aircraft collided into one map slot and all but one were dropped. Empty names now key on the unique hex id instead, so each is kept. The log line also reports the post-dedup count. - SPOS_STARTUP was set only on a successful gate match. Without that flag an aircraft never enters FPH_PARKED, so the Synthetic channel never adopts it to keep it alive and it ages out minutes after creation; and the ground-holding trivial-drop is not exempted for it. The flag (and bHeadFixed) is now set unconditionally — RT's feed is authoritative that the aircraft is parked, gate match or not. - Ground-holding trivial-drop ate the bootstrap seeds. AddNewPos drops stationary near-duplicate positions as jitter once holding is active. The parked feed's four identical seed positions (and the Synthetic keep-alive re-feeds) are exactly that shape, so they were dropped and the aircraft was left with too few positions to render. SPOS_STARTUP positions are now exempt from the drop: they are intentional placements, not feed jitter, and raw live-feed jitter is not SPOS_STARTUP at AddNewPos time (snapping runs later). - Every parked aircraft faced north. The gate match was tested via startupPos.isNormal(), but LTAptFindStartupLoc returns the matched apt.dat location with a NaN timestamp and isNormal() rejects a NaN ts — so the match was discarded every time and the heading override never ran. The match is now tested on the returned distance (NaN only when nothing matched). The startup-location search radius is also widened to the 3x default since RT's coordinates are not metre-precise, and dyn.heading is synced after the gate-match correction. - Gate handoff was indiscriminate. SyntheticConnection removed any stored parked aircraft within 10 m of any on-ground aircraft, so traffic merely taxiing past a gate wrongly evicted parked aircraft. The trigger is now ac.GetFlightPhase() == FPH_PARKED: only an aircraft that has itself come to rest on a stand evicts a stale ghost there. - Parked traffic is now re-fetched periodically. The request was a one-shot fired only on airport-data load, freezing the parked picture for the whole visit. RT_PARKED_REFRESH_INTVL_S (300 s) re-arms it so new arrivals appear and departures are reconciled.
The parked-traffic request was gated on LTAptAvailable() at issue time (SetRequType), but everything downstream was not — ProcessParkedAcBuffer could run ~1-2s later against a half-built gmapApt after the camera moved and a new async apt.dat load started. LTAptSnap and LTAptFindStartupLoc were not gated at any point, so live arrivals could also snap against a reloading layout and pick up wrong startup locations / wrong headings that then stuck (KLM604 "facing ass to the terminal" was an example). Three gates added: - ProcessFetchedData re-checks LTAptAvailable() when the parked response arrives, before calling ProcessParkedAcBuffer. If the layout went not-ready in the request->response window, the response is dropped, bDoParkedTraffic stays armed, and tLastParkedRefresh is left unchanged so the next cycle retries promptly. - LTAptSnap bails immediately if !LTAptAvailable(). AsyncReadApt first PurgeApt's, then re-adds airports one by one, releasing the lock between each, so a snap against gmapApt mid-load lands in the wrong taxiway / startup location and the bad heading sticks. - LTAptFindStartupLoc bails similarly. ProcessParkedAcBuffer is now also gated upstream, but this also protects the Synthetic channel's call site and any future lookup that races a mid-flight reload. Direct answer to the dev's "hope it doesn't backfire" concern after the parked-traffic acceleration work.
…ete) Replaces the state-free per-slot pushback heuristic with a real state machine, restructured to run BEFORE the feed-heading block so it is authoritative once it engages. Adds the renderer bridge needed for the held heading to actually reach the screen, and chooses the heading source per slot based on what the feed is actually reporting. THIS IS NOT YET A WORKING FIX. Pushback detection still misses cases where the feed heading is course-over-ground and the parked->push geometry is borderline (DAL73), false-triggers / fights for cases where the snapping pipeline interleaves SPOS_STARTUP positions into the deque (AMX026), and does not fully reproduce the "facing ass to the terminal" path (KLM604 — separate snapping issue). Work continues; this commit captures the state to keep the Windows-build tester in sync. What changed: - LTFlightData::bPushback state (Include/LTFlightData.h). Plus headingStable / headingStartup members consumed by the PUSHBACK_DIAG diagnostic and likely by the next round of detection redesign. - PUSHBACK_DETECT_GS_MAX_KT raised 3->12 (entry-only ceiling now; the 135deg direction gate is the real discriminator). PUSHBACK_EXIT_FWD_DIFF_DEG added (45deg, exit-when-moving-forward-relative-to-maintained-nose). PUSHBACK_FEED_IS_NOSE_DEG added (90deg, switches the held-heading source between feedHdg (true-nose feeds) and track+180 (course-over- ground feeds)). PUSHBACK_MIDPOINT_DIST_M removed (was dead code). - CalcHeading: pushback state machine moved BEFORE the feed-heading cross-check so it is authoritative when engaged. ENTRY on the geometric signature, HOLD chooses feedHdg-vs-track+180 per slot based on |feedHdg-track|, EXIT on direction reversal (no timer). Sub-threshold-motion slots carry the predecessor heading. Airborne forces bPushback=false defensively. - LTAircraft spline branch: when from.f.bHeadFixed is set, the rendered heading is interpolated from the slot headings instead of overridden by the spline tangent. Without this, the held pushback heading was discarded at render time because the tangent points along the (backward) motion. - TEMPORARY: PUSHBACK_DIAG logging in CalcHeading dumps feedHdg, track, the captured stable / startup reference headings and the angular gaps to each. To be removed once the detection is sorted.
RealTraffic v6 added a hex-keyed ICAO operator/airline flag code to both the UDP RTTFC broadcast (field 43, RT App v11.1.452+) and the HTTP Direct API JSON array (field 48). It is sourced from RT's OperatorFlagCode column of the OpenSky-derived BaseStation aircraft database, keyed on the transponder hex ID rather than the operating callsign — so it remains correct under wet-lease and codeshare operations where the callsign-derived airline would be wrong. LiveTraffic now populates FDStaticData::opIcao from this field in both processing paths, bounds-checked so older RT App builds / sparser responses still parse. When present (≈55% of records in test, doc says ~70% — the rest are GA/private airframes RT does not have in its DB), it wins over the callsign-substring guess in airlineCode() and is used authoritatively for XPMP2 livery matching. When absent it falls through to the existing fallback path with no behaviour change. UDP enum (RT_RTTFC_FIELDS_TY): index 41 renamed from AUGMENTATION_STATUS to BARO_ALT_UNCORRECTED (v6 changed the slot's semantics; the old name was not referenced anywhere). Added AUTHENTICATION (=42, shares slot with the preserved MIN_TFC_FIELDS strict-parse gate) and OPERATOR (43). The min-fields gate stays at 42 so older RT App builds that do not send fields 42-43 still parse. HTTP Direct enum (RT_DRCT_FIELDS_TY): inserted RT_DRCT_Operator at index 48 ahead of the NUM_FIELDS sentinel. The existing strict gate at ProcessFetchedData picks up the new sentinel value cleanly because v6 Direct API responses always carry 49 fields per the doc. Verified in-session in both RT App (UDP) and Direct API (HTTP) modes: 840 RealTraffic adds, 461 with a populated operator code, 379 empty and falling through to the callsign-substring fallback as designed.
Two related guards to stop a previously evicted parked ghost from coming back, plus a debug channel for diagnosing position-feed issues: - SyntheticConnection: persist hex ids that gate-handoff has evicted in a session-scoped set (evictedHexIds). The Synthetic re-adoption path now skips marked ids, blocking the race where a FetchAllData pass running during the async SetInvalid teardown recreated the ghost ~40s after eviction. - ProcessParkedAcBuffer: refuses to re-seed a marked id, and as defence-in-depth refuses to re-seed any hex that is already live-tracked and has either left the ground or moved farther than GATE_REFEED_MAX_DIST_M (50 m) from the gate position RT is reporting. Blocks the TFL3NA-class symptom where the 5-minute parked re-fetch silently appended an SPOS_STARTUP gate-position seed to an airborne aircraft deque, causing the render clock to later walk into it and teleport the aircraft back to the gate. Adds GATE_REFEED_MAX_DIST_M constant in Constants.h with verbose rationale on why 50 m is the right footprint for still-at-the-stand. Also lands FEED_DIAG instrumentation across both RT paths (ProcessTrafficBuffer / HTTP-Direct and ProcessRTTFC / UDP RTTFC): per-aircraft monotonicity log marking NEW / OK / REPEAT / BACKWARDS for every accepted feed timestamp, plus the source msg_type and seen age. Drives the recent debug analyses (TFL3NA, KLM911, KLM99F, KLM48/KLM874 gate handoff) and remains intentionally tagged TEMPORARY in the source so it is easy to find and remove once the feed-jitter work settles.
Several intertwined changes that together fix the visible takeoff glitches users reported (discrete 100ft+ altitude jumps mid-climb, aircraft slamming back to runway after partial liftoff, premature rotation animation without altitude change). Smoothing the rendered climb-out altitude: - LookupAltAtTs in LTAircraft now fits altitude via a Gaussian- weighted local linear regression (sigma 5s) instead of returning the raw per-leg linear interpolation. ADS-B reports altitude in 25ft quantization steps and slots arrive at irregular 1-5s cadence, so the per-leg slope swings wildly even during a steady climb; an interpolating spline (PCHIP etc.) faithfully reproduces that as visible kinks every few seconds. The regression renders along the fitted trend instead. - Numerically stable implementation: works in (t - targetTs) so the variance and covariance sums stay in the +-30s range. The textbook Sum(w*t*t) - sumW*tMean*tMean form would subtract two values of order 3e19 (timestamps are around 1.78e9) to get a result of order 1e2, losing the signal to floating-point cancellation. - pastAltSamples_ archive: fd.posDeque only keeps one past slot at any moment (CalcNextPos pops aggressively), and a regression with near-unit weight on that one sample shifts discretely when it pops. The archive mirrors every slot we ever observe and does NOT pop, so the Gaussian weight on each sample fades out continuously over many frames as targetTs moves past it. No more step changes at the deque-pop boundary. Liftoff-blend ground lock + floor clamp in CalcFlightModel / CalcPPos: - During the 10-second liftoff blend, bOnGrnd is locked to false even if LookupAltAtTs momentarily dips below MDL_CLOSE_TO_GND above terrain. Without this guard, transient regression dips (smoothing-window rebalancing as past-ground samples weight-out against future-airborne samples) would re-flip bOnGrnd to true, clamp ppos.alt to terrain, regress the phase from FPH_LIFT_OFF to FPH_TO_ROLL, and the next frame's recovery would reset liftoffBlendStartTs and restart the blend from scratch. Visibly: aircraft climbs to ~Nft, slams to the runway, continues T/O roll, rotates again, climbs again. - The blend formula clamps (LookupAltAtTs - liftoffStartAlt) to be non-negative so the rendered altitude can never go below terrain during the blend even if the smoothed input dips below the frozen start. - ALT_DIAG log line on every bOnGrnd transition with the relevant state (PHeight, blend status, sinceLO, phase) so future debugging can verify there is one clean transition per real liftoff. RealTraffic ingest: - posTime = TimeStamp - PosAge on both HTTP-Direct and UDP RTTFC paths. The RT TimeStamp is when the record was generated server- side and PosAge is the ingest age of the underlying measurement. Different sources (ADS-B vs MLAT vs satellite multilateration) have very different ingest delays, so two consecutive records can have the same TimeStamp but represent measurements taken seconds apart. Normalising to actual measurement time aligns the deque with what-the-aircraft-did-when rather than when-RT-ingested-it. - FEED_DIAG now also includes alt, gnd flag, and reported vsi so debug analyses can see what the feed is actually saying. Takeoff/landing animation polish (carried over from earlier in the session, not yet shipped): - LIFTOFF_BLEND_TIME_S bumped from 1.5s to 10s for a gradual climb-away from the runway. With the spline / regression smoothing on top, the blend curve is C2-continuous at both endpoints and renders as a single smooth arc. - TOUCHDOWN_HOLD_PITCH_S = 5s: after FPH_TOUCH_DOWN the pitch.moveTo to GND_PITCH_DEG is deferred for 5s, modelling the aerobrake during which real airliners keep the nose up after the mains touch. Previously the nose was on the ground within ~3s of touchdown.
…e exception
Two changes addressing reported visual pitch glitches on take-off.
ROTATE_PITCH_MAX_DEG = 10 deg
- FPH_ROTATE entry used to call pitch.max(), which walks the pitch
MovingParam toward pMdl->PITCH_MAX (15 deg by default). 15 deg is
past the tail-strike geometry of most narrow-bodies (B738 ~ 11 deg,
A320 ~ 13.5 deg), and users were seeing rendered aircraft drag
their tails during rotation.
- New constant ROTATE_PITCH_MAX_DEG = 10 caps the rotation target.
pitch.moveTo(ROTATE_PITCH_MAX_DEG) replaces pitch.max() in the
ENTERED(FPH_ROTATE) block.
- After FPH_LIFT_OFF, the in-air pitch logic in
LTFlightData::CalcNextPos (around line 1700) takes over and walks
pitch toward a VSI-derived target, still clamped to
pMdl->PITCH_MAX. So steep climbs still reach the full 15 deg, just
not while the gear is on the runway.
FPH_LIFT_OFF added to the ground-attitude pitch override exception
- CalcAcPos used to force pitch back to GND_PITCH_DEG whenever
bOnGrnd was true and phase was not in
{FPH_ROTATE, FPH_FLARE, FPH_TOUCH_DOWN, FPH_ROLL_OUT}. Phase can
advance ROTATE -> LIFT_OFF via the V_Climbing branch in
CalcFlightModel as soon as VSI crosses VSI_STABLE (100 fpm) -
which happens BEFORE bOnGrnd actually flips false on takeoffs
with smoothed altitude (the regression has not yet crossed
MDL_CLOSE_TO_GND above terrain).
- With FPH_LIFT_OFF missing from the exception list, the override
forcibly reset ppos.pitch() to 2 deg for the frames between
phase-becomes-LIFT_OFF and bOnGrnd-actually-flips-false.
Visible as: nose pitches up, briefly flips level on the runway,
then pitches up again once airborne. Reported on AAL2449.
- Adding FPH_LIFT_OFF to the exception list keeps the MovingParam
in control of pitch during the entire rotate-to-airborne window.
Reverts the `posTime -= jag_n(pJAc, RT_DRCT_PosAge)` adjustment added in commit d36ba8c on both ingest paths (HTTP-Direct ProcessTrafficBuffer and UDP RTTFC ProcessRTTFC). The original adjustment was based on an incorrect interpretation of the two time-related fields in the RT data: it assumed RT_DRCT_TimeStamp (idx 10) was a server-side record-creation timestamp and RT_DRCT_PosAge (idx 39) was the ingest-pipeline latency back to the actual measurement time, so subtracting one from the other would normalise positions to "what the aircraft did when". That is not what these fields mean in RT v6: idx 10 is already the measurement time and is the only field that should be used for chronological comparisons. The subtraction was actively moving every position backwards by an amount that does not represent any real lag, breaking deque ordering — slots from sources with higher PosAge values were being placed too far in the past, manifesting as cascades of BACKWARDS rejections and deque-position artefacts in the FEED_DIAG log. posTime on both paths is now back to the raw RT_DRCT_TimeStamp / RT_RTTFC_TIMESTAMP value, exactly as it was before d36ba8c. FEED_DIAG still prints PosAge as `seen=` for informational purposes but the `ts=` field and the OK / BACKWARDS / REPEAT flags now reflect raw-timestamp deltas, which is the correct chronology to use.
Builds the full pipeline for correctly identifying parked aircraft at gates and rendering their pushback maneuver, replacing the previous heading-flip- based heuristic with a state machine driven by gate detection, distance- gated motion acceptance, and locked nose-source choice. Constants.h - GATE_DETECT_MAX_DIST_M (30 m): max distance from apt.dat startup-loc for a held position to count as "at a gate". Tight on purpose — keeps runway hold-shorts and taxi pauses from being misclassified. - GATE_HOLD_MIN_ACCEPT_M (30 m): minimum displacement before the first motion slot is accepted on a gate-parked aircraft. Distance-based, NOT counter-based, because real pushbacks roll at 0.4-1.4 kt — below the stationary threshold — and a non-stationary-slot counter never advances for a real slow push. - PB_MOTION_GS_KT (0.3): pushback state-machine bMotion threshold, distinct from the global GND_STATIONARY_GS_KT (1.5). The global threshold is too high for real slow pushbacks; the state machine needs its own. - PB_FEED_NOSE_AGREE_DEG (30): max delta between parked heading and first- motion feed heading for the feed to be treated as true-nose source. LTFlightData (CalcHeading PB block) - bMotion loosened: drop the per-slot dist>=SIMILAR_POS_DIST requirement, use PB_MOTION_GS_KT. Continuous slow motion stays in PB_ACTIVE instead of bouncing into PB_PAUSED on every sub-7m slot. - Nose source locked at entry: FEED if first-motion feedHdg is within PB_FEED_NOSE_AGREE_DEG of the prior parked heading (true-nose case), otherwise TRACK+180. Held for the entire push — no per-slot flipping. - Statistical 4-slot track filter (equal-weight centroid, reject 2 largest outliers, vector between 2 inliers). Replaces the noisy 2-point chord during slow ground motion. - Predecessor fallback: when posDeque is drained during a long stationary period and the GATE_RELEASE slot arrives alone, fall back to pAc->GetToPos() as virtual predecessor so the state machine can engage on the first accepted slot. - bHeadFixed pinning: stamp the predecessor slot (guarded against posDeque.cbegin) so the renderer interpolates across the entry leg. LTFlightData (AddNewPos) - Third gate-detection path: on bGroundHolding flip, one-shot LTAptFindStartupLoc lookup; if within GATE_DETECT_MAX_DIST_M, set bGateParked. Distance signal complements the SPOS_STARTUP path and catches live-tracked aircraft that were never in RT's parked snapshot. The lookup uses outDist (not isNormal()) as the success signal — startup-loc positionTy has ts=NaN/alt=NaN and isNormal() rejects it. - Distance-based gate-hold motion suppression: drop any slot with displacement < GATE_HOLD_MIN_ACCEPT_M from the latest accepted position while bGateParked && pbState==PB_NONE. On acceptance, also clear bGroundHolding so subsequent in-push slots flow through. - youngestTS advancement on drops: keep the freshness timestamp moving even when slots are filtered out. Without this, an aircraft sitting at a gate receiving valid feed updates (all trivial-dropped) outdates after GetAcOutdatedIntvl() and is removed despite the feed being alive. LTAircraft (ground spline branch) - Honour to.f.bHeadFixed as well as from.f.bHeadFixed when deciding whether to interpolate heading or use the spline tangent. The previous from-only check failed at the PB_NONE→PB_ACTIVE transition: from (posList[0]) was a live-feed parked slot with bHeadFixed=false, so the renderer used the motion-tangent (≈ direction of travel) and visually pivoted the aircraft to face the push direction at the gate. Honouring to.f.bHeadFixed covers the transition from the destination side, since pinning bHeadFixed retroactively on the predecessor isn't reachable when posDeque has been drained. LTMain - Cosmetic (whitespace) from earlier revert of rewind-detection feature. Diagnostic logging - GND_DIAG_GATE (HIT / NEAR / NOAPT / APT_UNAVAIL) at HOLDIN moment. - GND_DIAG_GATE_HOLD on suppressed motion at gate (with dist/threshold). - GND_DIAG_GATE_RELEASE on acceptance. - PUSHBACK_DIAG ENTRY with chosen nose source and entry values. - Per-slot PUSHBACK_DIAG (state, gs, track, heldNose, assigned).
PB safety-valve emergency exit
- New constant PB_MAX_GS_KT = 10.0 in Constants.h.
- In CalcHeading, when pbState ∈ {ACTIVE, PAUSED} and the slot gs
exceeds PB_MAX_GS_KT, force exit to PB_NONE: clear pbHeldNose,
pbUseFeedNose, bGateParked and log PUSHBACK_DIAG ... FORCE_EXIT.
- Catches the failure mode where the held-nose source was chosen
incorrectly at entry (TRACK+180 picked because feedHdg disagreed
with parkedHdg, but the feed was actually right because the
aircraft had already rotated during GATE_HOLD suppression). With a
wrong reference, the directional PB_PAUSED→PB_NONE exit test never
fires; the aircraft taxis out at 20+ kt while the renderer still
shows the held nose, producing the visible tail-first / ass-
forward symptom. 10 kt is a hard upper bound on physical pushback
speed; anything above is definitively taxi.
`FF****` placeholder-hex duplicate prune
- New static LTFlightData::PrunePlaceholderHexDuplicates() (declared
in LTFlightData.h, implemented in LTFlightData.cpp).
- Two-pass scan of mapFd: pass 1 collects callsigns from non-FF
entries; pass 2 invalidates any FF entry whose callsign matches.
Two passes are necessary because the real-hex entry may appear
after its FF counterpart in iteration order. SetInvalid(true)
drops the rendered aircraft so the visual duplicate disappears
immediately and the standard cleanup pipeline erases the entry.
Uses GetUnsafeStat() for the callsign reads — racy but acceptable
given the periodic nature of the scan (stale reads re-evaluated
next pass).
- Hooked from LTRegularUpdates with a 10 s throttle; the duplicate
condition develops over many seconds and the per-flight-loop
cadence would waste mapFd-mutex time.
- Logs PRUNE_FF for visibility on each invalidation.
GitHub Actions exposes repository secrets via env-var assignments
unconditionally (`.github/workflows/build.yml:14-17`). When a fork PR
doesn't have the FSC secrets configured, GA passes the env vars as the
empty string — NOT as undefined. The previous `if(DEFINED ENV{...})`
checks treat empty-string-defined as "set", so 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`; the Linux CI build fails with "expected
primary-expression before ','" at line 48.
Every fork PR hits this — trivially reproducible with no FSC secrets
in the fork settings, succeeds in the upstream's own CI because the
secrets ARE configured there, succeeds locally because the env vars
aren't set at all (DEFINED is false → default fallback fires).
Fix: add `AND NOT "$ENV{...}" STREQUAL ""` to each of the four FSC
DEFINED-checks. Empty env vars now fall through to the defaults in
the same code path as the not-defined case. Verified locally that
both `unset` and `=""` simulate-as-CI cases produce the expected
"FSCharter Production = Client ID: 3, Client Secret: INOP" output.
Ground rendering / pushback / livery / weather / channels — 36-commit fix set
and reduced PITCH_FLARE for many models. Makes rotate pitch depending on aircraft size.
to make the duration of pitch hold after touch-down depending on aircraft size.
and tied all DIAG logging to it
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See original #312