G1000: deepen VNAV/PFD — V-DEV & VS-TGT chevrons, GPS phase, designated toggle
- PFD VNAV: magenta flight-plan target altitude on the alt scale (S.110), V DEV deviation scale + chevron (left, shown in VNAV when not on an ILS), VS TGT chevron on the VSI (S.113) - GPS phase annunciation is now dynamic: APR (approach leg) / TERM (<30 nm to destination) / ENR, instead of a fixed label - flight-plan ALT can be toggled designated(blue) <-> reference(white) by clicking the cell (S.106); only designated altitudes drive the VNAV profile - setPlan now preserves the dsgn/appr waypoint flags across the shared plan - AFCS vertical mode labelled VPTH (manual) instead of VNV Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -279,7 +279,7 @@ function startDemo() {
|
||||
apMode: 2, hdgStatus: 2, gpssStatus: 1, altStatus: 2,
|
||||
lat: 47.45, lon: -122.31, track: 90, groundspeed: 64, gpsDistNm: 18.4, gpsBearing: 92,
|
||||
// radios (XP freq units: nav/com in 10 kHz, e.g. 11030 = 110.30)
|
||||
nav1: 11030, nav1Sb: 11150, nav2: 11380, nav2Sb: 10890,
|
||||
nav1: 11380, nav1Sb: 11150, nav2: 11030, nav2Sb: 10890,
|
||||
com1: 12190, com1Sb: 13000, com2: 12475, com2Sb: 12180,
|
||||
// HSI / data fields
|
||||
obsCrs: 175, hsiDef: -0.6, hsiToFrom: 1, navBearing: 168, gsDef: 0.7,
|
||||
|
||||
@@ -18,7 +18,7 @@ export function setPlan(next) {
|
||||
const wps = Array.isArray(next?.waypoints)
|
||||
? next.waypoints
|
||||
.filter((w) => isFinite(w.lat) && isFinite(w.lon))
|
||||
.map((w) => ({ id: String(w.id || 'WPT'), lat: +w.lat, lon: +w.lon, type: w.type || 'WPT', alt: w.alt ?? null }))
|
||||
.map((w) => ({ id: String(w.id || 'WPT'), lat: +w.lat, lon: +w.lon, type: w.type || 'WPT', alt: w.alt ?? null, ...(w.dsgn != null ? { dsgn: !!w.dsgn } : {}), ...(w.appr ? { appr: true } : {}) }))
|
||||
: [];
|
||||
const wantLeg = Number.isFinite(next?.activeLeg) ? next.activeLeg : 1;
|
||||
plan = { name: next?.name || 'ACTIVE', waypoints: wps, activeLeg: Math.max(1, Math.min(wps.length - 1, wantLeg)) || 1 };
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function FplPage({ xp, full = false, onClose }) {
|
||||
for (let i = Math.max(1, active); i < wps.length; i++) {
|
||||
c += distNm({ lat: pl, lon: po }, wps[i]); pl = wps[i].lat; po = wps[i].lon;
|
||||
const t = num(wps[i].alt);
|
||||
if (t > 0 && t < alt - 50) {
|
||||
if (t > 0 && t < alt - 50 && (wps[i].dsgn ?? true)) {
|
||||
const tan = Math.tan((3 * Math.PI) / 180);
|
||||
const vsTgt = -gs * tan * 101.27;
|
||||
const vsReq = c > 0 ? (t - alt) / (c / gs * 60) : 0;
|
||||
@@ -106,7 +106,13 @@ export default function FplPage({ xp, full = false, onClose }) {
|
||||
<span className="r-dtk">{dtk == null ? '___' : `${String(dtk).padStart(3, '0')}°`}</span>
|
||||
<span className="r-dis">{orig ? '—' : d.toFixed(1)}</span>
|
||||
<span className="r-cum">{orig ? '—' : cum.toFixed(0)}</span>
|
||||
<span className={`r-alt ${w.alt ? 'dsgn' : ''}`}>{w.alt ? `${w.alt}FT` : '_____'}</span>
|
||||
<span className={`r-alt ${w.alt ? ((w.dsgn ?? true) ? 'dsgn' : 'refr') : ''}`}
|
||||
title={w.alt ? 'Klick: Designated ↔ Reference' : ''}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); if (!w.alt) return;
|
||||
const next = wps.map((x, j) => (j === i ? { ...x, dsgn: !(x.dsgn ?? true) } : x));
|
||||
fp.set({ name: 'ACTIVE', waypoints: next, activeLeg: flightPlan.activeLeg ?? 1 });
|
||||
}}>{w.alt ? `${w.alt}FT` : '_____'}</span>
|
||||
<button className="r-del" onClick={(e) => { e.stopPropagation(); fp.remove(i); }}>✕</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
+40
-10
@@ -74,7 +74,7 @@ function vnavInfo(V, fp) {
|
||||
cum += brgDist(prevLat, prevLon, wps[i].lat, wps[i].lon).dist;
|
||||
prevLat = wps[i].lat; prevLon = wps[i].lon;
|
||||
const tgt = num(wps[i].alt);
|
||||
if (tgt > 0 && tgt < alt - 50) {
|
||||
if (tgt > 0 && tgt < alt - 50 && (wps[i].dsgn ?? true)) {
|
||||
const tan = Math.tan((VNAV_FPA * Math.PI) / 180);
|
||||
const tMin = (cum / gs) * 60;
|
||||
const vsReq = tMin > 0 ? (tgt - alt) / tMin : 0; // fpm to make the fix
|
||||
@@ -181,6 +181,16 @@ export default function PFD({ values: V, command, connected = true, svt = true,
|
||||
|
||||
const nav = activeNav(V, flightPlan);
|
||||
const vnav = vnavInfo(V, flightPlan);
|
||||
// GPS phase annunciation: APR when an approach leg is active, TERM within 30 nm
|
||||
// of the destination, otherwise ENR (manual).
|
||||
const gpsPhase = (() => {
|
||||
const wps = flightPlan?.waypoints || [];
|
||||
if (!wps.length) return 'ENR';
|
||||
if (wps.some((w) => w.appr)) return 'APR';
|
||||
const d = wps[wps.length - 1];
|
||||
const dd = brgDist(num(V.lat), num(V.lon), d.lat, d.lon).dist;
|
||||
return dd < 30 ? 'TERM' : 'ENR';
|
||||
})();
|
||||
const [tune, setTune] = useState(null); // radio being tuned (tap a freq)
|
||||
// Eased values so the tapes + heading rose glide between X-Plane's ~20 Hz
|
||||
// samples (VSI a touch softer; attitude is smoothed separately, imperatively).
|
||||
@@ -219,9 +229,9 @@ export default function PFD({ values: V, command, connected = true, svt = true,
|
||||
<AFCS V={V} />
|
||||
<Marker V={V} />
|
||||
<AirspeedTape V={V} ias={iasS} />
|
||||
<AltitudeTape V={V} alt={altS} vs={vsS} baroHpa={baroHpa} minimums={minimums} />
|
||||
<AltitudeTape V={V} alt={altS} vs={vsS} baroHpa={baroHpa} minimums={minimums} vnav={vnav} />
|
||||
<GlideSlope V={V} />
|
||||
<HSI V={V} nav={nav} hdg={hdgS} obs={obs} />
|
||||
<HSI V={V} nav={nav} hdg={hdgS} obs={obs} phase={gpsPhase} />
|
||||
<HdgCrsBoxes V={V} nav={nav} />
|
||||
<Wind V={V} />
|
||||
<DataStrip V={V} />
|
||||
@@ -386,7 +396,7 @@ function AFCS({ V }) {
|
||||
]);
|
||||
const vrt = pick([
|
||||
['GS', st('gsStatus')], ['ALT', st('altStatus')], ['VS', st('vsStatus')],
|
||||
['FLC', st('flcStatus')], ['VNV', st('vnavStatus')],
|
||||
['FLC', st('flcStatus')], ['VPTH', st('vnavStatus')],
|
||||
]);
|
||||
if (!lat.act && fd) lat.act = 'ROL'; // wings-level default
|
||||
if (!vrt.act && fd) vrt.act = 'PIT'; // pitch-hold default
|
||||
@@ -599,7 +609,7 @@ function AirspeedTape({ V, ias: iasProp }) {
|
||||
}
|
||||
|
||||
/* ---------------- altitude tape + VSI + baro ---------------- */
|
||||
function AltitudeTape({ V, alt: altProp, vs: vsProp, baroHpa = false, minimums }) {
|
||||
function AltitudeTape({ V, alt: altProp, vs: vsProp, baroHpa = false, minimums, vnav }) {
|
||||
const alt = altProp != null ? altProp : num(V.altitude);
|
||||
const vs = vsProp != null ? vsProp : num(V.vspeed);
|
||||
const altBug = num(V.apAltBug), baro = num(V.baro, 29.92);
|
||||
@@ -683,17 +693,37 @@ function AltitudeTape({ V, alt: altProp, vs: vsProp, baroHpa = false, minimums }
|
||||
)}
|
||||
</g>
|
||||
)}
|
||||
{/* VSI to the right */}
|
||||
<VSI x={x + W2 + 34} cy={cy} h={h} vs={vs} bug={num(V.apVsBug)} />
|
||||
{/* VNAV: magenta flight-plan target altitude (upper-right of the scale, S.110)
|
||||
+ V DEV chevron on a small deviation scale left of the tape (S.113) */}
|
||||
{vnav && (
|
||||
<g fontFamily="monospace">
|
||||
<text x={x + W2 - 4} y={top + 15} textAnchor="end" fill="#ff20ff" fontSize="14">{vnav.tgtAlt}<tspan fill="#c060c0" fontSize="9">FT</tspan></text>
|
||||
{/* V DEV scale (left) — only in VNAV, not on an ILS (where the GS shows there) */}
|
||||
{!isILS(V.nav1) && (() => {
|
||||
const dy = Math.max(-1, Math.min(1, -vnav.vDev / 300)) * (h / 2 - 26);
|
||||
return (<g>
|
||||
<line x1={x - 16} y1={cy - (h / 2 - 26)} x2={x - 16} y2={cy + (h / 2 - 26)} stroke="#6a6a6a" strokeWidth="1.2" />
|
||||
{[-1, 1].map((k) => <circle key={k} cx={x - 16} cy={cy + k * (h / 2 - 26) / 2} r="2.5" fill="none" stroke="#9aa" strokeWidth="1" />)}
|
||||
<text x={x - 16} y={cy - (h / 2 - 26) - 4} textAnchor="middle" fill="#c060c0" fontSize="9">V</text>
|
||||
<polygon points={`${x - 9},${cy + dy} ${x - 23},${cy + dy - 7} ${x - 23},${cy + dy + 7}`} fill="#ff20ff" />
|
||||
</g>);
|
||||
})()}
|
||||
</g>
|
||||
)}
|
||||
{/* VSI to the right (+ magenta VS TGT chevron in VNAV) */}
|
||||
<VSI x={x + W2 + 34} cy={cy} h={h} vs={vs} bug={num(V.apVsBug)} vsTgt={vnav?.vsTgt} />
|
||||
</g>
|
||||
);
|
||||
}
|
||||
function VSI({ x, cy, h, vs, bug }) {
|
||||
function VSI({ x, cy, h, vs, bug, vsTgt }) {
|
||||
const max = 2000, top = cy - h / 2 + 10, bot = cy + h / 2 - 10;
|
||||
const yOf = (v) => cy - (Math.max(-max, Math.min(max, v)) / max) * (h / 2 - 10);
|
||||
return (
|
||||
<g fontFamily="monospace">
|
||||
<rect x={x} y={top} width="30" height={bot - top} fill="#0e1626a0" />
|
||||
{vsTgt != null && Math.abs(vsTgt) > 20 && (
|
||||
<polygon points={`${x + 30},${yOf(vsTgt)} ${x + 42},${yOf(vsTgt) - 7} ${x + 42},${yOf(vsTgt) + 7}`} fill="#ff20ff" />
|
||||
)}
|
||||
{[2000, 1000, 0, -1000, -2000].map((v) => (
|
||||
<g key={v}>
|
||||
<line x1={x} y1={yOf(v)} x2={x + 8} y2={yOf(v)} stroke="#9aa" strokeWidth="2" />
|
||||
@@ -709,7 +739,7 @@ function VSI({ x, cy, h, vs, bug }) {
|
||||
}
|
||||
|
||||
/* ---------------- HSI compass rose ---------------- */
|
||||
function HSI({ V, nav, hdg: hdgProp, obs = false }) {
|
||||
function HSI({ V, nav, hdg: hdgProp, obs = false, phase = 'ENR' }) {
|
||||
const hdg = hdgProp != null ? hdgProp : ((num(V.heading) % 360) + 360) % 360;
|
||||
const bug = num(V.apHdgBug);
|
||||
// CDI source mirrors the in-sim G1000: 2 = GPS (magenta), 0/1 = VLOC1/2 (green).
|
||||
@@ -762,7 +792,7 @@ function HSI({ V, nav, hdg: hdgProp, obs = false }) {
|
||||
</g>
|
||||
{/* CDI source label (GPS magenta / VLOC green) */}
|
||||
<text x={cx - 56} y={cy - 10} textAnchor="middle" fill={C} fontSize="15">{srcLabel}</text>
|
||||
{isGps && <text x={cx + 56} y={cy - 10} textAnchor="middle" fill={C} fontSize="15">ENR</text>}
|
||||
{isGps && <text x={cx + 56} y={cy - 10} textAnchor="middle" fill={C} fontSize="15">{phase}</text>}
|
||||
{/* course pointer + CDI */}
|
||||
<g transform={`rotate(${crsA} ${cx} ${cy})`}>
|
||||
<line x1={cx} y1={cy - r + 18} x2={cx} y2={cy - 40} stroke={C} strokeWidth="4" />
|
||||
|
||||
@@ -248,7 +248,9 @@ body {
|
||||
.r-wpt b { font-weight: 700; } .r-wpt b.cur { background: #19b8e6; color: #042230; padding: 0 4px; border-radius: 1px; }
|
||||
.r-dtk, .r-dis, .r-cum { color: #e7edf2; text-align: right; }
|
||||
.r-alt { color: #6f808d; text-align: right; }
|
||||
.r-alt { cursor: pointer; }
|
||||
.r-alt.dsgn { color: #4fa8ff; } /* designated (VNAV) altitude = blue, per manual S.105 */
|
||||
.r-alt.refr { color: #e7edf2; } /* reference altitude = white (not in the VNAV profile) */
|
||||
/* CURRENT VNV PROFILE panel (MFD flight-plan page) */
|
||||
.fpl-vnav { border-top: 1px solid #2c343c; padding: 6px 12px 8px; font-family: 'Roboto Mono', monospace; }
|
||||
.fpl-vnav-h { color: #36d2ff; font-size: 11px; letter-spacing: 1px; margin-bottom: 5px; }
|
||||
|
||||
Reference in New Issue
Block a user