diff --git a/web/src/App.jsx b/web/src/App.jsx
index a5d2ced..3512569 100644
--- a/web/src/App.jsx
+++ b/web/src/App.jsx
@@ -74,6 +74,9 @@ export default function App() {
const xp = useXplane();
// Active cockpit profile — persisted; switches the whole avionics suite.
const [profile, setProfile] = useState(() => localStorage.getItem('cockpitProfile') || 'g1000');
+ // Citation Nav Source Selector bearing-pointer sources (p24): pointer 1 = cyan
+ // circle, pointer 2 = white diamond. Each OFF/VORn/ADFn/FMSn. Shared PFD↔RMU.
+ const [navSrc, setNavSrc] = useState({ brg1: 'VOR1', brg2: 'VOR2' });
const [profMenu, setProfMenu] = useState(false);
const PROF = PROFILES[profile] || PROFILES.g1000;
const TABS = PROF.tabs;
@@ -236,12 +239,12 @@ export default function App() {
)}
{/* ---- Cessna Citation X suite (Honeywell Primus 2000) ---- */}
- {profile === 'citation' && tab === 'duo' && }
- {profile === 'citation' && tab === 'pfd' && }
+ {profile === 'citation' && tab === 'duo' && }
+ {profile === 'citation' && tab === 'pfd' && }
{profile === 'citation' && tab === 'mfd' && }
{profile === 'citation' && tab === 'eicas' && }
{profile === 'citation' && tab === 'ap' && }
- {profile === 'citation' && tab === 'rmu' && }
+ {profile === 'citation' && tab === 'rmu' && }
{/* ---- shared tabs ---- */}
{tab === 'map' && }
diff --git a/web/src/components/citation/CitDuo.jsx b/web/src/components/citation/CitDuo.jsx
index 88cadb0..a98759d 100644
--- a/web/src/components/citation/CitDuo.jsx
+++ b/web/src/components/citation/CitDuo.jsx
@@ -5,10 +5,10 @@ import CitMFD from './CitMFD.jsx';
// Side-by-side PFD + MFD — the two pilot tubes of the Citation X panel on one
// tablet screen (landscape). Each keeps its own bezel/soft-keys; they scale to
// fill half the width like the real instrument panel (DU-870 displays).
-export default function CitDuo({ xp }) {
+export default function CitDuo({ xp, navSrc }) {
return (
);
diff --git a/web/src/components/citation/CitPFD.jsx b/web/src/components/citation/CitPFD.jsx
index 3077adb..cd85b9c 100644
--- a/web/src/components/citation/CitPFD.jsx
+++ b/web/src/components/citation/CitPFD.jsx
@@ -265,8 +265,9 @@ function HSI({ hdg, trk, crs, hdgBug, cdi, toFrom, brg1, brg2, srcLabel, srcColo
);
}
-export default function CitPFD({ xp }) {
+export default function CitPFD({ xp, navSrc }) {
const V = xp.values || {};
+ const bsrc = navSrc || { brg1: 'VOR1', brg2: 'VOR2' };
const [std, setStd] = React.useState(false);
const [raBaro, setRaBaro] = React.useState(false); // #9 RA/BARO minimums source
const [min, setMin] = React.useState({ on: false, ft: 200 });
@@ -287,15 +288,37 @@ export default function CitPFD({ xp }) {
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 hdgRaw = num(V.heading);
+ const vor1e = useEasedAngle(num(V.nav1Brg), 0.12);
+ const vor2e = useEasedAngle(num(V.nav2Brg), 0.12);
+ const adf1e = useEasedAngle(mod360(hdgRaw + num(V.adf1Brg)), 0.12); // ADF relative → mag bearing
+ const adf2e = useEasedAngle(mod360(hdgRaw + num(V.adf2Brg)), 0.12);
+ const gpsBrgE = useEasedAngle(num(V.gpsBearing), 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) ? brg1e : null;
- const brg2 = (num(V.nav2Dme) > 0 || num(V.nav2Brg) > 0) ? brg2e : null;
+ // bearing pointer source (Nav Source Selector): resolve each pointer to a
+ // magnetic bearing or null (no station / OFF). Pointer 1 = cyan ◯, 2 = white ◇.
+ const pickBrg = (sel) => {
+ if (sel === 'VOR1') return (num(V.nav1Dme) > 0 || num(V.nav1Brg) > 0) ? vor1e : null;
+ if (sel === 'VOR2') return (num(V.nav2Dme) > 0 || num(V.nav2Brg) > 0) ? vor2e : null;
+ if (sel === 'ADF1') return num(V.adf1Brg) ? adf1e : null;
+ if (sel === 'ADF2') return num(V.adf2Brg) ? adf2e : null;
+ if (sel === 'FMS1' || sel === 'FMS') return num(V.gpsBearing) ? gpsBrgE : null;
+ return null;
+ };
+ const brg1 = pickBrg(bsrc.brg1);
+ const brg2 = pickBrg(bsrc.brg2);
+ // FMA / AFCS mode annunciation (active = green, armed = white)
+ const st = (k) => num(V[k]);
+ const latM = st('aprStatus') ? ['LOC', st('aprStatus')] : (st('navStatus') || st('gpssStatus')) ? ['NAV', Math.max(st('navStatus'), st('gpssStatus'))]
+ : st('bcStatus') ? ['BC', st('bcStatus')] : st('hdgStatus') ? ['HDG', st('hdgStatus')] : ['ROLL', 2];
+ const vertM = st('gsStatus') ? ['GS', st('gsStatus')] : st('vnavStatus') ? ['VNAV', st('vnavStatus')]
+ : st('flcStatus') ? ['FLC', st('flcStatus')] : st('vsStatus') ? ['VS', st('vsStatus')]
+ : st('altStatus') ? ['ALT', st('altStatus')] : ['PITCH', 2];
+ const apTxt = num(V.apEngaged) > 0 || num(V.apMode) >= 2 ? 'AP' : fdOn ? 'FD' : '';
+ const fmaColor = (s) => (s >= 2 ? '#16e000' : '#fff');
// Nav source (Nav Source Selector, manual p24): 0 VOR1 · 1 VOR2 · 2 FMS.
// FMS course is magenta, VOR course is green (Honeywell convention).
const cdiSrc = num(V.cdiSrc);
@@ -315,6 +338,14 @@ export default function CitPFD({ xp }) {