From 5db22c85bc5143239f07c6139b3df046d989ade9 Mon Sep 17 00:00:00 2001 From: karim Date: Thu, 4 Jun 2026 00:59:00 +0200 Subject: [PATCH] Manual audit fixes A/E/F: fuel totalizer, Victor/Jet airways, TIME TO BOD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A — Fuel totalizer (SYSTEM key, renamed from ENGINE): DEC/INC/RST FUEL had no handler. Now adjust the fuel_totalizer_sum_kg dataref (±1 gal, RST→max fuel) and the EIS shows the calculated remaining/used. cdiSrc-style writable + demo echo. E — AIRWAYS: was a single on/off. earth_awy.dat field 8 (airway layer) is now parsed (1=Victor/low, 2=Jet/high; name-prefix fallback), the bbox returns it, and the AIRWAYS softkey cycles off→all→Victor→Jet with an AIRWY-LO/-HI label. F — CURRENT VNV PROFILE now shows TIME TO BOD (bottom of descent) once past the top of descent, instead of only TIME TO TOD. Co-Authored-By: Claude Opus 4.8 --- server/bridge.js | 1 + server/config.js | 3 +++ server/navdata.js | 6 +++++- web/src/components/Bezel.jsx | 16 +++++++++++----- web/src/components/FplPage.jsx | 11 +++++++++-- web/src/components/MFD.jsx | 11 +++++++++++ web/src/components/MapView.jsx | 5 ++++- 7 files changed, 44 insertions(+), 9 deletions(-) diff --git a/server/bridge.js b/server/bridge.js index 81427a2..013454f 100644 --- a/server/bridge.js +++ b/server/bridge.js @@ -325,6 +325,7 @@ function startDemo() { // engine strip (arrays, like the sim) engRpm: [2410], fuelFlow: [0.0072], oilTemp: [88], oilPress: [52], egt: [720], fuelQty: [60, 58], volts: [process.env.DEMO_ALERT ? 23.4 : 28.0, 27.8], amps: [-1.5], genAmps: [20.5], engHrs: 5040, + fuelTot: 118 * 2.72, fuelMax: 144 * 2.72, // fuel totalizer: 118 of 144 gal (kg) — SYSTEM keys adjust }); // a sample plan so the map/FMS show something in demo mode fp.setPlan({ name: 'DEMO', waypoints: [ diff --git a/server/config.js b/server/config.js index 7917248..a08e566 100644 --- a/server/config.js +++ b/server/config.js @@ -96,6 +96,8 @@ export const DATAREFS = { oilPress: 'sim/cockpit2/engine/indicators/oil_pressure_psi', egt: 'sim/cockpit2/engine/indicators/EGT_deg_C', fuelQty: 'sim/cockpit2/fuel/fuel_quantity', + fuelTot: 'sim/cockpit2/fuel/fuel_totalizer_sum_kg', // pilot-set fuel totalizer (remaining, kg) + fuelMax: 'sim/aircraft/weight/acf_m_fuel_tot', // max fuel capacity (kg) — for RST FUEL volts: 'sim/cockpit2/electrical/bus_volts', // array: [0]=main bus, [1]=essential amps: 'sim/cockpit2/electrical/battery_amps', // battery (S) amps genAmps: 'sim/cockpit2/electrical/generator_amps', // alternator (M) amps @@ -135,6 +137,7 @@ export const WRITABLE_DATAREFS = { xpdrMode: 'sim/cockpit2/radios/actuators/transponder_mode', // 0 off,1 stby,2 on,3 alt xpdrCode: 'sim/cockpit2/radios/actuators/transponder_code', // 4-digit squawk cdiSrc: 'sim/cockpit2/radios/actuators/HSI_source_select_pilot', // 0 NAV1 · 1 NAV2 · 2 GPS (CDI softkey cycles it) + fuelTot: 'sim/cockpit2/fuel/fuel_totalizer_sum_kg', // SYSTEM → DEC/INC/RST FUEL adjusts the totalizer }; // Commands the frontend may TRIGGER (autopilot mode buttons etc.). diff --git a/server/navdata.js b/server/navdata.js index 9f8ddaa..0c40d27 100644 --- a/server/navdata.js +++ b/server/navdata.js @@ -167,9 +167,13 @@ async function parseAirways(file) { const b = index.get((p[3] || '').toUpperCase()); if (!a || !b) continue; // endpoint not in our database const name = p[p.length - 1]; + // Field 8 (index 7) = airway layer: 1 = low/Victor, 2 = high/Jet (used by the + // MFD AIRWAYS key to show all / Victor-only / Jet-only). Fall back to the name + // prefix (V… = low, J… = high) if the field is missing. + const lyr = +p[7] === 1 ? 1 : +p[7] === 2 ? 2 : /^J/i.test(name) ? 2 : /^V/i.test(name) ? 1 : 0; const k = `${Math.floor((a.lat + b.lat) / 2)},${Math.floor((a.lon + b.lon) / 2)}`; let arr = awyCells.get(k); if (!arr) { arr = []; awyCells.set(k, arr); } - arr.push({ la1: a.lat, lo1: a.lon, la2: b.lat, lo2: b.lon, name }); + arr.push({ la1: a.lat, lo1: a.lon, la2: b.lat, lo2: b.lon, name, lyr }); state.awy++; } } diff --git a/web/src/components/Bezel.jsx b/web/src/components/Bezel.jsx index ad89dc4..6188a7a 100644 --- a/web/src/components/Bezel.jsx +++ b/web/src/components/Bezel.jsx @@ -28,10 +28,11 @@ const PFD_MENU = { // page; TOPO/TERRAIN/OSM switch the base map; BACK returns. (OSM is our tuned // extra layer in an otherwise-empty slot.) const MFD_MENU = { - root: ['ENGINE', 'MAP', '', '', '', '', '', '', '', 'DCLTR', '', ''], + root: ['SYSTEM', 'MAP', '', '', '', '', '', '', '', 'DCLTR', '', ''], mapopt: ['TRAFFIC', 'PROFILE', 'TOPO', 'TERRAIN', 'AIRWAYS', 'AIRSPACE', 'NEXRAD', 'OSM', '', '', 'BACK', ''], - engine: ['DEC FUEL', 'INC FUEL', 'RST FUEL', '', '', '', '', '', '', '', 'BACK', ''], + system: ['DEC FUEL', 'INC FUEL', 'RST FUEL', '', '', '', '', '', '', '', 'BACK', ''], }; +const KG_PER_GAL = 2.72; // fuel totalizer steps in US gallons (matches the EIS readout) // autopilot_state bitfield (best-effort; tweak per aircraft) const AP_BITS = { fd: 1 << 0, hdg: 1 << 1, vs: 1 << 4, flc: 1 << 6, nav: 1 << 8, apr: 1 << 9, vnav: 1 << 11, altHold: 1 << 14, bc: 1 << 18 }; @@ -67,13 +68,16 @@ export default function Bezel({ variant = 'pfd', xp, svt3d, onToggleSvt, inset, const cycleDcltr = (setter) => setter && setter((m) => ({ ...m, dcltr: (((m.dcltr || 0) + 1) % 4) })); if (variant === 'mfd') { if (label === 'MAP') setPage('mapopt'); - else if (label === 'ENGINE') setPage('engine'); + else if (label === 'SYSTEM') setPage('system'); else if (label === 'BACK') setPage('root'); + else if (label === 'DEC FUEL' && xp) xp.setDataref('fuelTot', Math.max(0, num(xp.values?.fuelTot) - KG_PER_GAL)); + else if (label === 'INC FUEL' && xp) xp.setDataref('fuelTot', num(xp.values?.fuelTot) + KG_PER_GAL); + else if (label === 'RST FUEL' && xp) xp.setDataref('fuelTot', num(xp.values?.fuelMax) || num(xp.values?.fuelTot)); else if (label === 'TOPO') setBase('topo'); // relief on/off else if (label === 'TERRAIN') onMapMode && onMapMode((m) => ({ ...m, terrain: !m.terrain })); // awareness overlay (independent) else if (label === 'OSM') setBase('osm'); else if (label === 'DCLTR') cycleDcltr(onMapMode); - else if (label === 'AIRWAYS') onMapMode && onMapMode((m) => ({ ...m, airways: !m.airways })); + else if (label === 'AIRWAYS') onMapMode && onMapMode((m) => ({ ...m, airways: (((m.airways | 0) + 1) % 4) })); // off→all→Victor→Jet else if (label === 'AIRSPACE') onMapMode && onMapMode((m) => ({ ...m, airspace: !m.airspace })); } else { if (label === 'PFD') setPage('pfd'); @@ -149,7 +153,9 @@ export default function Bezel({ variant = 'pfd', xp, svt3d, onToggleSvt, inset,
{keys.map((s, i) => ( { - (s === 'DCLTR' && (mapMode?.dcltr || insetMode?.dcltr)) ? `DCLTR-${mapMode?.dcltr || insetMode?.dcltr}` : s + (s === 'DCLTR' && (mapMode?.dcltr || insetMode?.dcltr)) ? `DCLTR-${mapMode?.dcltr || insetMode?.dcltr}` + : (s === 'AIRWAYS' && (mapMode?.airways | 0) >= 2) ? (mapMode.airways === 2 ? 'AIRWY-LO' : 'AIRWY-HI') + : s } ))}
diff --git a/web/src/components/FplPage.jsx b/web/src/components/FplPage.jsx index 9b87c83..bbcd8b4 100644 --- a/web/src/components/FplPage.jsx +++ b/web/src/components/FplPage.jsx @@ -80,7 +80,11 @@ export default function FplPage({ xp, full = false, onClose }) { const vsReq = c > 0 ? (t - alt) / (c / gs * 60) : 0; const vDev = alt - (t + c * 6076.12 * tan); const todNm = c - (alt - t) / (6076.12 * tan); - vnav = { wptId: wps[i].id, tgtAlt: t, vsTgt, vsReq, vDev, fpa: 3.0, todSec: todNm > 0 ? (todNm / gs) * 3600 : 0 }; + // Before TOD: time until the descent path is intercepted. After TOD (already + // descending): time to Bottom of Descent = reaching the target waypoint. + const todSec = todNm > 0 ? (todNm / gs) * 3600 : 0; + const bodSec = todNm > 0 ? 0 : (c / gs) * 3600; + vnav = { wptId: wps[i].id, tgtAlt: t, vsTgt, vsReq, vDev, fpa: 3.0, todSec, bodSec }; break; } } @@ -124,7 +128,10 @@ export default function FplPage({ xp, full = false, onClose }) {
ACTIVE VNV WPT{vnav.tgtAlt}FT at {vnav.wptId} VS TGT{Math.round(vnav.vsTgt)}FPMFPA{vnav.fpa.toFixed(1)}° - VS REQ{Math.round(vnav.vsReq)}FPMTIME TO TOD{fmtSec(vnav.todSec)} + VS REQ{Math.round(vnav.vsReq)}FPM + {vnav.todSec > 0 + ? <>TIME TO TOD{fmtSec(vnav.todSec)} + : <>TIME TO BOD{fmtSec(vnav.bodSec)}} V DEV{vnav.vDev >= 0 ? '+' : ''}{Math.round(vnav.vDev)}FT
) :
— no active VNAV profile —
} diff --git a/web/src/components/MFD.jsx b/web/src/components/MFD.jsx index 64ad036..bdcede4 100644 --- a/web/src/components/MFD.jsx +++ b/web/src/components/MFD.jsx @@ -155,6 +155,17 @@ function EisStrip({ V }) { + {/* fuel totalizer (SYSTEM → DEC/INC/RST FUEL): pilot-set remaining + used */} + {(() => { + const rem = num(V.fuelTot) / KG_PER_GAL; + const used = Math.max(0, (num(V.fuelMax) - num(V.fuelTot)) / KG_PER_GAL); + return (<> + CALC + {rem.toFixed(1)} + GAL + USED {used.toFixed(0)} + ); + })()} ENG {engHrs.toFixed(1)} HRS – ELECTRICAL – diff --git a/web/src/components/MapView.jsx b/web/src/components/MapView.jsx index 94f62fe..957c820 100644 --- a/web/src/components/MapView.jsx +++ b/web/src/components/MapView.jsx @@ -100,7 +100,7 @@ export default function MapView({ values, flightPlan, fp, inset = false, hud = t const track = num(values.track); const gs = num(values.groundspeed) * 1.94384; // m/s -> kt const base = mapMode?.base || 'topo'; - const airways = !!mapMode?.airways; + const airways = (mapMode?.airways | 0); // 0 off · 1 all · 2 Victor (low) · 3 Jet (high) const airspace = !!mapMode?.airspace; aspOnRef.current = airspace; const dcltrRef = useRef(dcltr); @@ -154,8 +154,11 @@ export default function MapView({ values, flightPlan, fp, inset = false, hud = t const segs = await res.json(); layer.clearLayers(); const labels = map.getZoom() >= 8; + const mode = awyOnRef.current; // 1 all · 2 Victor (low) · 3 Jet (high) const seen = new Set(); for (const sg of segs) { + if (mode === 2 && sg.lyr !== 1) continue; // Victor-only: low-altitude airways + if (mode === 3 && sg.lyr !== 2) continue; // Jet-only: high-altitude airways L.polyline([[sg.la1, sg.lo1], [sg.la2, sg.lo2]], { color: '#5db4e6', weight: 1.2, opacity: 0.7, interactive: false }).addTo(layer); if (labels && sg.name && !seen.has(sg.name)) { seen.add(sg.name);