Manual audit fixes A/E/F: fuel totalizer, Victor/Jet airways, TIME TO BOD
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 <noreply@anthropic.com>
This commit is contained in:
@@ -325,6 +325,7 @@ function startDemo() {
|
|||||||
// engine strip (arrays, like the sim)
|
// engine strip (arrays, like the sim)
|
||||||
engRpm: [2410], fuelFlow: [0.0072], oilTemp: [88], oilPress: [52], egt: [720],
|
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,
|
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
|
// a sample plan so the map/FMS show something in demo mode
|
||||||
fp.setPlan({ name: 'DEMO', waypoints: [
|
fp.setPlan({ name: 'DEMO', waypoints: [
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ export const DATAREFS = {
|
|||||||
oilPress: 'sim/cockpit2/engine/indicators/oil_pressure_psi',
|
oilPress: 'sim/cockpit2/engine/indicators/oil_pressure_psi',
|
||||||
egt: 'sim/cockpit2/engine/indicators/EGT_deg_C',
|
egt: 'sim/cockpit2/engine/indicators/EGT_deg_C',
|
||||||
fuelQty: 'sim/cockpit2/fuel/fuel_quantity',
|
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
|
volts: 'sim/cockpit2/electrical/bus_volts', // array: [0]=main bus, [1]=essential
|
||||||
amps: 'sim/cockpit2/electrical/battery_amps', // battery (S) amps
|
amps: 'sim/cockpit2/electrical/battery_amps', // battery (S) amps
|
||||||
genAmps: 'sim/cockpit2/electrical/generator_amps', // alternator (M) 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
|
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
|
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)
|
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.).
|
// Commands the frontend may TRIGGER (autopilot mode buttons etc.).
|
||||||
|
|||||||
+5
-1
@@ -167,9 +167,13 @@ async function parseAirways(file) {
|
|||||||
const b = index.get((p[3] || '').toUpperCase());
|
const b = index.get((p[3] || '').toUpperCase());
|
||||||
if (!a || !b) continue; // endpoint not in our database
|
if (!a || !b) continue; // endpoint not in our database
|
||||||
const name = p[p.length - 1];
|
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)}`;
|
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); }
|
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++;
|
state.awy++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,11 @@ const PFD_MENU = {
|
|||||||
// page; TOPO/TERRAIN/OSM switch the base map; BACK returns. (OSM is our tuned
|
// page; TOPO/TERRAIN/OSM switch the base map; BACK returns. (OSM is our tuned
|
||||||
// extra layer in an otherwise-empty slot.)
|
// extra layer in an otherwise-empty slot.)
|
||||||
const MFD_MENU = {
|
const MFD_MENU = {
|
||||||
root: ['ENGINE', 'MAP', '', '', '', '', '', '', '', 'DCLTR', '', ''],
|
root: ['SYSTEM', 'MAP', '', '', '', '', '', '', '', 'DCLTR', '', ''],
|
||||||
mapopt: ['TRAFFIC', 'PROFILE', 'TOPO', 'TERRAIN', 'AIRWAYS', 'AIRSPACE', 'NEXRAD', 'OSM', '', '', 'BACK', ''],
|
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)
|
// 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 };
|
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) }));
|
const cycleDcltr = (setter) => setter && setter((m) => ({ ...m, dcltr: (((m.dcltr || 0) + 1) % 4) }));
|
||||||
if (variant === 'mfd') {
|
if (variant === 'mfd') {
|
||||||
if (label === 'MAP') setPage('mapopt');
|
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 === '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 === 'TOPO') setBase('topo'); // relief on/off
|
||||||
else if (label === 'TERRAIN') onMapMode && onMapMode((m) => ({ ...m, terrain: !m.terrain })); // awareness overlay (independent)
|
else if (label === 'TERRAIN') onMapMode && onMapMode((m) => ({ ...m, terrain: !m.terrain })); // awareness overlay (independent)
|
||||||
else if (label === 'OSM') setBase('osm');
|
else if (label === 'OSM') setBase('osm');
|
||||||
else if (label === 'DCLTR') cycleDcltr(onMapMode);
|
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 === 'AIRSPACE') onMapMode && onMapMode((m) => ({ ...m, airspace: !m.airspace }));
|
||||||
} else {
|
} else {
|
||||||
if (label === 'PFD') setPage('pfd');
|
if (label === 'PFD') setPage('pfd');
|
||||||
@@ -149,7 +153,9 @@ export default function Bezel({ variant = 'pfd', xp, svt3d, onToggleSvt, inset,
|
|||||||
<div className="sk-labels">
|
<div className="sk-labels">
|
||||||
{keys.map((s, i) => (
|
{keys.map((s, i) => (
|
||||||
<span key={i} className={`skl ${s ? '' : 'empty'} ${s === 'CAUTION' ? 'caution' : ''} ${isOn(s) ? 'on' : ''}`}>{
|
<span key={i} className={`skl ${s ? '' : 'empty'} ${s === 'CAUTION' ? 'caution' : ''} ${isOn(s) ? 'on' : ''}`}>{
|
||||||
(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
|
||||||
}</span>
|
}</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -80,7 +80,11 @@ export default function FplPage({ xp, full = false, onClose }) {
|
|||||||
const vsReq = c > 0 ? (t - alt) / (c / gs * 60) : 0;
|
const vsReq = c > 0 ? (t - alt) / (c / gs * 60) : 0;
|
||||||
const vDev = alt - (t + c * 6076.12 * tan);
|
const vDev = alt - (t + c * 6076.12 * tan);
|
||||||
const todNm = c - (alt - t) / (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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +128,10 @@ export default function FplPage({ xp, full = false, onClose }) {
|
|||||||
<div className="fpl-vnav-grid">
|
<div className="fpl-vnav-grid">
|
||||||
<b>ACTIVE VNV WPT</b><span className="vwpt">{vnav.tgtAlt}<u>FT</u> at {vnav.wptId}</span>
|
<b>ACTIVE VNV WPT</b><span className="vwpt">{vnav.tgtAlt}<u>FT</u> at {vnav.wptId}</span>
|
||||||
<b>VS TGT</b><span>{Math.round(vnav.vsTgt)}<u>FPM</u></span><b>FPA</b><span>{vnav.fpa.toFixed(1)}°</span>
|
<b>VS TGT</b><span>{Math.round(vnav.vsTgt)}<u>FPM</u></span><b>FPA</b><span>{vnav.fpa.toFixed(1)}°</span>
|
||||||
<b>VS REQ</b><span>{Math.round(vnav.vsReq)}<u>FPM</u></span><b>TIME TO TOD</b><span>{fmtSec(vnav.todSec)}</span>
|
<b>VS REQ</b><span>{Math.round(vnav.vsReq)}<u>FPM</u></span>
|
||||||
|
{vnav.todSec > 0
|
||||||
|
? <><b>TIME TO TOD</b><span>{fmtSec(vnav.todSec)}</span></>
|
||||||
|
: <><b>TIME TO BOD</b><span>{fmtSec(vnav.bodSec)}</span></>}
|
||||||
<b>V DEV</b><span>{vnav.vDev >= 0 ? '+' : ''}{Math.round(vnav.vDev)}<u>FT</u></span>
|
<b>V DEV</b><span>{vnav.vDev >= 0 ? '+' : ''}{Math.round(vnav.vDev)}<u>FT</u></span>
|
||||||
</div>
|
</div>
|
||||||
) : <div className="fpl-vnav-none">— no active VNAV profile —</div>}
|
) : <div className="fpl-vnav-none">— no active VNAV profile —</div>}
|
||||||
|
|||||||
@@ -155,6 +155,17 @@ function EisStrip({ V }) {
|
|||||||
<Bar y={284} label="VAC" min={0} max={10} value={5}
|
<Bar y={284} label="VAC" min={0} max={10} value={5}
|
||||||
zones={[{ from: 4.5, to: 5.5, c: '#0c0' }]} />
|
zones={[{ from: 4.5, to: 5.5, c: '#0c0' }]} />
|
||||||
<FuelBar y={330} left={fuelL} right={fuelR} />
|
<FuelBar y={330} left={fuelL} right={fuelR} />
|
||||||
|
{/* 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 (<>
|
||||||
|
<text x="8" y="392" fill="#39d3c0" fontSize="11">CALC</text>
|
||||||
|
<text x="118" y="392" fill="#fff" fontSize="14" textAnchor="end">{rem.toFixed(1)}</text>
|
||||||
|
<text x="124" y="392" fill="#7f8c97" fontSize="10">GAL</text>
|
||||||
|
<text x="182" y="392" fill="#9aa" fontSize="10" textAnchor="end">USED {used.toFixed(0)}</text>
|
||||||
|
</>);
|
||||||
|
})()}
|
||||||
<text x="8" y="412" fill="#39d3c0" fontSize="12">ENG</text>
|
<text x="8" y="412" fill="#39d3c0" fontSize="12">ENG</text>
|
||||||
<text x="182" y="412" fill="#fff" fontSize="14" textAnchor="end">{engHrs.toFixed(1)} HRS</text>
|
<text x="182" y="412" fill="#fff" fontSize="14" textAnchor="end">{engHrs.toFixed(1)} HRS</text>
|
||||||
<text x="95" y="438" fill="#39d3c0" fontSize="12" textAnchor="middle">– ELECTRICAL –</text>
|
<text x="95" y="438" fill="#39d3c0" fontSize="12" textAnchor="middle">– ELECTRICAL –</text>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export default function MapView({ values, flightPlan, fp, inset = false, hud = t
|
|||||||
const track = num(values.track);
|
const track = num(values.track);
|
||||||
const gs = num(values.groundspeed) * 1.94384; // m/s -> kt
|
const gs = num(values.groundspeed) * 1.94384; // m/s -> kt
|
||||||
const base = mapMode?.base || 'topo';
|
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;
|
const airspace = !!mapMode?.airspace;
|
||||||
aspOnRef.current = airspace;
|
aspOnRef.current = airspace;
|
||||||
const dcltrRef = useRef(dcltr);
|
const dcltrRef = useRef(dcltr);
|
||||||
@@ -154,8 +154,11 @@ export default function MapView({ values, flightPlan, fp, inset = false, hud = t
|
|||||||
const segs = await res.json();
|
const segs = await res.json();
|
||||||
layer.clearLayers();
|
layer.clearLayers();
|
||||||
const labels = map.getZoom() >= 8;
|
const labels = map.getZoom() >= 8;
|
||||||
|
const mode = awyOnRef.current; // 1 all · 2 Victor (low) · 3 Jet (high)
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
for (const sg of segs) {
|
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);
|
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)) {
|
if (labels && sg.name && !seen.has(sg.name)) {
|
||||||
seen.add(sg.name);
|
seen.add(sg.name);
|
||||||
|
|||||||
Reference in New Issue
Block a user