diff --git a/changelog.d/1434-voice-unknown-altitude.md b/changelog.d/1434-voice-unknown-altitude.md new file mode 100644 index 000000000..229d31800 --- /dev/null +++ b/changelog.d/1434-voice-unknown-altitude.md @@ -0,0 +1,3 @@ +- Selecting the nearest aircraft by voice no longer says "0 feet" for a contact + whose feed reported no altitude; the reply and card leave the altitude out + (Aditya, @Rohilalala, #1434). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index c1c1ae770..74e8c410d 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -5280,6 +5280,8 @@ and unreachable upstream (502/504) separately from road geometry. cannot race the nearest-aircraft query ahead of layer enablement. A healthy fallback feed is queried normally and its source is returned with the selection; fallback with no airborne records remains an honest no-data result, not an enable failure. + The spoken reply and the card leave out an altitude the feed did not report + rather than reading it as 0 feet. - Voice treats the parent Context panel and Contacts as separate intents. An explicit request to open Context expands only `global-context-panel`; it does not choose a mode. An explicit request to open Contacts expands that parent diff --git a/src/voice/speech.js b/src/voice/speech.js index cbfae621f..a0f65b573 100644 --- a/src/voice/speech.js +++ b/src/voice/speech.js @@ -74,9 +74,9 @@ function listPhrase(items) { } function feetRounded(meters) { - const value = Number(meters); - return Number.isFinite(value) - ? Math.round((value * 3.28084) / 100) * 100 + // A missing altitude is unknown, not sea level: Number(null) is 0. + return Number.isFinite(meters) + ? Math.round((meters * 3.28084) / 100) * 100 : null; } diff --git a/src/voice/speech.test.mjs b/src/voice/speech.test.mjs index cc11fef6e..04b04d9b4 100644 --- a/src/voice/speech.test.mjs +++ b/src/voice/speech.test.mjs @@ -218,6 +218,34 @@ test('nearest-aircraft results name the aircraft, distance and altitude briefly' assert.equal(stale.say, 'Selected AAL1, 3.3 km from Austin, stale.'); }); +test('nearest-aircraft results leave out an altitude the feed did not report', () => { + // gevActions sends `altitudeM: null` for an airborne military contact + // without a barometric altitude; that is unknown, not sea level. + const unknown = envelope('select_nearest_aircraft', { + ok: true, + location: 'Ramstein', + layerId: 'military', + feed: { state: 'nominal' }, + aircraft: { callsign: 'RCH123', altitudeM: null, distanceKm: 8.2 }, + }); + assert.equal(unknown.say, 'Selected RCH123, 8.2 km from Ramstein.'); + assert.deepEqual(unknown.display.lines, ['8.2\u00a0km from Ramstein']); + + // A reported sea-level altitude is still spoken. + const seaLevel = envelope('select_nearest_aircraft', { + ok: true, + location: 'Ramstein', + layerId: 'military', + feed: { state: 'nominal' }, + aircraft: { callsign: 'RCH123', altitudeM: 0, distanceKm: 8.2 }, + }); + assert.equal(seaLevel.say, 'Selected RCH123, 8.2 km from Ramstein, 0 feet.'); + assert.deepEqual(seaLevel.display.lines, [ + '8.2\u00a0km from Ramstein', + 'Altitude 0 feet', + ]); +}); + test('frame_overhead keeps the essential scope audible and lists referents', () => { const framed = envelope('frame_overhead', { ok: true,