Citation: ADF/VOR/FMS bearing pointers wired through + PFD FMA mode bar

- Nav Source Selector (p24) now fully drives the PFD: shared brg1/brg2 state
  (App ↔ RMU ↔ PFD). RMU has both pointer knobs — ◯ blue (OFF/VOR1/ADF1/FMS1)
  and ◇ white (OFF/VOR2/ADF2/FMS2). The PFD resolves each to a magnetic bearing
  (VOR bearing, ADF relative+heading, GPS bearing) and the HSI legend reflects
  the selected sources.
- PFD FMA / AFCS mode annunciation bar across the top (lateral · AP/FD ·
  vertical) reading the per-mode *_status datarefs — active green, armed white,
  matching the real Primus PFD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 20:10:13 +02:00
parent e8dfa84266
commit 0ceb1dede3
4 changed files with 61 additions and 20 deletions
+42 -11
View File
@@ -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 }) {
<div className="cit-screen">
<svg className="cit-pfd" viewBox="0 0 800 940" preserveAspectRatio="xMidYMid meet">
<rect x={0} y={0} width={800} height={940} fill="#05080b" />
{/* FMA / AFCS mode annunciation bar (active green · armed white) */}
<g transform="translate(204 8)">
<rect x={0} y={0} width={392} height={26} fill="#0a0e12" stroke="#2a3138" />
<line x1={130} y1={2} x2={130} y2={24} stroke="#2a3138" /><line x1={262} y1={2} x2={262} y2={24} stroke="#2a3138" />
<text x={65} y={18} fontSize="14" fill={fmaColor(latM[1])} textAnchor="middle" fontWeight="700">{latM[0]}</text>
<text x={196} y={18} fontSize="14" fill="#16e000" textAnchor="middle" fontWeight="700">{apTxt}</text>
<text x={328} y={18} fontSize="14" fill={fmaColor(vertM[1])} textAnchor="middle" fontWeight="700">{vertM[0]}</text>
</g>
{/* attitude */}
<g transform="translate(400 270)"><Attitude pitch={pitch} roll={roll} slip={slip} fdP={num(V.fdPitch)} fdR={num(V.fdRoll)} fdOn={fdOn} /></g>
{/* speed tape (#2,#3) */}
@@ -336,12 +367,12 @@ export default function CitPFD({ xp }) {
<text x={20} y={642} fill="#d24bd2">HDG</text>
<text x={20} y={662} fill="#d24bd2" fontSize="20">{String(Math.round(mod360(hdgBug))).padStart(3, '0')}</text>
</g>
{/* secondary NAV legend (#6) */}
{/* secondary NAV legend (#6) — reflects the Nav Source Selector */}
<g fontSize="13">
<circle cx={28} cy={726} r="6" fill="none" stroke="#19c3e0" strokeWidth="2" />
<text x={42} y={731} fill="#19c3e0">VOR1</text>
<rect x={22} y={744} width={12} height={12} fill="none" stroke="#cfd6dc" strokeWidth="2" transform="rotate(45 28 750)" />
<text x={42} y={755} fill="#cfd6dc">VOR2</text>
{bsrc.brg1 !== 'OFF' && <><circle cx={28} cy={726} r="6" fill="none" stroke="#19c3e0" strokeWidth="2" />
<text x={42} y={731} fill="#19c3e0">{bsrc.brg1}</text></>}
{bsrc.brg2 !== 'OFF' && <><rect x={22} y={744} width={12} height={12} fill="none" stroke="#cfd6dc" strokeWidth="2" transform="rotate(45 28 750)" />
<text x={42} y={755} fill="#cfd6dc">{bsrc.brg2}</text></>}
</g>
{/* DME (#16) */}
{dme > 0 && <text x={760} y={620} fontSize="16" fill="#13e000" textAnchor="end">{dme.toFixed(1)}NM</text>}