Citation: combined PFD+MFD view, hardware AP look, FMS build-out, fluid easing

- CitDuo: PFD + MFD side-by-side on one tablet screen (new 'PFD+MFD' tab,
  first in the Citation profile) — the two pilot DU-870 tubes at once.
- Autopilot restyled to the real Primus FGC: machined dark bezel w/ corner
  screws, engraved square keys with green annunciator triangles (lit when
  active), ridged pitch thumbwheel.
- FMS more complete per the FMS manual: DEP/ARR now does the two-step
  procedure→transition pick (NO TRANS / RWxx / named transitions), VNAV split
  into CLB/CRZ/DES pages (trans-alt, speed/alt limits, cruise alt, target
  speed, VPA) via PREV/NEXT, and a new PROG page (TO/DEST distance-to-go + ETE
  at GS). Page keys: FPLN/LEGS/DEP-ARR/DIR-INTC/VNAV/PROG/MENU.
- Fluidity: Citation PFD/MFD/EICAS now use the same rAF time-constant easing as
  the G1000 (useEased/useEasedAngle) for attitude, speed/alt/VS tapes, HSI,
  compass, map ownship and N1/ITT gauges — smooth 60 fps instead of stepping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 12:33:04 +02:00
parent b05ffedbc1
commit 6756acab4a
7 changed files with 211 additions and 65 deletions
+23 -8
View File
@@ -1,5 +1,6 @@
import React, { useRef } from 'react';
import { num } from '../../api/useXplane.js';
import { useEased, useEasedAngle } from '../../api/ease.js';
// ============================================================================
// Cessna Citation X — Primary Flight Display (Honeywell Primus 2000).
@@ -264,16 +265,30 @@ export default function CitPFD({ xp }) {
const [min, setMin] = React.useState({ on: false, ft: 200 });
const trend = useRef({ ias: 0, t: 0 });
const ias = num(V.airspeed), alt = num(V.altitude), vs = num(V.vspeed);
const pitch = num(V.pitch), roll = num(V.roll), slip = num(V.slip);
const hdg = num(V.heading), trk = num(V.track), crs = num(V.obsCrs);
const hdgBug = num(V.apHdgBug), cdi = num(V.hsiDef), toFrom = num(V.hsiToFrom);
const baro = num(V.baro, 29.92), mach = num(V.mach);
// Smooth the moving symbology toward the live datarefs (frame-rate-independent
// easing) — the same rAF glide the G1000 uses, so a 10-20 Hz stream renders as
// fluid 60 fps motion instead of stepping.
const ias = useEased(num(V.airspeed), 0.10);
const alt = useEased(num(V.altitude), 0.12);
const vs = useEased(num(V.vspeed), 0.18);
const pitch = useEased(num(V.pitch), 0.07);
const roll = useEased(num(V.roll), 0.07);
const slip = useEased(num(V.slip), 0.12);
const hdg = useEasedAngle(num(V.heading), 0.08);
const crs = useEasedAngle(num(V.obsCrs), 0.10);
const hdgBug = useEasedAngle(num(V.apHdgBug), 0.10);
const cdi = useEased(num(V.hsiDef), 0.12);
const mach = useEased(num(V.mach), 0.2);
const aoa = useEased(num(V.aoa), 0.12);
const brg1e = useEasedAngle(num(V.nav1Brg), 0.12);
const brg2e = useEasedAngle(num(V.nav2Brg), 0.12);
const trk = num(V.track), toFrom = num(V.hsiToFrom);
const baro = num(V.baro, 29.92);
const radAlt = num(V.radioAlt, 99999);
const fdOn = num(V.apMode) >= 1 || num(V.apEngaged) > 0;
// bearing pointers only when a station is received (finite, nonzero)
const brg1 = (num(V.nav1Dme) > 0 || num(V.nav1Brg) > 0) ? num(V.nav1Brg) : null;
const brg2 = (num(V.nav2Dme) > 0 || num(V.nav2Brg) > 0) ? num(V.nav2Brg) : null;
const brg1 = (num(V.nav1Dme) > 0 || num(V.nav1Brg) > 0) ? brg1e : null;
const brg2 = (num(V.nav2Dme) > 0 || num(V.nav2Brg) > 0) ? brg2e : null;
const srcLabel = num(V.cdiSrc) === 2 ? 'FMS1' : num(V.cdiSrc) === 1 ? 'VOR2' : 'VOR1';
const dme = num(V.cdiSrc) === 1 ? num(V.nav2Dme) : num(V.nav1Dme);
@@ -287,7 +302,7 @@ export default function CitPFD({ xp }) {
<g transform="translate(96 90)"><SpeedTape ias={ias} mach={mach} bug={num(V.apSpdBug)} alt={alt} /></g>
<text x={120} y={78} fontSize="14" fill="#9aa6ad" textAnchor="middle">KIAS</text>
{/* AOA index (#manual p22) */}
<g transform="translate(48 600)"><AoaIndex alpha={num(V.aoa)} /></g>
<g transform="translate(48 600)"><AoaIndex alpha={aoa} /></g>
{/* altitude tape (#20,#21) + baro (#12,#17) */}
<g transform="translate(584 90)"><AltTape alt={alt} bug={num(V.apAltBug)} vs={vs} baro={baro} std={std} baroHpa={false} minOn={min.on} minFt={min.ft} raBaro={raBaro} /></g>
{/* VSI (#13,#14) */}