Files
gods-eye-view/src/voice/gevActions.js
T
Sameh Khamis c1f60cb7e7 Cut the Google requests the HUD summary makes on its own
The HUD builds place context for its AI summary whenever the camera
settles, and each build paid for up to four reverse geocodes plus a Nearby
Places search, even when no OpenAI key was configured to use them.

- Ask GET /api/openai/hud-summary/status once; with no summary configured,
  or after an unconfigured reply, make no place lookups for the page.
- Reverse-geocode at most two points: the view center and one viewport
  point.
- Reuse a lookup until the view center moves a quarter of the visible
  width, the camera's distance to it doubles or halves, or the camera
  crosses a lookup height. The reused points are asked again, so only a
  timeout, failure or empty answer spends.
- Repaint the last AI reply when a small pan leaves the context unchanged.

Cached-only hosts and voice context lookups are unchanged.
2026-10-28 14:59:33 -07:00

5090 lines
164 KiB
JavaScript

import { interruptGlobeRotation } from '../cameraVerbs.js';
import {
layerSnapshot,
feedProvenanceEnvelope,
} from '../data/layerSnapshot.js';
import { readLayerLifecycleSummary } from './layerSummary.js';
import { SPEECH_BUILDERS, attachVoiceResult } from './speech.js';
import { resolveDeicticArgs } from './deixis.js';
export { readLayerLifecycleSummary } from './layerSummary.js';
import { defaultGeospatial } from '../search/defaults.js';
import { msg } from '../i18n/index.js';
import * as Cesium from 'cesium';
import { CITY_POIS } from '../cityPois.js';
import { greatCircleKm } from '../geoDistance.js';
import {
hudViewportSamples,
placeLookupView,
placeLookupViewChanged,
} from '../hudPlaceLookup.js';
import {
findPoiByName,
flyToGlobeView,
flyToLandmark,
flyToPOI,
flyToPresetLocation,
GLOBE_VIEW,
searchAndFlyTo,
} from '../locations.js';
import {
getContextStore,
getSelectedEntityContext,
isContextRecordActive,
} from '../data/contextStore.js';
import { CCTV_FOCUS_RESULT } from '../layers/cctv/index.js';
import {
createAnalystEngine,
normalizeAnalystSpec,
selectAnalystRows,
} from '../data/analystEngine.js';
import { voiceLayer, voiceLayerAliasMap } from './layerManifest.js';
import { layerFeedState } from '../data/feedState.js';
import {
initCameraVerbs,
moveCamera,
flyRoute,
interruptCameraMotion,
adjustOrbitRange,
} from '../cameraVerbs.js';
import * as defaultFloorServices from '../data/groundFloor.js';
import { isPickedWorldPosition } from '../data/scenePick.js';
import { unavailablePlaceSearch } from '../search/placeSearch.js';
import * as defaultAnnotationResolver from '../annotations/annotationResolver.js';
import { normalizeRadioCountryInput } from '../data/radioCountry.js';
import { TR3B_CLASS } from '../data/tr3bRegistry.js';
import {
getWorldPacket,
withContextModeVocabulary,
activeContactsWindow,
TRACKABLE_FAMILIES,
} from '../agent/worldPacket.js';
const ALLOWED_STYLES = new Set([
'normal',
'retro',
'surveillance',
'thermal',
'anime',
'noir',
'snow',
]);
const PANEL_ALIASES = new Map([
['data', 'data-panel'],
['data layers', 'data-panel'],
['layers', 'data-panel'],
['layer menu', 'data-panel'],
['data layer menu', 'data-panel'],
['locations', 'location-bar'],
['location', 'location-bar'],
['styles', 'control-panel'],
['filters', 'control-panel'],
['visual styles', 'control-panel'],
['visual preset', 'control-panel'],
['visual presets', 'control-panel'],
['provider settings', 'provider-settings'],
['cctv', 'cctv-panel'],
['cameras', 'cctv-panel'],
['voice settings', 'voice-settings'],
['voice-settings', 'voice-settings'],
['voice preview', 'voice-preview'],
['voice-preview', 'voice-preview'],
['voice control', 'voice-preview'],
['voice controls', 'voice-preview'],
['radio', 'radio-panel'],
['internet radio', 'radio-panel'],
['radio stations', 'radio-panel'],
['context', 'global-context-panel'],
['context panel', 'global-context-panel'],
['global context', 'global-context-panel'],
['right context', 'global-context-panel'],
['context right panel', 'global-context-panel'],
['scenes', 'scene-panel'],
['scene', 'scene-panel'],
['post processing', 'pp-toggles'],
['hud controls', 'pp-toggles'],
['map stack', 'control-panel'],
['stack', 'control-panel'],
['basemap', 'control-panel'],
['map sources', 'control-panel'],
['sources', 'control-panel'],
['weather', 'weather-panel'],
['weather panel', 'weather-panel'],
['recent imagery', 'recent-imagery-panel'],
['imagery panel', 'recent-imagery-panel'],
]);
const PANEL_IDS = new Set([
'data-panel',
'location-bar',
'control-panel',
'cctv-panel',
'radio-panel',
'global-context-panel',
'scene-panel',
'pp-toggles',
'weather-panel',
'recent-imagery-panel',
'provider-settings',
]);
const CONTEXT_MODE_ALIASES = new Map([
['off', 'off'],
['none', 'off'],
['clear', 'off'],
['contacts', 'flights'],
['contact', 'flights'],
['flights', 'flights'],
['space missions', 'space-missions'],
['space-mission', 'space-missions'],
['space mission', 'space-missions'],
['space-missions', 'space-missions'],
['missions', 'space-missions'],
]);
const COCKPIT_ACTION_ALIASES = new Map([
['next', 'next'],
['previous', 'previous'],
['prev', 'previous'],
['enter', 'enter'],
['exit', 'exit'],
['status', 'status'],
['state', 'status'],
['next military', 'next'],
['next military aircraft', 'next'],
['next helicopter', 'next'],
['next closest', 'next'],
['next closest helicopter', 'next'],
['next closest military', 'next'],
['go to next', 'next'],
]);
const COCKPIT_TARGET_LAYERS = new Set([
'flights',
'military',
'ais-live-vessels',
'military-installations',
]);
// Spoken layer names come from the voice layer manifest, the same source the
// Realtime tool enums are generated from.
const LAYER_ALIASES = voiceLayerAliasMap();
const CITY_ALIASES = new Map([
['new york', 'nyc'],
['new york city', 'nyc'],
['san francisco', 'sf'],
['washington', 'dc'],
['washington dc', 'dc'],
['washington d.c.', 'dc'],
]);
// Basemap stack vocabulary. Switching requires an explicit stack name
// ("Bing aerial", "road map", "OSM", "Google 3D") — any "satellite(s)"
// phrasing ALWAYS means the satellites DATA LAYER, never a basemap; the
// session instructions carry the decision table.
//
// Road phrasings resolve to OSM, the one shipped road basemap. Every alias
// must name a live `MAP_STACKS` id: an alias for a retired stack would resolve
// cleanly and then fail at the controller with "Unknown map stack", which reads
// to the operator as a broken command rather than a retired source.
const STACK_ALIASES = new Map([
['photoreal', 'photoreal'],
['google 3d', 'photoreal'],
['google', 'photoreal'],
['3d', 'photoreal'],
['photorealistic', 'photoreal'],
['bing-aerial', 'bing-aerial'],
['bing aerial', 'bing-aerial'],
['bing-labels', 'bing-labels'],
['bing labels', 'bing-labels'],
['labels', 'bing-labels'],
['aerial with labels', 'bing-labels'],
['esri-imagery', 'esri-imagery'],
['esri', 'esri-imagery'],
['esri imagery', 'esri-imagery'],
['esri satellite', 'esri-imagery'],
['osm', 'osm'],
['openstreetmap', 'osm'],
['open street map', 'osm'],
['road', 'osm'],
['roads', 'osm'],
['road map', 'osm'],
]);
const FRAME_TARGETS = new Map([
['flights', 'flights'],
['planes', 'flights'],
['aircraft', 'flights'],
['military', 'military'],
['military flights', 'military'],
['satellites', 'satellites'],
['vessels', 'ais-live-vessels'],
['ships', 'ais-live-vessels'],
]);
const serviceCaches = new WeakMap();
function cachesFor(service) {
service.signal?.throwIfAborted();
let caches = serviceCaches.get(service);
if (!caches) {
caches = {
reverseGeocodeCache: new Map(),
reverseGeocodeInFlight: new Map(),
nearbyPlacesCache: new Map(),
nearbyPlacesInFlight: new Map(),
};
serviceCaches.set(service, caches);
service.signal?.addEventListener(
'abort',
() => {
for (const cache of Object.values(caches)) cache.clear();
},
{ once: true },
);
}
return caches;
}
const VISIBLE_ENTITY_SHORTLIST = 64;
const BASEMAP_CONTEXT_WAIT_MS = 1500;
const viewTargetCache = new WeakMap();
/** Create application actions over the supplied scene and services. */
export function createGevActionRunner({
viewer,
styleManager,
dataManager,
sceneDirector = null,
annotations = null,
placeSearch = unavailablePlaceSearch,
floorServices = defaultFloorServices,
annotationResolver = defaultAnnotationResolver,
searchNavigation = searchAndFlyTo,
speechBuilders = null,
deixis = null,
readVoicePanelOperation = null,
}) {
// Voice enable times and analyst follow-up memory belong to this runner.
// Speech builders turn results into say/display envelopes; callers extend
// them (e.g. an analyst_query builder) without replacing the defaults.
const builders = speechBuilders
? { ...SPEECH_BUILDERS, ...speechBuilders }
: SPEECH_BUILDERS;
const _layerEnabledAt = new Map();
let analystEngine;
const resolveRegionRing = (name, signal) =>
annotationResolver.resolveRegionRingForQuery(name, signal, placeSearch);
const ensureAnalystEngine = () =>
(analystEngine ||= createAnalystEngine(
analystProviders(viewer, dataManager, {
placeSearch,
resolveRegionRing,
isWarming: (layerKey) => layerIsWarming(_layerEnabledAt, layerKey),
}),
));
installViewTargetPrewarm(viewer);
initCameraVerbs(viewer, getViewTargetCartesian, {
isCockpitActive: () => Boolean(styleManager?.getCockpitState?.()?.active),
});
// Nested calls (select_nearest_aircraft composing layer + flight) use the
// raw runner; only the outer call gets a voice envelope.
const voiceUnavailable = (action) => ({
ok: false,
action,
applied: false,
unavailable: true,
error: 'Voice panel controls are unavailable in this host.',
say: 'Voice panel controls are unavailable in this host.',
});
async function showVoicePanel(
view,
open,
options,
operation = readVoicePanelOperation?.(),
) {
const current = () =>
!options.signal?.aborted &&
(!options.isCurrent || options.isCurrent()) &&
operation?.isCurrent();
if (!operation) return voiceUnavailable('set_panel_open');
if (!current())
return {
ok: false,
action: 'set_panel_open',
applied: false,
cancelled: true,
};
let exit = null;
if (open) {
if (typeof styleManager?.getCockpitState !== 'function')
return voiceUnavailable('set_panel_open');
if (styleManager.getCockpitState()?.active) {
if (typeof styleManager.controlCockpit !== 'function')
return voiceUnavailable('set_panel_open');
exit = await styleManager.controlCockpit('exit');
if (styleManager.getCockpitState()?.active)
return {
ok: false,
action: 'set_panel_open',
applied: false,
exit,
error: exit?.error || 'Cockpit did not exit.',
};
}
}
if (!current())
return {
ok: false,
action: 'set_panel_open',
applied: false,
cancelled: true,
exit,
cockpitExited: Boolean(exit && !styleManager.getCockpitState()?.active),
say:
exit && !styleManager.getCockpitState()?.active
? 'Cockpit exited; the voice panel was left as you last set it.'
: 'The voice panel was left as you last set it.',
};
const result = operation.show(view, open);
return {
...result,
action: 'set_panel_open',
panelId: view === 'settings' ? 'voice-settings' : 'voice-preview',
exit,
state: styleManager?.getCockpitState?.() ?? null,
say: !result.ok
? 'The voice panel was not changed.'
: result.settingsOpen
? 'Voice Settings is open.'
: result.panelOpen
? result.previewEnabled
? 'Voice Preview is open.'
: 'Voice control is open; Preview remains off.'
: 'The voice panel is closed.',
};
}
const runGevAction = async function runGevAction(
name,
rawArgs = {},
runOptions = {},
) {
const args = rawArgs && typeof rawArgs === 'object' ? rawArgs : {};
const current = () =>
!runOptions.signal?.aborted &&
(typeof runOptions.isCurrent !== 'function' || runOptions.isCurrent());
// Step reports for voice narration and the voice card; never fatal.
const reportProgress = (step, label = '') => {
try {
runOptions.progress?.({ step, label: String(label || '') });
} catch {
/* Progress is advisory. */
}
};
// Navigation tools interrupt any continuous camera motion (spec §1.1) —
// checked FIRST because each handler returns.
if (name === 'zoom_to_globe') {
interruptCameraMotion(`nav:${name}`);
}
// Explicit navigation while TRACKING supersedes the follow camera —
// otherwise the tracker drags the view back and "I flew there but can't
// do anything" (field finding). track_entity manages its own handoff.
if (name === 'zoom_to_globe' && viewer.trackedEntity) {
stopAllTracking(viewer, dataManager);
}
// Zoom during an active orbit adjusts the orbit RADIUS (spiral in/out) —
// a straight camera move would be snapped back by the per-frame lookAt.
if (name === 'adjust_camera_zoom') {
interruptGlobeRotation('voice-zoom-intent');
const zoomOut = String(args.direction || '').toLowerCase() === 'out';
const amt = String(args.amount || 'medium').toLowerCase();
const factor = { little: 1.25, medium: 1.6, lot: 2.4 }[amt] || 1.6;
if (adjustOrbitRange(zoomOut ? factor : 1 / factor)) {
return {
ok: true,
action: 'adjust_camera_zoom',
direction: zoomOut ? 'out' : 'in',
amount: amt,
orbitRadiusAdjusted: true,
};
}
}
if (name === 'set_layer_visibility') {
const layerId = normalizeLayerId(args.layerId);
if (!layerId) {
throw new Error(`Unknown data layer: ${args.layerId || 'missing'}`);
}
if (!dataManager.layers.has(layerId)) {
if (layerId === 'radio') {
return {
ok: false,
action: 'set_layer_visibility',
layerId,
error: 'Radio layer unavailable',
...readLayerLifecycleSummary(dataManager, layerId),
};
}
throw new Error(`Unknown data layer: ${args.layerId || 'missing'}`);
}
const enabled = Boolean(args.enabled);
if (enabled) {
const pending = dataManager.getAll?.().find((l) => l.id === layerId);
reportProgress('layer', pending?.name || layerId);
}
const changeOptions = { origin: 'voice' };
if (runOptions.signal) changeOptions.signal = runOptions.signal;
let changed = false;
let changeError = null;
let intentOutcome = null;
try {
if (typeof dataManager._setEnabledWithIntent === 'function') {
const intent = dataManager._setEnabledWithIntent(
layerId,
enabled,
changeOptions,
);
changed = await intent.promise;
if (Number.isInteger(intent.intentEpoch)) {
intentOutcome = await dataManager._waitForVisibilityIntent?.(
layerId,
intent.intentEpoch,
);
}
} else {
changed = await dataManager.setEnabled(
layerId,
enabled,
changeOptions,
);
}
if (layerId === 'rocket-launches' || layerId === 'satellites') {
await styleManager?._waitForContextLayerSettlement?.();
}
} catch (error) {
changeError = error;
}
const lifecycleSummary = readLayerLifecycleSummary(dataManager, layerId);
if (
intentOutcome?.succeeded === false &&
intentOutcome.cancellationReason
) {
return {
ok: false,
action: 'set_layer_visibility',
layerId,
cancelled: true,
phase: intentOutcome.phase,
cancellationReason: intentOutcome.cancellationReason,
successorIntentEpoch: intentOutcome.successorIntentEpoch,
successorEnabled: intentOutcome.successorEnabled,
successorOrigin: intentOutcome.successorOrigin,
...lifecycleSummary,
};
}
const intentCommitted = intentOutcome?.succeeded === true;
const current =
!runOptions.signal?.aborted &&
(typeof runOptions.isCurrent !== 'function' || runOptions.isCurrent());
if (!current && !intentCommitted) {
return {
ok: false,
action: 'set_layer_visibility',
layerId,
cancelled: true,
error: 'Layer request was superseded by a newer voice turn',
...lifecycleSummary,
};
}
const settledEnabled = lifecycleSummary.enabled;
const lifecycleSettled =
lifecycleSummary.lifecycleState ===
(enabled ? 'enabled' : 'disabled') &&
!lifecycleSummary.lifecycleUncertain;
if (changeError) {
return {
ok: false,
action: 'set_layer_visibility',
layerId,
error:
changeError?.message ||
`Could not ${enabled ? 'enable' : 'disable'} the requested layer`,
...lifecycleSummary,
};
}
if (
changed === false ||
settledEnabled !== enabled ||
!lifecycleSettled
) {
return {
ok: false,
action: 'set_layer_visibility',
layerId,
error: `Could not ${enabled ? 'enable' : 'disable'} the requested layer`,
...lifecycleSummary,
};
}
if (enabled) _layerEnabledAt.set(layerId, Date.now());
const layer = dataManager.getAll().find((item) => item.id === layerId);
const snapshot = layer && enabled ? layerSnapshot(layer) : null;
return {
ok: true,
action: 'set_layer_visibility',
layerId,
label: layer?.name || layerId,
...lifecycleSummary,
...(snapshot?.feedState ? { feedState: snapshot.feedState } : {}),
...(snapshot?.source ? { source: snapshot.source } : {}),
};
}
if (name === 'select_nearest_aircraft') {
const layerId = normalizeLayerId(args.layerId || 'flights');
if (!['flights', 'military'].includes(layerId)) {
return {
ok: false,
action: 'select_nearest_aircraft',
error:
'Nearest-aircraft selection supports Flights or Military Flights only',
};
}
const hasLocationId = Boolean(String(args.locationId || '').trim());
const hasLocationQuery = Boolean(String(args.locationQuery || '').trim());
const hasCoordinates =
args.latitude != null &&
args.longitude != null &&
Number.isFinite(Number(args.latitude)) &&
Number.isFinite(Number(args.longitude));
if (!hasLocationId && !hasLocationQuery && !hasCoordinates) {
return {
ok: false,
action: 'select_nearest_aircraft',
stage: 'location',
error:
'Nearest-aircraft selection needs a preset, place name, or latitude and longitude',
};
}
const locationArgs = {
waitForArrival: true,
...(args.locationId ? { locationId: args.locationId } : {}),
...(args.locationQuery ? { query: args.locationQuery } : {}),
...(hasCoordinates
? {
latitude: Number(args.latitude),
longitude: Number(args.longitude),
}
: {}),
};
const layer = await runGevAction(
'set_layer_visibility',
{
layerId,
enabled: true,
},
runOptions,
);
if (layer?.ok !== true || !current()) {
return {
ok: false,
action: 'select_nearest_aircraft',
stage: 'layer',
cancelled: !current() || Boolean(layer?.cancelled),
error: layer?.error || `${layerId} could not be enabled`,
layer,
};
}
const location = await runGevAction(
'fly_to_location',
locationArgs,
runOptions,
);
if (location?.ok !== true || !current()) {
return {
ok: false,
action: 'select_nearest_aircraft',
stage: 'location',
cancelled: !current() || Boolean(location?.cancelled),
error:
location?.error ||
`Could not arrive at ${location?.label || args.locationQuery || args.locationId || 'the requested place'}`,
location,
layer,
};
}
const layerModule = dataManager.layers.get(layerId)?.module || null;
let refreshed = false;
reportProgress('refresh', location.label);
try {
if (typeof dataManager.refreshLayer === 'function') {
refreshed = await dataManager.refreshLayer(layerId, {
signal: runOptions.signal,
});
} else if (typeof layerModule?.update === 'function') {
refreshed =
(await layerModule.update(viewer, {
signal: runOptions.signal,
})) !== false;
}
} catch {
refreshed = false;
}
if (!refreshed || !current()) {
return {
ok: false,
action: 'select_nearest_aircraft',
stage: 'refresh',
cancelled: !current(),
error: !current()
? 'Nearest-aircraft refresh was cancelled'
: `${layer.label || layerId} is enabled, but its destination refresh did not complete`,
location,
layer,
};
}
const stats = layerModule?.getStats?.() || {};
const source =
String(stats.source || layerModule?.source || '').trim() || null;
const feed = {
state: layerFeedState({ ...stats, source }),
source,
count: Number.isFinite(Number(stats.count))
? Number(stats.count)
: null,
};
reportProgress('nearest', location.label);
const nearest = await createAnalystEngine(
analystProviders(viewer, dataManager, {
recordLimitByLayer: { [layerId]: Number.MAX_SAFE_INTEGER },
placeSearch,
resolveRegionRing,
}),
).query({
layers: [layerId],
scope: { kind: 'view' },
filters: [{ field: 'onGround', op: 'eq', value: false }],
sortBy: 'distance',
sortDir: 'asc',
limit: 1,
});
const aircraft = nearest?.items?.[0] || null;
if (!aircraft || !current()) {
return {
ok: false,
action: 'select_nearest_aircraft',
stage: 'nearest',
cancelled: !current(),
error: !current()
? 'Nearest-aircraft selection was cancelled'
: feed.state === 'unavailable'
? `${layer.label || layerId} is enabled, but ${feed.source || 'its aircraft feed'} is unavailable`
: `${layer.label || layerId} is enabled${feed.state === 'fallback' ? ` on the ${feed.source || 'fallback'} feed` : ''}, but no airborne aircraft is loaded in the ${location.label || 'destination'} view yet`,
location,
layer,
feed,
count: nearest?.count || 0,
};
}
const stableAircraftId = aircraft.icao24 || aircraft.id;
const selection = await trackEntity(viewer, dataManager, styleManager, {
query: stableAircraftId,
layerId,
});
if (selection?.ok !== true) {
return {
ok: false,
action: 'select_nearest_aircraft',
stage: 'selection',
error:
selection?.error ||
'The nearest airborne aircraft could not be selected',
location,
layer,
feed,
selection,
};
}
return {
ok: true,
action: 'select_nearest_aircraft',
location: location.label,
layerId,
label: selection.label,
feed,
aircraft: {
id: stableAircraftId,
callsign: aircraft.callsign || null,
altitudeM: aircraft.altitudeM ?? null,
distanceKm: aircraft.distanceKm ?? null,
onGround: false,
},
};
}
if (name === 'set_visual_style') {
const style = normalizeStyle(args.style);
if (!style)
throw new Error(`Unknown visual style: ${args.style || 'missing'}`);
styleManager.setStyle(style, { userInitiated: true });
return { ok: true, action: 'set_visual_style', style };
}
if (name === 'set_voice_inactivity_timeout') {
const operation = readVoicePanelOperation?.();
if (!operation) return voiceUnavailable(name);
if (!current() || !operation.isCurrent())
return { ok: false, action: name, applied: false, cancelled: true };
const result = operation.setMinutes(args.minutes);
const duration =
result.minutes === 0 ? 'disabled' : `${result.minutes} minutes`;
return {
...result,
action: name,
say: !result.applied
? 'Voice inactivity was not changed.'
: result.persisted
? `Voice inactivity is ${duration}, saved in this browser.`
: `Voice inactivity is ${duration} for now, but could not be saved in this browser.`,
};
}
if (name === 'set_panel_open') {
const panelId = normalizePanelId(args.panelId || args.panel);
if (!panelId)
throw new Error(
`Unknown panel: ${args.panelId || args.panel || 'missing'}`,
);
const open = args.open !== false;
if (panelId === 'voice-settings' || panelId === 'voice-preview')
return showVoicePanel(
panelId === 'voice-settings' ? 'settings' : 'preview',
open,
runOptions,
);
if (panelId === 'provider-settings') {
if (!current())
return {
ok: false,
action: name,
panelId,
applied: false,
cancelled: true,
};
const doc = globalThis.document;
const panel = doc?.getElementById?.('key-setup');
const button = open
? doc?.getElementById?.('key-setup-chip')
: panel?.querySelector?.('[data-key-setup-close]');
const concealed =
open &&
[
'ui-clean-view',
'recording-mode',
'cockpit-mode',
'scene-playback-mode',
].some((mode) => doc?.body?.classList?.contains(mode));
if (
concealed ||
panel?.dataset?.ready !== 'true' ||
typeof button?.click !== 'function'
) {
const error = concealed
? 'Provider Settings is hidden in the current view. Leave that view before opening it.'
: 'Provider Settings is unavailable in this runtime.';
return {
ok: false,
action: name,
panelId,
applied: false,
unavailable: true,
error,
say: error,
};
}
button.click();
// Opening unhides immediately; closing removes visible before its fade ends.
const applied = open
? !panel.hidden
: !panel.classList.contains('visible');
const say = applied
? `Provider Settings is ${open ? 'open' : 'closed'}.`
: 'Provider Settings did not change.';
return {
ok: applied,
action: name,
panelId,
applied,
open: applied ? open : !panel.hidden,
say,
...(!applied ? { error: say } : {}),
};
}
setPanelOpen(styleManager, panelId, open);
return { ok: true, action: 'set_panel_open', panelId, open };
}
if (name === 'set_context_mode') {
if (!styleManager?.setContextMode) {
return {
ok: false,
action: 'set_context_mode',
error: 'Context mode control unavailable',
};
}
const mode = normalizeContextMode(args.mode || args.contextMode);
if (
mode === null &&
args.mode != null &&
String(args.mode || '').trim() !== 'off'
) {
return {
ok: false,
action: 'set_context_mode',
error: `Unknown context mode: ${args.mode || 'missing'}`,
};
}
const cancellationState = () =>
withContextModeVocabulary(
typeof styleManager.getContextModeState === 'function'
? styleManager.getContextModeState()
: {},
);
if (!current()) {
return {
ok: false,
action: 'set_context_mode',
cancelled: true,
error: 'Context request was cancelled before it could run',
...cancellationState(),
};
}
if (mode && mode !== 'off') {
setPanelOpen(styleManager, 'global-context-panel', true);
}
const result = await styleManager.setContextMode(
mode === 'off' ? null : mode,
{
signal: runOptions.signal,
isCurrent: runOptions.isCurrent,
},
);
if (!current() && result?.ok !== true) {
return {
...withContextModeVocabulary(result),
ok: false,
action: 'set_context_mode',
cancelled: true,
error:
result?.error ||
'Context request was cancelled before it completed',
...cancellationState(),
};
}
const contactsWindow = ['contacts', 'flights'].includes(mode)
? activeContactsWindow(dataManager)
: null;
return {
...withContextModeVocabulary(result),
...(contactsWindow ? { contactsWindow } : {}),
};
}
if (name === 'control_cockpit') {
if (!styleManager?.controlCockpit) {
return {
ok: false,
action: 'control_cockpit',
error: 'Cockpit control unavailable',
};
}
const rawAction = args.action || args.command;
const action = normalizeCockpitAction(rawAction);
const notificationToken = args.notificationToken || null;
if (!action) {
return {
ok: false,
action: 'control_cockpit',
error: `Unknown cockpit action: ${args.action || args.command || 'missing'}`,
};
}
if (args.voicePanel !== undefined) {
if (
action !== 'exit' ||
!['preview', 'settings'].includes(args.voicePanel)
)
return {
ok: false,
action: 'control_cockpit',
applied: false,
error: 'Voice panel reveal requires exit and a valid view.',
};
const result = await showVoicePanel(args.voicePanel, true, runOptions);
return {
...result,
action: 'control_cockpit',
cockpitAction: 'exit',
voicePanel: args.voicePanel,
};
}
const inferred = normalizeCockpitNavigationHints(rawAction);
const targetLayer = normalizeCockpitTargetLayer(
args.targetLayer || inferred.targetLayer || args.layer || args.layerId,
);
const aircraftClass = normalizeAircraftClassFilter(
args.aircraftClass ||
inferred.aircraftClass ||
args.type ||
args.filterType,
);
let contextChangedForEntry = false;
let priorContextMode = null;
let rollbackTarget = null;
if (
action === 'enter' &&
typeof styleManager.setContextMode === 'function'
) {
if (!current()) {
return {
ok: false,
action: 'control_cockpit',
cancelled: true,
error: 'Cockpit entry was cancelled before it could run',
state: styleManager.getCockpitState?.() || null,
};
}
rollbackTarget = styleManager.getAircraftTrackingTarget?.() || null;
const contextState =
typeof styleManager.getContextModeState === 'function'
? styleManager.getContextModeState()
: {};
priorContextMode = contextState?.mode || null;
const contactsReady =
contextState?.mode === 'flights' &&
contextState?.active !== false &&
contextState?.changing !== true;
if (!contactsReady) {
const contextResult = await styleManager.setContextMode('flights', {
signal: runOptions.signal,
isCurrent: runOptions.isCurrent,
// Cockpit entry establishes Contacts as its own precondition. That
// is internal choreography, not an operator Context request, so it
// must stay inert: claiming here would cancel a pending shared
// style/detection restore the operator never overrode.
claimVisualAuthority: false,
});
contextChangedForEntry = contextResult?.ok === true;
if (contextResult?.ok !== true || !current()) {
const contextRollback = contextChangedForEntry
? await styleManager.setContextMode(priorContextMode, {
claimVisualAuthority: false,
})
: null;
return {
ok: false,
action: 'control_cockpit',
cancelled: !current() || Boolean(contextResult?.cancelled),
error:
contextResult?.error ||
'Contacts context could not be established for Cockpit entry',
context: contextResult
? withContextModeVocabulary(contextResult)
: null,
contextRollback: withContextModeVocabulary(contextRollback),
state: styleManager.getCockpitState?.() || null,
};
}
}
}
// Contacts activation can adopt a newer explicit aircraft selection.
// Sample only after that transaction settles so an older voice snapshot
// cannot overwrite the operator's newer choice.
const selectedTarget =
action === 'enter' ? selectedCockpitTarget(dataManager) : null;
let cockpitResult;
try {
cockpitResult = await styleManager.controlCockpit(action, {
notificationToken,
targetLayer,
aircraftClass,
selectedTarget,
rollbackTarget,
});
} catch (error) {
cockpitResult = {
ok: false,
action: 'control_cockpit',
error: error instanceof Error ? error.message : String(error),
state: styleManager.getCockpitState?.() || null,
};
}
if (
action === 'enter' &&
cockpitResult?.ok !== true &&
contextChangedForEntry
) {
const contextRollback = await styleManager.setContextMode(
priorContextMode,
{
// Undoing this action's own precondition — still choreography.
claimVisualAuthority: false,
...(current()
? {
signal: runOptions.signal,
isCurrent: runOptions.isCurrent,
}
: {}),
},
);
return {
...cockpitResult,
contextRollback: withContextModeVocabulary(contextRollback),
};
}
return cockpitResult;
}
if (name === 'show_data_layers_menu') {
const layerId = normalizeLayerId(args.layerId || args.layer);
setPanelOpen(styleManager, 'data-panel', true);
const focusedLayer =
layerId && dataManager.layers.has(layerId)
? focusDataLayerRow(layerId)
: null;
return {
ok: true,
action: 'show_data_layers_menu',
panelId: 'data-panel',
focusedLayer,
layers: dataManager
.getAll()
.filter((layer) => layer.showInTogglePanel !== false)
.map((layer) => ({
id: layer.id,
name: layer.name,
enabled: layer.enabled,
count: layer.stats?.count || 0,
})),
};
}
if (name === 'fly_to_location') {
// A referenced contact moves: locate it now, not from the old list.
if (args.entity?.layerId && args.entity?.id) {
const located = locateLayerEntity(dataManager, args.entity);
if (!located) {
return {
ok: false,
action: 'fly_to_location',
error: `${args.entity.label || args.entity.id} is no longer loaded`,
};
}
const { entity: _entity, ...rest } = args;
return runGevAction(
name,
{ ...rest, latitude: located.lat, longitude: located.lon },
runOptions,
);
}
return flyToRequestedLocation(viewer, args, {
onProgress: reportProgress,
placeSearch,
searchNavigation,
signal: runOptions.signal,
runImmediate:
typeof styleManager?.runImmediateLocationNavigation === 'function'
? (navigate) =>
styleManager.runImmediateLocationNavigation(navigate)
: null,
beginDeferred:
typeof styleManager?.beginDeferredLocationNavigation === 'function'
? () => styleManager.beginDeferredLocationNavigation()
: null,
reassertDeferred:
typeof styleManager?.reassertDeferredLocationNavigation === 'function'
? (generation) =>
styleManager.reassertDeferredLocationNavigation(generation)
: null,
onStart: () => {
if (typeof styleManager?.beginLocationNavigation === 'function') {
styleManager.beginLocationNavigation();
return;
}
interruptCameraMotion('nav:fly_to_location');
if (viewer.trackedEntity) stopAllTracking(viewer, dataManager);
},
});
}
if (name === 'adjust_camera_zoom') {
return adjustCameraZoom(viewer, args);
}
if (name === 'zoom_to_globe') {
if (typeof styleManager?.resetToGlobeView === 'function') {
return styleManager.resetToGlobeView();
}
const result = flyToGlobeView(viewer);
return {
ok: true,
action: 'zoom_to_globe',
heightKm: Math.round(GLOBE_VIEW.heightM / 1000),
centeredOn: {
latitude: Number(result.latitude.toFixed(2)),
longitude: Number(result.longitude.toFixed(2)),
},
};
}
if (name === 'next_satellite_pass') {
return nextSatellitePass(viewer, dataManager, args);
}
if (name === 'next_iss_pass') {
return nextIssPass(viewer, dataManager, args);
}
if (name === 'analyst_query') {
return runAnalystQuery(
ensureAnalystEngine(),
dataManager,
args,
_layerEnabledAt,
current,
runOptions.signal,
);
}
if (name === 'move_camera') {
return moveCamera(args, (navigate, releaseOptions) =>
runManagedVoiceNavigation(
styleManager,
'camera',
'move_camera',
navigate,
releaseOptions,
),
);
}
if (name === 'fly_route') {
return flyRoute(
annotations?.list?.() || [],
args,
(lat, lon) => floorServices.cachedGroundFloor(lat, lon),
(navigate) =>
runManagedVoiceNavigation(
styleManager,
'route',
'fly_route',
navigate,
),
(cells) => floorServices.warmGroundFloor(cells),
);
}
if (name === 'get_entity_context') {
return getEntityContext(
viewer,
dataManager,
styleManager,
args,
placeSearch,
);
}
if (name === 'get_current_view_state') {
return getCurrentViewState(
viewer,
styleManager,
dataManager,
sceneDirector,
);
}
if (name === 'set_hud') {
const out = { ok: true, action: 'set_hud' };
if (args.layout != null) {
const result = styleManager.setHudLayout(args.layout);
if (!result.ok) return { ...result, action: 'set_hud' };
Object.assign(out, result);
}
if (args.visible != null) {
const result = styleManager.setHudVisible(args.visible);
if (!result.ok) return { ...result, action: 'set_hud' };
Object.assign(out, result);
}
return { ...out, hud: styleManager.getControlState().hud };
}
if (name === 'set_cyber_sonar') {
if (typeof styleManager?.setCyberSonar !== 'function') {
return {
ok: false,
action: 'set_cyber_sonar',
error: 'Cyber sonar controls are unavailable.',
};
}
return { action: 'set_cyber_sonar', ...styleManager.setCyberSonar(args) };
}
if (name === 'set_detection') {
const result = styleManager.setDetection({
enabled: typeof args.enabled === 'boolean' ? args.enabled : undefined,
mode: typeof args.mode === 'string' ? args.mode : undefined,
densityPct: Number.isFinite(Number(args.densityPct))
? Number(args.densityPct)
: undefined,
allocationStrategy:
typeof args.allocationStrategy === 'string'
? args.allocationStrategy
: undefined,
});
return { action: 'set_detection', ...result };
}
if (name === 'set_map_stack') {
const stackId = normalizeStackId(args.stack);
if (!stackId)
throw new Error(`Unknown map stack: ${args.stack || 'missing'}`);
const result = await styleManager.setMapStack(stackId);
return { action: 'set_map_stack', requested: stackId, ...result };
}
if (name === 'set_post_processing') {
const out = { ok: true, action: 'set_post_processing' };
if (args.bloom && typeof args.bloom === 'object') {
Object.assign(
out,
styleManager.setBloom({
enabled:
typeof args.bloom.enabled === 'boolean'
? args.bloom.enabled
: undefined,
intensityPct: Number.isFinite(Number(args.bloom.intensityPct))
? Number(args.bloom.intensityPct)
: undefined,
}),
);
}
if (args.sharpen && typeof args.sharpen === 'object') {
Object.assign(
out,
styleManager.setSharpen({
enabled:
typeof args.sharpen.enabled === 'boolean'
? args.sharpen.enabled
: undefined,
intensityPct: Number.isFinite(Number(args.sharpen.intensityPct))
? Number(args.sharpen.intensityPct)
: undefined,
}),
);
}
return out;
}
if (name === 'control_scene') {
return controlScene(sceneDirector, args);
}
if (name === 'control_cctv') {
const action = String(args.action || '').toLowerCase();
if (
(action === 'enable' || action === 'disable') &&
dataManager.layers.get('cctv')?.module
) {
const result = await runGevAction(
'set_layer_visibility',
{ layerId: 'cctv', enabled: action === 'enable' },
runOptions,
);
return { ...result, action: 'control_cctv' };
}
return controlCctv(dataManager, args, styleManager);
}
if (name === 'control_radio') {
return controlRadio(viewer, dataManager, args, {
...runOptions,
placeSearch,
});
}
if (name === 'track_entity') {
return trackEntity(viewer, dataManager, styleManager, args, {
isCurrent: current,
});
}
if (name === 'stop_tracking') {
return stopAllTracking(viewer, dataManager);
}
if (name === 'frame_overhead') {
return frameOverhead(viewer, dataManager, styleManager, args);
}
if (name === 'annotate_map') {
return annotateMap(annotations, args, reportProgress);
}
if (name === 'clear_annotations') {
return clearAnnotations(annotations);
}
throw new Error(`Unknown GEV tool: ${name}`);
};
// Lifetimes: the runner lives for the page; conversation state — the
// analyst's follow-up memory and any write still in flight — lives for one
// voice session and is dropped by resetConversation().
let conversation = 0;
let disposed = false;
async function runVoiceAction(name, rawArgs = {}, callerOptions = {}) {
if (disposed)
return {
ok: false,
action: name,
cancelled: true,
error: 'Voice ended.',
};
const epoch = conversation;
const runOptions = {
...callerOptions,
isCurrent: () =>
epoch === conversation &&
!disposed &&
(typeof callerOptions.isCurrent !== 'function' ||
callerOptions.isCurrent()),
};
const input = rawArgs && typeof rawArgs === 'object' ? rawArgs : {};
// Point-and-ask: 'pointer' and referent:n become real targets here, from
// the turn's pointer snapshot and the latest numbered result.
const deictic = resolveDeicticArgs(name, input, {
pointer: deixis?.pointer?.activeSnapshot?.() || null,
referents: deixis?.referents || null,
imageFrame: deixis?.imageFrame?.() || null,
cameraKey: deixis?.cameraKey?.() || null,
});
if (deictic.error) {
return attachVoiceResult(
name,
{ ok: false, action: name, error: deictic.error },
input,
builders,
);
}
const referentGeneration = deixis?.referents?.generation?.();
let result = await runGevAction(name, deictic.args, runOptions);
if (deictic.used && result && typeof result === 'object')
result = { ...result, resolvedFrom: deictic.used };
const out = attachVoiceResult(name, result, deictic.args, builders);
// Only an accepted, still-current result may become "the last list": a
// cancelled turn or one finishing after the session ended must not
// repopulate a cleared registry.
const stillCurrent =
!runOptions.signal?.aborted &&
(typeof runOptions.isCurrent !== 'function' || runOptions.isCurrent());
if (stillCurrent)
deixis?.referents?.recordResult?.(name, out, {
since: referentGeneration,
});
if (stillCurrent && deictic.used?.source === 'pointer')
deixis?.onPointerUsed?.();
return out;
}
/**
* A voice session ended: invalidate every write still in flight, then
* forget the analyst's "last answer".
*/
runVoiceAction.resetConversation = () => {
conversation++;
analystEngine?.reset();
};
/** Voice was removed: nothing may run or write again. */
runVoiceAction.dispose = () => {
if (disposed) return;
runVoiceAction.resetConversation();
disposed = true;
};
return runVoiceAction;
}
function selectedCockpitTarget(dataManager) {
const selected = getSelectedEntityContext({ dataManager });
if (!selected || !['flights', 'military'].includes(selected.layerId))
return null;
const module = dataManager?.layers?.get(selected.layerId)?.module;
if (!module?.trackById || typeof module.trackById !== 'function') return null;
const id = String(selected.id || '').trim();
return id ? { layerId: selected.layerId, id } : null;
}
// Abuse guards: a single tool call may not request more than this many marks,
// each route no more waypoints than this, and free-text fields are clamped so a
// runaway model call can't drive unbounded geocode/Overpass/OSRM/DOM work. These
// mirror the tool-schema caps (defense-in-depth: a direct or schema-ignoring call
// is still bounded here).
const MAX_ANNOTATIONS_PER_CALL = 24;
const MAX_ROUTE_POINTS = 12;
const MAX_TARGET_LEN = 200;
const MAX_LABEL_LEN = 120;
function clampStr(value, max) {
if (typeof value !== 'string') return value;
const trimmed = value.trim();
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
}
/** Bound the free-text + array sizes inside one annotation spec before it reaches the engine. */
function sanitizeAnnotationSpec(spec) {
if (!spec || typeof spec !== 'object') return spec;
const out = { ...spec };
if (typeof out.target === 'string')
out.target = clampStr(out.target, MAX_TARGET_LEN);
if (typeof out.toTarget === 'string')
out.toTarget = clampStr(out.toTarget, MAX_TARGET_LEN);
if (typeof out.label === 'string')
out.label = clampStr(out.label, MAX_LABEL_LEN);
if (Array.isArray(out.points)) {
out.points = out.points
.slice(0, MAX_ROUTE_POINTS)
.map((p) =>
p && typeof p === 'object' && typeof p.target === 'string'
? { ...p, target: clampStr(p.target, MAX_TARGET_LEN) }
: p,
);
}
return out;
}
/**
* Draw "whiteboard" annotations on the 3D world to point out what the agent is
* talking about. Place names are resolved to real-world coordinates (and OSM
* footprints) by the annotation engine, so the agent never has to guess pixels.
*/
async function annotateMap(annotations, args = {}, reportProgress = () => {}) {
if (!annotations || typeof annotations.annotate !== 'function') {
return {
ok: false,
action: 'annotate_map',
error: 'Annotation engine unavailable',
};
}
const raw = Array.isArray(args.annotations) ? args.annotations : [];
if (!raw.length) {
return {
ok: false,
action: 'annotate_map',
error: 'No annotations supplied',
};
}
if (raw.length > MAX_ANNOTATIONS_PER_CALL) {
return {
ok: false,
action: 'annotate_map',
error: `Too many annotations in one call (${raw.length}); max ${MAX_ANNOTATIONS_PER_CALL}. Mark fewer places, or split across calls.`,
};
}
const requests = raw.map(sanitizeAnnotationSpec);
const firstTarget = requests.find((spec) => spec?.target || spec?.label);
reportProgress('resolve', firstTarget?.target || firstTarget?.label || '');
const result = await annotations.annotate(requests, {
// C1 invariant enforced in CODE (not just the prompt): the VOICE path NEVER clears as
// a side effect of drawing — annotations accumulate/persist, and only an explicit
// clear_annotations tool call wipes the board. (clearPrevious is intentionally ignored
// here and removed from the annotate_map schema; the console/demo API still has it.)
clearPrevious: false,
persist: args.persist !== false,
flyTo: Boolean(args.flyTo),
});
// Honesty: surface partial failure explicitly so the agent can tell the user
// which place(s) it couldn't mark instead of implying everything appeared.
const drewSome = result.drawn > 0;
const someFailed = result.failed > 0;
const failedLabels = [];
const pendingOutline = (result.results || []).find(
(r) => r.ok && r.outlinePending,
);
if (pendingOutline)
reportProgress('outline', pendingOutline.label || pendingOutline.target);
for (const r of result.results || []) {
if (r.ok) continue;
// Route failures carry the specific missing waypoint name(s) in failedTargets;
// everything else names its own label/target.
if (Array.isArray(r.failedTargets) && r.failedTargets.length)
failedLabels.push(...r.failedTargets);
else failedLabels.push(r.target || r.label || 'an unnamed place'); // target (the place) before caption
}
return {
ok: drewSome,
action: 'annotate_map',
drawn: result.drawn,
failed: result.failed,
partial: drewSome && someFailed,
failedLabels: someFailed ? failedLabels : undefined,
// A drawn route whose street routing was unavailable is a straight direct line,
// not a real walking/driving route — flag it so the voice layer stays honest.
routeFallback: (result.results || []).some((r) => r.ok && r.fallback),
capped: Boolean(result.capped),
// Progressive outlines: anchors are placed and returned immediately; footprints for
// these items are still being traced and will appear on their own (or the mark
// honestly stays a point). NOT a failure — the voice layer must not report it as one.
outlinePending:
(result.results || []).some((r) => r.ok && r.outlinePending) || undefined,
items: result.results,
// Keep `error` populated whenever ANYTHING failed (partial or total) so the
// result never reads as a clean success — but keep it STATIC (no raw place text);
// the actual names live only in the structured failedLabels DATA field, so the
// model-facing prose can't carry injected instructions from a place name.
error: someFailed ? 'Could not place one or more annotations' : null,
};
}
function clearAnnotations(annotations) {
if (!annotations || typeof annotations.clear !== 'function') {
return {
ok: false,
action: 'clear_annotations',
error: 'Annotation engine unavailable',
};
}
annotations.clear();
return { ok: true, action: 'clear_annotations' };
}
export function normalizeStackId(value) {
const raw = String(value || '')
.trim()
.toLowerCase();
if (!raw) return null;
return STACK_ALIASES.get(raw) || null;
}
/**
* Voice scene playback control. Playback is fire-and-forget: startScene
* sequences shots for minutes and must not block the realtime tool loop.
*/
function controlScene(sceneDirector, args = {}) {
if (!sceneDirector) {
return {
ok: false,
action: 'control_scene',
error: 'Scene director unavailable',
};
}
const sceneAction = String(args.action || '').toLowerCase();
if (sceneAction === 'list') {
return {
ok: true,
action: 'control_scene',
scenes: sceneDirector.listScenes(),
...sceneDirector.getPlaybackStatus(),
};
}
if (sceneAction === 'status') {
return {
ok: true,
action: 'control_scene',
...sceneDirector.getPlaybackStatus(),
};
}
if (sceneAction === 'stop') {
sceneDirector.stopScene(msg('Stopped by voice'));
return { ok: true, action: 'control_scene', running: false };
}
if (sceneAction === 'next') {
sceneDirector.runNextScene();
return { ok: true, action: 'control_scene', advanced: true };
}
if (sceneAction === 'play') {
if (sceneDirector.running) {
return {
ok: false,
action: 'control_scene',
error: 'A scene is already running — stop it first',
};
}
const scene = args.sceneId
? sceneDirector.findSceneByQuery(args.sceneId)
: sceneDirector.listScenes()[0] || null;
if (!scene) {
return {
ok: false,
action: 'control_scene',
error: `No scene matched "${args.sceneId || ''}"`,
scenes: sceneDirector.listScenes(),
};
}
void sceneDirector.startScene(scene.id, { single: true });
return {
ok: true,
action: 'control_scene',
playing: scene.title,
shots: scene.shots,
};
}
throw new Error(`Unknown scene action: ${args.action || 'missing'}`);
}
/** Voice CCTV control over the cctv layer module's public surface. */
export async function controlCctv(dataManager, args = {}, styleManager = null) {
const action = String(args.action || '').toLowerCase();
const cctv = dataManager.layers.get('cctv')?.module;
if (!cctv) {
return {
ok: false,
action: 'control_cctv',
error: 'CCTV layer unavailable',
};
}
if (action === 'enable' || action === 'disable') {
return {
ok: false,
action: 'control_cctv',
error: 'Enable or disable CCTV through set_layer_visibility',
};
}
if (!dataManager.isEnabled('cctv')) {
return {
ok: false,
action: 'control_cctv',
error: 'CCTV layer is off — enable it first',
};
}
const summarize = () => {
const ui = cctv.getUIState?.() || {};
return {
activeCameraId: ui.activeCameraId || null,
activeCamera: ui.activeCamera?.name || ui.activeCamera?.id || null,
cameraCount: Array.isArray(ui.cameras)
? ui.cameras.length
: ui.count || 0,
showCoverage: !!ui.showCoverage,
coverageMode: ui.coverageMode || (ui.showCoverage ? 'on' : 'off'),
showProjection: !!ui.showProjection,
calibrationMode: !!ui.calibrationMode,
autoHop: !!ui.autoHop,
};
};
if (action === 'select') {
const query = String(args.cameraQuery || '')
.trim()
.toLowerCase();
if (!query) throw new Error('control_cctv select needs cameraQuery');
const cams = cctv.getUIState?.()?.cameras || [];
const match =
cams.find((cam) => String(cam.id || '').toLowerCase() === query) ||
cams.find((cam) => String(cam.name || '').toLowerCase() === query) ||
cams.find((cam) =>
String(cam.name || '')
.toLowerCase()
.includes(query),
);
if (!match) {
return {
ok: false,
action: 'control_cctv',
error: `No camera matched "${args.cameraQuery}"`,
...summarize(),
};
}
styleManager?.supersedeDeferredNavigation?.();
const selected = cctv.selectCamera(match.id);
const focusResult = selected
? cctv.focusCamera(match.id, 1.8)
: CCTV_FOCUS_RESULT.NO_ACTIVE_CAMERA;
return {
action: 'control_cctv',
selected: match.name || match.id,
...summarize(),
...cctvVoiceFocusOutcome(focusResult, { cameraSelected: !!selected }),
};
}
if (action === 'next' || action === 'prev') {
styleManager?.supersedeDeferredNavigation?.();
const nextId = cctv.cycleCamera(action === 'next' ? 1 : -1);
const focusResult = nextId
? cctv.focusCamera(nextId, 1.8)
: CCTV_FOCUS_RESULT.NO_ACTIVE_CAMERA;
return {
action: 'control_cctv',
...summarize(),
...cctvVoiceFocusOutcome(focusResult, { cameraSelected: !!nextId }),
};
}
if (action === 'nearest') {
styleManager?.supersedeDeferredNavigation?.();
const nearestId = cctv.focusNearest({ focus: false });
const focusResult = nearestId
? cctv.focusCamera(nearestId, 1.8)
: CCTV_FOCUS_RESULT.NO_ACTIVE_CAMERA;
return {
action: 'control_cctv',
...summarize(),
...cctvVoiceFocusOutcome(focusResult, { cameraSelected: !!nearestId }),
};
}
if (action === 'focus') {
const activeId = cctv.getUIState?.()?.activeCameraId;
if (activeId) styleManager?.supersedeDeferredNavigation?.();
const focusResult = activeId
? cctv.focusCamera(activeId)
: CCTV_FOCUS_RESULT.NO_ACTIVE_CAMERA;
return {
action: 'control_cctv',
...summarize(),
...cctvVoiceFocusOutcome(focusResult),
};
}
if (action === 'viewshed') {
// Color-coded coverage volumes; enabled:false drops back to plain
// coverage wireframes (not off — "hide coverage" is the coverage action).
const next =
typeof args.enabled === 'boolean' && !args.enabled ? 'on' : 'viewshed';
dataManager.setLayerParams(
'cctv',
{ coverageMode: next },
{ origin: 'voice' },
);
return { ok: true, action: 'control_cctv', ...summarize() };
}
if (action === 'adjust') {
const current = summarize();
const next =
typeof args.enabled === 'boolean'
? args.enabled
: !current.calibrationMode;
dataManager.setLayerParams(
'cctv',
{ calibrationMode: next },
{ origin: 'voice' },
);
return { ok: true, action: 'control_cctv', ...summarize() };
}
if (action === 'coverage') {
const current = summarize();
const next =
typeof args.enabled === 'boolean' ? args.enabled : !current.showCoverage;
dataManager.setLayerParams(
'cctv',
{ coverageMode: next ? 'on' : 'off' },
{ origin: 'voice' },
);
return { ok: true, action: 'control_cctv', ...summarize() };
}
if (action === 'projection' || action === 'autohop') {
const key = action === 'projection' ? 'showProjection' : 'autoHop';
const current = summarize();
const next =
typeof args.enabled === 'boolean' ? args.enabled : !current[key];
dataManager.setLayerParams('cctv', { [key]: next }, { origin: 'voice' });
return { ok: true, action: 'control_cctv', ...summarize() };
}
throw new Error(`Unknown CCTV action: ${args.action || 'missing'}`);
}
const RADIO_COUNTRY_CENTERS = new Map([
// i18n-ignore: place name in tool data
['us', { lat: 39.8, lon: -98.6, country: 'US', label: 'United States' }],
// i18n-ignore: place name in tool data
['usa', { lat: 39.8, lon: -98.6, country: 'US', label: 'United States' }],
[
'united states',
// i18n-ignore: place name in tool data
{ lat: 39.8, lon: -98.6, country: 'US', label: 'United States' },
],
[
'united states of america',
// i18n-ignore: place name in tool data
{ lat: 39.8, lon: -98.6, country: 'US', label: 'United States' },
],
]);
/** Resolve curated cities and common country requests without moving the camera. */
export function knownRadioLocation(query, locationId = '') {
const requestedId =
normalizeLocationId(locationId) || normalizeLocationId(query);
const city = requestedId ? CITY_POIS[requestedId] : null;
if (city) {
const bounds = city.viewBounds;
return {
lat: bounds
? (bounds.southwest.lat + bounds.northeast.lat) / 2
: city.pois[0]?.lat,
lon: bounds
? (bounds.southwest.lng + bounds.northeast.lng) / 2
: city.pois[0]?.lon,
label: city.name,
country: '',
};
}
return (
RADIO_COUNTRY_CENTERS.get(
String(query || '')
.trim()
.toLowerCase(),
) || null
);
}
function radioCoordinatePair(args = {}) {
const latitudeProvided = Object.hasOwn(args, 'latitude');
const longitudeProvided = Object.hasOwn(args, 'longitude');
const provided = latitudeProvided || longitudeProvided;
const latitude = args.latitude;
const longitude = args.longitude;
const valid =
latitudeProvided &&
longitudeProvided &&
typeof latitude === 'number' &&
typeof longitude === 'number' &&
Number.isFinite(latitude) &&
Number.isFinite(longitude) &&
latitude >= -90 &&
latitude <= 90 &&
longitude >= -180 &&
longitude <= 180;
return { provided, valid, latitude, longitude };
}
function radioActionIsCurrent(options = {}) {
return (
!options.signal?.aborted &&
(typeof options.isCurrent !== 'function' || options.isCurrent())
);
}
function radioAbortError() {
const error = new Error('Radio request was superseded by a newer voice turn');
error.name = 'AbortError';
return error;
}
async function resolveRadioLocation(
args = {},
coordinates = radioCoordinatePair(args),
options = {},
) {
if (!radioActionIsCurrent(options)) throw radioAbortError();
if (coordinates.valid) {
const { latitude, longitude } = coordinates;
return {
lat: latitude,
lon: longitude,
label: `${latitude.toFixed(3)}, ${longitude.toFixed(3)}`,
country: '',
};
}
const query = String(args.locationQuery || '').trim();
const known = knownRadioLocation(query, args.locationId);
if (known) return known;
if (!query) return null;
const { placeSearch = unavailablePlaceSearch, signal } = options;
const { place } = await placeSearch.geocode(query, { signal });
if (!radioActionIsCurrent(options)) throw radioAbortError();
if (!place) return null;
// Localized provider country labels must not become station country filters.
return {
lat: place.lat,
lon: place.lng,
label: place.label || query,
country: '',
};
}
/** Voice Radio controls over the Radio layer's public player surface. */
export async function controlRadio(
viewer,
dataManager,
args = {},
options = {},
) {
const requestedAction = String(args.action || '')
.trim()
.toLowerCase();
const coordinates = radioCoordinatePair(args);
const hasSelectionCriteria = Boolean(
args.category ||
args.locationId ||
args.locationQuery ||
coordinates.provided ||
args.country ||
args.stationQuery,
);
// Realtime models can reasonably interpret "play news near Austin" as Play
// plus qualifiers. Play cannot honor those qualifiers, so normalize that
// equivalent tool shape to Select instead of silently choosing the current
// viewport's nearest station.
const action =
requestedAction === 'play' && hasSelectionCriteria
? 'select'
: requestedAction;
const readRadioLifecycle = () =>
readLayerLifecycleSummary(dataManager, 'radio');
const radio = dataManager.layers.get('radio')?.module;
if (!radio) {
return {
ok: false,
action: 'control_radio',
error: 'Radio layer unavailable',
...readRadioLifecycle(),
};
}
const normalizedCountry = normalizeRadioCountryInput(args.country);
let lastIntentOutcome = null;
let lastIntentError = null;
const intentSummary = () =>
lastIntentOutcome
? {
phase: lastIntentOutcome.phase,
cancellationReason: lastIntentOutcome.cancellationReason || null,
successorIntentEpoch: lastIntentOutcome.successorIntentEpoch ?? null,
successorEnabled: lastIntentOutcome.successorEnabled ?? null,
successorOrigin: lastIntentOutcome.successorOrigin ?? null,
}
: {};
const cancelled = (summarize) => ({
ok: false,
action: 'control_radio',
cancelled: true,
error: 'Radio request was superseded by a newer voice turn',
...intentSummary(),
...summarize(),
});
const radioLifecycleIsSettled = (shouldEnable) => {
const lifecycle = readRadioLifecycle();
return (
lifecycle.enabled === shouldEnable &&
lifecycle.lifecycleState === (shouldEnable ? 'enabled' : 'disabled') &&
!lifecycle.lifecycleUncertain
);
};
const setRadioEnabled = async (shouldEnable) => {
lastIntentOutcome = null;
lastIntentError = null;
if (!radioActionIsCurrent(options)) return false;
const enableOptions = { origin: 'voice' };
// Both lifecycle directions can await module work. Let the manager own
// the complete transaction so barge-in cancels it before a settled
// visibility event can record explicit Context intent.
if (options.signal) enableOptions.signal = options.signal;
let result = false;
try {
if (typeof dataManager._setEnabledWithIntent === 'function') {
const intent = dataManager._setEnabledWithIntent(
'radio',
shouldEnable,
enableOptions,
);
result = await intent.promise;
if (Number.isInteger(intent.intentEpoch)) {
lastIntentOutcome = await dataManager._waitForVisibilityIntent?.(
'radio',
intent.intentEpoch,
);
}
} else {
result = await dataManager.setEnabled(
'radio',
shouldEnable,
enableOptions,
);
}
} catch (error) {
lastIntentError = error;
return false;
}
if (lastIntentOutcome?.succeeded === false) return false;
if (!radioActionIsCurrent(options) && lastIntentOutcome?.succeeded !== true)
return false;
return result !== false && radioLifecycleIsSettled(shouldEnable);
};
const lifecycleFailure = (message, summarize) => ({
ok: false,
action: 'control_radio',
cancelled: Boolean(lastIntentOutcome?.cancellationReason),
error: lastIntentError?.message || message,
...intentSummary(),
...summarize(),
});
const authorizeRadioPlayerMutation = async ({ enableIfOff = false } = {}) => {
const lifecycle = readRadioLifecycle();
if (radioLifecycleIsSettled(true)) return true;
if (
!enableIfOff &&
!lifecycle.enabled &&
lifecycle.lifecycleState === 'disabled' &&
!lifecycle.lifecycleUncertain
)
return false;
const reconciled = await setRadioEnabled(true);
return reconciled && radioLifecycleIsSettled(true);
};
const summarize = () => {
const state = radio.getUIState?.() || {};
return {
radioAction: action,
...readRadioLifecycle(),
stationId: state.selected?.id || null,
category: state.filter || 'all',
audioState: state.audioState || 'stopped',
volumePct: Math.round((state.volume ?? 0.8) * 100),
mutedForVoice: Boolean(state.voiceDucked),
};
};
if (!normalizedCountry.valid) {
return {
ok: false,
action: 'control_radio',
error:
'Radio country must be a recognized code or country name (80 characters maximum)',
...summarize(),
};
}
if (coordinates.provided && !coordinates.valid) {
return {
ok: false,
action: 'control_radio',
error:
'Radio coordinates require a complete numeric latitude/longitude pair in range',
...summarize(),
};
}
if (!radioActionIsCurrent(options)) return cancelled(summarize);
if (action === 'enable' || action === 'disable') {
const shouldEnable = action === 'enable';
const changed = await setRadioEnabled(shouldEnable);
if (!radioActionIsCurrent(options) && lastIntentOutcome?.succeeded !== true)
return cancelled(summarize);
if (!changed) {
return lifecycleFailure(
`Radio could not be ${shouldEnable ? 'enabled' : 'disabled'}`,
summarize,
);
}
return { ok: true, action: 'control_radio', ...summarize() };
}
if (action === 'status')
return { ok: true, action: 'control_radio', ...summarize() };
if (action === 'volume') {
const volumePct = Number(args.volumePct);
if (!Number.isFinite(volumePct) || volumePct < 0 || volumePct > 100) {
return {
ok: false,
action: 'control_radio',
error: 'Radio volume must be from 0 to 100',
...summarize(),
};
}
const authorized = await authorizeRadioPlayerMutation();
if (!radioActionIsCurrent(options)) return cancelled(summarize);
if (!authorized) {
return lifecycleFailure(
'Radio must be fully enabled before changing volume',
summarize,
);
}
if (!radioActionIsCurrent(options)) return cancelled(summarize);
const volumeApplied =
typeof dataManager.setLayerParams === 'function'
? dataManager.setLayerParams(
'radio',
{ volume: volumePct / 100 },
{ origin: 'voice' },
)
: radio.setVolume(volumePct / 100);
if (volumeApplied === false) {
return {
ok: false,
action: 'control_radio',
error: 'Radio must be fully enabled before changing volume',
...summarize(),
};
}
return { ok: true, action: 'control_radio', ...summarize() };
}
if (action === 'stop') {
if (!radioActionIsCurrent(options)) return cancelled(summarize);
let stopped = false;
try {
stopped = await radio.stopPlayback({ origin: 'voice' });
} catch (error) {
return {
ok: false,
action: 'control_radio',
error: error?.message || 'Radio could not be stopped',
...summarize(),
};
}
if (!radioActionIsCurrent(options)) return cancelled(summarize);
if (stopped === false) {
return {
ok: false,
action: 'control_radio',
error: 'Radio could not be stopped',
...summarize(),
};
}
return { ok: true, action: 'control_radio', ...summarize() };
}
if (action === 'pause') {
// Pause is a playback-only control. In particular, a Pause sibling must
// never resurrect a layer that an explicit Disable just turned off. Keep
// its established cancellation authority while an enable is in flight:
// the controller commits a successful Pause by aborting that older work.
const lifecycle = readRadioLifecycle();
if (!lifecycle.enabled && lifecycle.lifecycleState !== 'enabling') {
return {
ok: true,
action: 'control_radio',
changed: false,
...summarize(),
};
}
if (!radioActionIsCurrent(options)) return cancelled(summarize);
const paused = radio.pause?.({ origin: 'voice' }) || false;
if (!paused) {
return {
ok: false,
action: 'control_radio',
error: 'Radio could not be paused',
...summarize(),
};
}
return { ok: true, action: 'control_radio', changed: true, ...summarize() };
}
let resolvedLocation = null;
if (action === 'select') {
try {
// Resolve asynchronous user input before enabling Radio. That keeps an
// interrupted lookup from mutating layer or station state after barge-in.
resolvedLocation = await resolveRadioLocation(args, coordinates, options);
} catch (error) {
if (error?.name === 'AbortError' || !radioActionIsCurrent(options))
return cancelled(summarize);
throw error;
}
if (!radioActionIsCurrent(options)) return cancelled(summarize);
if ((args.locationQuery || args.locationId) && !resolvedLocation) {
return {
ok: false,
action: 'control_radio',
error: `Could not resolve Radio location "${args.locationQuery || args.locationId}"`,
...summarize(),
};
}
}
const authorized = await authorizeRadioPlayerMutation({ enableIfOff: true });
if (!radioActionIsCurrent(options)) return cancelled(summarize);
if (!authorized) {
return lifecycleFailure('Radio could not be enabled', summarize);
}
const state = radio.getUIState?.() || {};
if (!radioActionIsCurrent(options)) return cancelled(summarize);
if (!state.stationCount) {
return {
ok: false,
action: 'control_radio',
error: state.error || 'No healthy Radio stations are available',
...summarize(),
};
}
if (action === 'play' || action === 'resume') {
if (!radioActionIsCurrent(options)) return cancelled(summarize);
const prepared = state.selected
? true
: radio.cycleStation?.(1, { focus: false, autoplay: false });
return {
ok: Boolean(prepared),
action: 'control_radio',
radioPlaybackRequested: Boolean(prepared),
...summarize(),
};
}
if (action === 'next' || action === 'previous') {
if (!radioActionIsCurrent(options)) return cancelled(summarize);
if (args.category) {
if (typeof dataManager.setLayerParams === 'function') {
dataManager.setLayerParams(
'radio',
{ filter: String(args.category) },
{ origin: 'voice' },
);
} else {
radio.setFilter(String(args.category));
}
}
const selected = radio.cycleStation?.(action === 'next' ? 1 : -1, {
focus: false,
autoplay: false,
});
return {
ok: Boolean(selected),
action: 'control_radio',
radioPlaybackRequested: Boolean(selected),
...summarize(),
};
}
if (action === 'select') {
const location = resolvedLocation;
if (!radioActionIsCurrent(options)) return cancelled(summarize);
const station = radio.selectRequestedStation?.(
{
categoryId: String(args.category || 'all'),
anchor: location ? { lat: location.lat, lon: location.lon } : null,
country: normalizedCountry.empty
? String(location?.country || '')
: normalizedCountry.code,
stationQuery: String(args.stationQuery || ''),
},
{ autoplay: false },
);
if (!station) {
return {
ok: false,
action: 'control_radio',
error: 'No Radio station matched that location and category',
...summarize(),
};
}
return {
ok: true,
action: 'control_radio',
radioPlaybackRequested: true,
requestedLocation: location?.label || null,
...summarize(),
};
}
throw new Error(`Unknown Radio action: ${args.action || 'missing'}`);
}
/**
* Maps a CCTV focus code to an honest voice-tool result.
* @param {string|boolean} focusResult CCTV focus result code.
* @param {Object} [options]
* @param {boolean} [options.cameraSelected=false] Whether this action first selected a camera.
* @returns {{ok: boolean, error: string|null}} Voice-facing result fields.
*/
export function cctvVoiceFocusOutcome(
focusResult,
{ cameraSelected = false } = {},
) {
if (focusResult === CCTV_FOCUS_RESULT.FOCUSED || focusResult === true) {
return { ok: true, error: null };
}
if (focusResult === CCTV_FOCUS_RESULT.TRACKING_HOLDS_VIEW) {
return {
ok: false,
error: cameraSelected
? 'Camera selected; tracking holds the view — say untrack to fly'
: 'Camera active; tracking holds the view — say untrack first',
};
}
if (focusResult === CCTV_FOCUS_RESULT.COCKPIT_ACTIVE) {
return {
ok: false,
error: cameraSelected
? 'Camera selected; in cockpit — exit cockpit to fly to it'
: 'In cockpit — exit cockpit to fly to a camera',
};
}
return { ok: false, error: 'No active camera to focus' };
}
/**
* Spoken name for a tracked entity descriptor.
*
* Aircraft follow the flight layers' label convention — callsign →
* registration → icao24 — so the narrated name matches the readout and the
* detection card instead of speaking a raw hex at a contact the UI is calling
* `N123AB`. Vessels and satellites carry no `registration`, so that link
* simply falls through to their own name/mmsi/noradId links.
* @param {object} found - Layer descriptor from `findByQuery`.
* @param {string} query - The spoken query, used as the last resort.
* @returns {string} A non-empty display name.
*/
export function formatTrackedEntityLabel(found, query = '') {
const text = (v) => String(v ?? '').trim();
return (
text(found?.callsign) ||
text(found?.registration) ||
text(found?.name) ||
text(found?.icao24) ||
text(found?.mmsi) ||
text(found?.noradId) ||
String(query)
);
}
/** Finds and tracks/selects an entity by spoken query across layer families. */
async function trackEntity(
viewer,
dataManager,
styleManager,
args = {},
{ isCurrent = () => true } = {},
) {
const cancelled = () => ({
ok: false,
cancelled: true,
action: 'track_entity',
error: 'Tracking request was superseded by a newer voice turn',
});
if (!isCurrent()) return cancelled();
const query = String(args.query || '').trim();
if (!query) throw new Error('track_entity needs a query');
// Fire queries route to the FIRMS layer's strongest detection
if (/\bfires?\b/i.test(query)) {
if (!dataManager.isEnabled('local-firms')) {
return {
ok: false,
action: 'track_entity',
query,
error: 'The FIRMS fires layer is not enabled',
};
}
const firms = dataManager.layers.get('local-firms')?.module;
const strongest = firms?.getStrongestFire?.();
if (!strongest) {
return {
ok: false,
action: 'track_entity',
query,
error: 'No fire detections loaded yet',
};
}
if (
!Number.isFinite(strongest.latitude) ||
!Number.isFinite(strongest.longitude)
) {
return {
ok: false,
action: 'track_entity',
query,
error: 'The strongest fire has no usable position',
};
}
if (!isCurrent()) return cancelled();
return runManagedVoiceNavigation(
styleManager,
'fire',
'track_entity',
() => {
flyToLandmark(viewer, strongest.latitude, strongest.longitude, {
range: 14000,
pitch: -50,
heading: 0,
buildingHeight: 0,
duration: 2.2,
});
return {
ok: true,
action: 'track_entity',
kind: 'fire',
layerId: 'local-firms',
// i18n-ignore: entity name in a tool result for the voice model
label: strongest.label || 'Strongest fire',
latitude: strongest.latitude,
longitude: strongest.longitude,
frp: strongest.frp ?? null,
};
},
);
}
const requested = args.layerId ? normalizeLayerId(args.layerId) : null;
const families = TRACKABLE_FAMILIES.filter(
(family) => !requested || family.layerId === requested,
);
const skippedDisabled = [];
for (const family of families) {
if (!dataManager.isEnabled(family.layerId)) {
skippedDisabled.push(family.layerId);
continue;
}
const module = dataManager.layers.get(family.layerId)?.module;
if (!module || typeof module.findByQuery !== 'function') continue;
const found = module.findByQuery(query);
if (!found) continue;
if (!isCurrent()) return cancelled();
if (
family.kind === 'vessel' &&
(!Number.isFinite(found.latitude) || !Number.isFinite(found.longitude))
) {
return {
ok: false,
action: 'track_entity',
layerId: family.layerId,
kind: family.kind,
error: 'The matched vessel has no usable position',
};
}
return runManagedVoiceNavigation(
styleManager,
family.kind,
'track_entity',
() => {
let trackedOk = false;
if (family.kind === 'vessel') {
trackedOk = !!module.selectById?.(found.mmsi);
flyToLandmark(viewer, found.latitude, found.longitude, {
range: 6000,
pitch: -45,
heading: 0,
buildingHeight: 0,
duration: 2.0,
});
} else if (family.kind === 'satellite') {
trackedOk = !!module.trackById?.(found.noradId, { origin: 'voice' });
} else {
trackedOk = !!module.trackById?.(found.icao24, { origin: 'voice' });
}
return {
ok: trackedOk,
action: 'track_entity',
layerId: family.layerId,
kind: family.kind,
// Aircraft follow the flight layers' label convention (callsign →
// registration → icao24) so the spoken name matches what the UI shows;
// `registration` is absent on vessels/satellites and simply falls
// through to their own name/id links.
label: formatTrackedEntityLabel(found, query),
latitude: found.latitude ?? null,
longitude: found.longitude ?? null,
altitudeM: Number.isFinite(found.altitudeM)
? Math.round(found.altitudeM)
: null,
error: trackedOk ? null : 'Match found but tracking failed',
};
},
);
}
const disabledNote = skippedDisabled.length
? ` (disabled layers skipped: ${skippedDisabled.join(', ')})`
: '';
return {
ok: false,
action: 'track_entity',
query,
error: `Nothing matched "${query}"${disabledNote}`,
};
}
/** Releases tracking/selection on every entity layer family. */
function stopAllTracking(viewer, dataManager) {
const released = [];
const failed = new Set();
for (const family of TRACKABLE_FAMILIES) {
const module = dataManager.layers.get(family.layerId)?.module;
if (!module) continue;
try {
if (family.kind === 'vessel') {
if (module.getSelectedInfo?.()) {
if (
typeof module.clearSelection !== 'function' ||
module.clearSelection() === false
) {
failed.add(family.layerId);
} else {
released.push(family.layerId);
}
}
} else if (module.getTrackedInfo?.()) {
if (
typeof module.stopTracking !== 'function' ||
module.stopTracking({ origin: 'voice' }) === false
) {
failed.add(family.layerId);
} else {
released.push(family.layerId);
}
}
} catch {
failed.add(family.layerId);
}
}
for (const [layerId, key] of [
['flights', 'selectedFlightsTrackingId'],
['military', 'selectedMilitaryTrackingId'],
['satellites', 'selectedSatTrackingId'],
]) {
try {
if (
dataManager.setLayerParams(
layerId,
{ [key]: null },
{ origin: 'voice' },
) === false
) {
failed.add(layerId);
}
} catch {
failed.add(layerId);
}
}
if (viewer) viewer.trackedEntity = undefined;
if (failed.size) {
const failedLayerIds = [...failed];
return {
ok: false,
action: 'stop_tracking',
released,
failedLayerIds,
error: `Tracking could not be cleared for: ${failedLayerIds.join(', ')}`,
};
}
return { ok: true, action: 'stop_tracking', released };
}
/**
* Frames entities near the current view target with a cinematic pull-back:
* oblique high pitch for aircraft/ships, shallow wide pitch for satellites.
* When entries are found and detection is OFF, auto-enables panoptic
* detection so the framed entities are labeled, and reports
* detectionEnabled so the voice agent can mention labels are on.
*/
async function frameOverhead(viewer, dataManager, styleManager, args = {}) {
const targetRaw = String(args.target || 'flights').toLowerCase();
const layerId =
FRAME_TARGETS.get(targetRaw) || normalizeLayerId(targetRaw) || 'flights';
if (!dataManager.layers.has(layerId)) {
return {
ok: false,
action: 'frame_overhead',
error: `Unknown target layer: ${args.target}`,
};
}
if (!dataManager.isEnabled(layerId)) {
return {
ok: false,
action: 'frame_overhead',
layerId,
error: `The ${layerId} layer is not enabled`,
};
}
const module = dataManager.layers.get(layerId)?.module;
const isSatellites = layerId === 'satellites';
const defaultRadiusKm = isSatellites
? 3000
: layerId === 'ais-live-vessels'
? 120
: 150;
const radiusKm = clampNumber(args.radiusKm, 10, 20000, defaultRadiusKm);
const center = getViewTargetCartesian(viewer) || viewer.camera.positionWC;
let entries = [];
if (typeof module.getNearby === 'function') {
entries = module.getNearby(center, radiusKm * 1000, 80) || [];
} else if (typeof module.getAllPositions === 'function') {
entries = (module.getAllPositions(800) || [])
.filter((entry) => entry.position)
.map((entry) => ({
...entry,
distance: Cesium.Cartesian3.distance(center, entry.position),
}))
.filter((entry) => entry.distance <= radiusKm * 1000)
.sort((a, b) => a.distance - b.distance)
.slice(0, 80);
}
if (!entries.length) {
return {
ok: false,
action: 'frame_overhead',
layerId,
radiusKm: Math.round(radiusKm),
count: 0,
error: `No ${targetRaw} within ${Math.round(radiusKm)} km of the current view`,
};
}
const sphere = Cesium.BoundingSphere.fromPoints(
entries.map((entry) => entry.position),
);
sphere.radius = Math.max(sphere.radius * 1.25, 8000);
const pitch = Cesium.Math.toRadians(isSatellites ? -35 : -62);
return runManagedVoiceNavigation(
styleManager,
'frame',
'frame_overhead',
() => {
viewer.camera.flyToBoundingSphere(sphere, {
duration: 2.0,
offset: new Cesium.HeadingPitchRange(
viewer.camera.heading,
pitch,
sphere.radius * 2.4,
),
});
let detectionEnabled = false;
try {
const detectionState = styleManager?.getDetectionState?.();
if (detectionState?.detectionMode === 'OFF') {
const detectionResult = styleManager.setDetection({ mode: 'dense' });
detectionEnabled = detectionResult?.ok === true;
} else if (detectionState) {
detectionEnabled = true;
}
} catch {
// detection facade unavailable; framing still succeeded
}
return {
ok: true,
action: 'frame_overhead',
layerId,
radiusKm: Math.round(radiusKm),
count: entries.length,
detectionEnabled,
nearest: entries.slice(0, 5).map((entry) => ({
id: entry.id || entry.icao24 || entry.mmsi || null,
// Canonical identity: `id` is a display label for aircraft.
...(entry.icao24 ? { icao24: String(entry.icao24) } : {}),
...(entry.mmsi ? { mmsi: String(entry.mmsi) } : {}),
label: entry.label || entry.callsign || entry.name || null,
})),
};
},
);
}
/** Run one validated voice camera mutation through the UI-owned authority seam. */
function runManagedVoiceNavigation(
styleManager,
noun,
action,
navigate,
releaseOptions = undefined,
) {
if (typeof styleManager?.runImmediateNavigation !== 'function') {
return { ok: false, action, error: 'Camera navigation policy unavailable' };
}
const result = styleManager.runImmediateNavigation(
noun,
navigate,
releaseOptions,
);
if (result !== false) return result;
return {
ok: false,
action,
error: 'Camera navigation is unavailable in the current view',
};
}
/**
* Place labels for the HUD's passive summary. It geocodes the view center
* and one more point. While the view still shows about the same area (see
* hudPlaceLookup.js) it answers for the points the last lookup asked about
* instead, so a settled camera does not buy a new set of Google requests for
* every small pan; those points are cache hits, and only what the last
* lookup did not get (a timeout, a failure, an empty answer) is asked again.
* `cachedOnly` reads the shared caches and makes no request.
*/
export async function getBasemapLabelContext(
viewer,
service = defaultGeospatial,
{ cachedOnly = false, previous = null } = {},
) {
const cameraHeightM = viewer.camera.positionCartographic.height;
const target = getViewTargetCartographic(viewer);
if (!target) {
return {
placeLabels: [],
streetLabels: [],
nearbyPlaceLabels: [],
view: null,
lookup: null,
};
}
const latitude = Number(Cesium.Math.toDegrees(target.latitude).toFixed(6));
const longitude = Number(Cesium.Math.toDegrees(target.longitude).toFixed(6));
const view = placeLookupView({
latitude,
longitude,
rangeM: viewTargetRangeM(viewer, target, cameraHeightM),
fovRad: viewer.camera.frustum?.fov,
aspectRatio: viewer.camera.frustum?.aspectRatio,
tier: basemapLookupTier(cameraHeightM),
});
if (
!cachedOnly &&
previous?.lookup &&
!placeLookupViewChanged(previous.view, view)
) {
// The same points again: what the last lookup got is a cache hit, and
// only what it did not (a timeout, a failure, an empty answer) is asked.
const labels = await basemapLabelsAt(previous.lookup, service, {
cachedOnly: false,
});
return { ...labels, view: previous.view, lookup: previous.lookup };
}
const samples = sampleViewportCartographics(viewer);
const lookup = {
latitude,
longitude,
cameraHeightM,
// Cache reads are free, so a cached-only host keeps every sample.
samples: cachedOnly
? samples
: hudViewportSamples(samples, shouldReverseGeocode(cameraHeightM)),
};
return {
...(await basemapLabelsAt(lookup, service, { cachedOnly })),
view,
lookup,
};
}
/** Which of the height-gated lookups a camera height calls for. */
function basemapLookupTier(cameraHeightM) {
return [
shouldReverseGeocode(cameraHeightM),
shouldReverseGeocodeViewport(cameraHeightM) && cameraHeightM > 10000,
shouldFetchNearbyPlaces(cameraHeightM) &&
nearbyPlacesRadiusM(cameraHeightM),
].join(':');
}
/** Camera distance to the view target, or the camera height without one. */
function viewTargetRangeM(viewer, target, cameraHeightM) {
const position = viewer.camera.positionWC;
if (!position) return cameraHeightM;
const range = Cesium.Cartesian3.distance(
position,
Cesium.Cartographic.toCartesian(target),
);
return Number.isFinite(range) ? range : cameraHeightM;
}
async function basemapLabelsAt(
{ latitude, longitude, cameraHeightM, samples },
service,
{ cachedOnly },
) {
const { reverseGeocodeCache, nearbyPlacesCache } = cachesFor(service);
const cachedViewportPlaces = viewportPlacesFromCache(
samples,
cameraHeightM,
service,
);
const viewportPromise = cachedViewportPlaces
? Promise.resolve(cachedViewportPlaces)
: cachedOnly
? Promise.resolve(null)
: reverseGeocodeViewportSamples(samples, cameraHeightM, service);
const placePromise = shouldReverseGeocode(cameraHeightM)
? cachedOnly
? Promise.resolve(
reverseGeocodeCache.get(
reverseGeocodeKey(latitude, longitude, service),
) || null,
)
: reverseGeocode(latitude, longitude, service)
: Promise.resolve(null);
const nearbyPromise = shouldFetchNearbyPlaces(cameraHeightM)
? cachedOnly
? Promise.resolve(
nearbyPlacesCache.get(
nearbyPlacesCacheKey(latitude, longitude, cameraHeightM, service),
) || [],
)
: fetchNearbyPlaces(latitude, longitude, cameraHeightM, service)
: Promise.resolve([]);
const [viewportPlaces, place, nearbyPlaces] = await Promise.all([
resolveWithin(
viewportPromise,
BASEMAP_CONTEXT_WAIT_MS,
cachedViewportPlaces,
),
resolveWithin(placePromise, BASEMAP_CONTEXT_WAIT_MS, null),
resolveWithin(nearbyPromise, BASEMAP_CONTEXT_WAIT_MS, []),
]);
return {
placeLabels: uniqueStrings([
place?.formattedAddress,
place?.locality,
place?.region,
place?.country,
...(place?.labels || []),
...(viewportPlaces?.visibleLabels || []),
]).slice(0, 24),
streetLabels: uniqueStrings([
...(place?.streetLabels || []),
...(viewportPlaces?.streetLabels || []),
]).slice(0, 16),
nearbyPlaceLabels: uniqueStrings(
(nearbyPlaces || []).flatMap((nearbyPlace) => [
nearbyPlace.name,
nearbyPlace.address,
]),
).slice(0, 24),
};
}
function installViewTargetPrewarm(viewer) {
if (viewer.__gevViewTargetPrewarmInstalled) return;
viewer.__gevViewTargetPrewarmInstalled = true;
let timer = null;
let reportedPrewarmFailure = false;
viewer.camera.moveEnd.addEventListener(() => {
if (timer) window.clearTimeout(timer);
timer = window.setTimeout(() => {
timer = null;
// Belt and braces. Validating the pick is the fix; this catch exists
// because an idle callback is an UNCAUGHT context — nothing above it can
// handle a surprise from the scene graph, and a red console error on a
// plain camera flight is worse than a cold cache. Reported once per
// viewer so a repeating cause cannot spam the console.
const warm = () => {
try {
getViewTargetCartographic(viewer);
} catch (error) {
if (reportedPrewarmFailure) return;
reportedPrewarmFailure = true;
console.debug(
'[Voice] view-target prewarm skipped:',
error?.message || error,
);
}
};
if (typeof window.requestIdleCallback === 'function') {
window.requestIdleCallback(warm, { timeout: 500 });
} else {
warm();
}
}, 120);
});
}
function adjustCameraZoom(viewer, args) {
const direction = String(args.direction || '').toLowerCase();
if (direction !== 'in' && direction !== 'out') {
throw new Error('adjust_camera_zoom direction must be "in" or "out"');
}
const amount = String(args.amount || 'little').toLowerCase();
const fraction = {
little: 0.25,
medium: 0.55,
lot: 1.0,
}[amount];
if (!fraction) throw new Error(`Unknown zoom amount: ${args.amount}`);
const camera = viewer.camera;
const beforePosition = Cesium.Cartesian3.clone(camera.positionWC);
const beforeHeightM = camera.positionCartographic.height;
const target = getViewTargetCartesian(viewer);
const targetDistanceM = target
? Cesium.Cartesian3.distance(beforePosition, target)
: Math.max(100, beforeHeightM);
const minimumDistanceM = direction === 'in' ? 20 : 50;
const movementM = Math.max(minimumDistanceM, targetDistanceM * fraction);
camera.cancelFlight();
if (direction === 'out') {
camera.zoomOut(movementM);
} else {
const safeMovementM = Math.min(
movementM,
Math.max(0, targetDistanceM - 25),
);
if (safeMovementM <= 0) {
return {
ok: false,
action: 'adjust_camera_zoom',
direction,
amount,
error: 'Camera is already at the minimum target distance',
};
}
camera.zoomIn(safeMovementM);
}
viewer.scene.requestRender();
const afterPosition = camera.positionWC;
const movedM = Cesium.Cartesian3.distance(beforePosition, afterPosition);
const afterHeightM = camera.positionCartographic.height;
const moved = movedM >= 0.5;
return {
ok: moved,
action: 'adjust_camera_zoom',
direction,
amount,
movementRequestedM: Math.round(movementM),
movementActualM: Math.round(movedM),
beforeHeightM: Math.round(beforeHeightM),
afterHeightM: Math.round(afterHeightM),
error: moved ? null : 'Cesium camera position did not change',
};
}
const COMPASS_16 = [
'N',
'NNE',
'NE',
'ENE',
'E',
'ESE',
'SE',
'SSE',
'S',
'SSW',
'SW',
'WSW',
'W',
'WNW',
'NW',
'NNW',
];
function compassDir(azDeg) {
return COMPASS_16[Math.round((((azDeg % 360) + 360) % 360) / 22.5) % 16];
}
function nextIssPass(viewer, dataManager, args) {
let latDeg = Number.isFinite(args.latitude) ? args.latitude : null;
let lonDeg = Number.isFinite(args.longitude) ? args.longitude : null;
if (latDeg == null || lonDeg == null) {
const carto = viewer?.camera?.positionCartographic;
if (!carto) throw new Error('Camera position unavailable');
latDeg = Cesium.Math.toDegrees(carto.latitude);
lonDeg = Cesium.Math.toDegrees(carto.longitude);
}
const minElevDeg = Number.isFinite(args.minElevationDeg)
? args.minElevationDeg
: 10;
const result = dataManager?.layers
?.get('satellites')
?.module?.getNextIssPass?.({ latDeg, lonDeg, minElevDeg }) ?? {
status: 'no-tle',
};
if (result.status === 'no-tle') {
return {
ok: false,
action: 'next_iss_pass',
error:
'ISS orbital elements not loaded yet — enable the satellites layer once, then ask again.',
};
}
if (result.status === 'none') {
return {
ok: false,
action: 'next_iss_pass',
error: `No ISS pass above ${minElevDeg}° in the next 24 hours for this location.`,
};
}
const { pass } = result;
return {
ok: true,
action: 'next_iss_pass',
observer: { latitude: latDeg, longitude: lonDeg },
riseIso: new Date(pass.riseMs).toISOString(),
minutesFromNow: Math.round((pass.riseMs - Date.now()) / 60000),
durationMin: Math.max(1, Math.round((pass.setMs - pass.riseMs) / 60000)),
peakElevationDeg: Math.round(pass.maxElevDeg),
riseDirection: compassDir(pass.riseAzDeg),
visible: typeof pass.visible === 'boolean' ? pass.visible : null,
visibilityNote:
'Geometric illumination estimate only; weather, brightness and orbital-element age affect actual visibility.',
setIso: new Date(pass.setMs).toISOString(),
peakIso: new Date(pass.maxElevMs).toISOString(),
};
}
function nextSatellitePass(viewer, dataManager, args) {
const layer = dataManager?.layers?.get('satellites')?.module;
const identity = layer?.resolveSatelliteForPass?.(args.target) || {
status: 'not-found',
};
if (identity.status !== 'ok')
return {
ok: false,
action: 'next_satellite_pass',
...identity,
error:
identity.status === 'ambiguous'
? 'Several loaded satellites match. Choose a NORAD ID from candidates.'
: 'No loaded satellite matches. Enable satellites and use an exact name or NORAD ID.',
};
// Reuse the legacy location fallback and result formatting, substituting only
// this explicitly resolved catalog identity and the optional visibility filter.
const adapter = {
layers: new Map([
[
'satellites',
{
module: {
getNextIssPass: (options) =>
layer.getNextSatellitePass(identity.noradId, {
...options,
requireVisible: args.visibleOnly === true,
}),
},
},
],
]),
};
const result = nextIssPass(viewer, adapter, args);
if (result.error) {
result.error = result.error.replace(
/ISS/g,
identity.name || String(identity.noradId),
);
if (args.visibleOnly === true)
result.error +=
' Search required estimated illumination under a dark sky.';
}
return {
...result,
action: 'next_satellite_pass',
noradId: identity.noradId,
name: identity.name,
visibleOnly: args.visibleOnly === true,
horizonHours: 24,
};
}
function normalizePanelId(value) {
const raw = String(value || '').trim();
if (!raw) return null;
if (PANEL_IDS.has(raw)) return raw;
return PANEL_ALIASES.get(raw.toLowerCase()) || null;
}
function normalizeLayerId(value) {
const raw = String(value || '').trim();
if (!raw) return null;
if (LAYER_ALIASES.has(raw.toLowerCase()))
return LAYER_ALIASES.get(raw.toLowerCase());
return raw;
}
function normalizeCockpitTargetLayer(value) {
const layerId = normalizeLayerId(value);
if (!layerId || !COCKPIT_TARGET_LAYERS.has(layerId)) return null;
return layerId;
}
function normalizeCockpitNavigationHints(rawAction) {
const raw = String(rawAction || '')
.trim()
.toLowerCase();
if (!raw) return {};
const targetLayer =
raw.includes('vessel') || raw.includes('ship') || raw.includes('ais')
? 'ais-live-vessels'
: raw.includes('installation') ||
raw.includes('facility') ||
raw.includes('base')
? 'military-installations'
: raw.includes('military')
? 'military'
: null;
const aircraftClass =
raw.includes('helicopter') ||
raw.includes('helo') ||
raw.includes('chopper')
? 'helicopter'
: null;
return {
targetLayer,
aircraftClass,
};
}
/**
* Normalize a spoken/typed aircraft-class filter to the class id the analyst
* records carry.
*
* Every real `classifyAircraft()` id is a single unpunctuated word, so callers
* already say them exactly and a plain lower-case is enough. The one exception
* is the TR-3B Easter egg (`tr3b`): people write and say it hyphenated, so
* "TR-3B" / "tr 3b" / "tr 3 b" would otherwise reach the analyst as a value no
* record matches. Collapsing spaces and hyphens and comparing against THAT ONE
* id keeps this surgical — no general alias table, and no other class id
* collapses to `tr3b`, so nothing else can be caught by it.
*
* App-side only: the voice tool schema and the model instructions are
* untouched, so this costs no prompt-cache churn.
* @param {*} value Raw class filter from the tool call or an utterance hint.
* @returns {string|null} Class id, or null when nothing was supplied.
*/
function normalizeAircraftClassFilter(value) {
const raw = String(value || '')
.trim()
.toLowerCase();
if (!raw) return null;
return raw.replace(/[\s-]+/g, '') === TR3B_CLASS ? TR3B_CLASS : raw;
}
function setPanelOpen(styleManager, panelId, open) {
if (styleManager && typeof styleManager.setPanelCollapsed === 'function') {
styleManager.setPanelCollapsed(panelId, !open, { explicit: true });
} else {
const panel = document.getElementById(panelId);
if (panel) panel.classList.toggle('collapsed', !open);
}
}
function normalizeContextMode(value) {
const raw = String(value || '')
.trim()
.toLowerCase();
if (!raw) return null;
return CONTEXT_MODE_ALIASES.get(raw) || null;
}
function normalizeCockpitAction(value) {
const raw = String(value || '')
.trim()
.toLowerCase();
if (!raw) return null;
const direct = COCKPIT_ACTION_ALIASES.get(raw);
if (direct) return direct;
const normalized = raw
.replace(/[^a-z0-9]+/g, ' ')
.trim()
.replace(/\s+/g, ' ');
const directNormalized = COCKPIT_ACTION_ALIASES.get(normalized);
if (directNormalized) return directNormalized;
if (/\bprevious\b|\bprev\b/.test(normalized)) return 'previous';
if (/\bstatus\b|\bstate\b/.test(normalized)) return 'status';
if (/\bexit\b|\bleave\b|\bquit\b/.test(normalized)) return 'exit';
if (/\benter\b|\bopen\b|\bstart\b/.test(normalized)) return 'enter';
if (/\bnext\b|\bclosest\b|\bnearby\b|\bnearest\b/.test(normalized))
return 'next';
return null;
}
function focusDataLayerRow(layerId) {
const row = document.querySelector(
`#data-toggles [data-layer-id="${CSS.escape(layerId)}"]`,
);
if (!row) return null;
row.scrollIntoView({ block: 'center', behavior: 'smooth' });
row.classList.remove('gev-voice-focus');
void row.offsetWidth;
row.classList.add('gev-voice-focus');
window.setTimeout(() => row.classList.remove('gev-voice-focus'), 3000);
// The English name: tool results stay English whatever the UI language.
const name =
row.dataset.layerName ||
row.querySelector('.data-name')?.textContent?.trim() ||
layerId;
return { id: layerId, name };
}
function normalizeStyle(value) {
const raw = String(value || '')
.trim()
.toLowerCase();
if (raw === 'filter off' || raw === 'off' || raw === 'default')
return 'normal';
if (raw === 'night vision' || raw === 'nvg') return 'surveillance';
if (raw === 'flir') return 'thermal';
if (ALLOWED_STYLES.has(raw)) return raw;
return null;
}
async function flyToRequestedLocation(
viewer,
args,
{
placeSearch = unavailablePlaceSearch,
searchNavigation = searchAndFlyTo,
signal,
onProgress = () => {},
onStart = null,
runImmediate = null,
beginDeferred = null,
reassertDeferred = null,
} = {},
) {
const requestedRangeM = Number(args.rangeM);
const rangeM = Number.isFinite(requestedRangeM)
? clampNumber(requestedRangeM, 100, 20000000, 900)
: null;
const locationId = normalizeLocationId(args.locationId || args.query);
const immediate = (navigate) =>
typeof runImmediate === 'function' ? runImmediate(navigate) : navigate();
const immediateOnStart = typeof runImmediate === 'function' ? null : onStart;
const cancelled = (label) => ({
ok: false,
cancelled: true,
action: 'fly_to_location',
label,
});
let settleArrival = null;
const arrival =
args.waitForArrival === true
? new Promise((resolve) => {
settleArrival = resolve;
})
: null;
const arrivalHooks = arrival
? {
onComplete: () => settleArrival?.('arrived'),
onCancel: () => settleArrival?.('cancelled'),
}
: {};
const afterArrival = async (result, label) => {
if (!arrival || result?.ok !== true) return result;
onProgress('fly', label);
const status = await arrival;
settleArrival = null;
return status === 'arrived'
? { ...result, arrived: true }
: cancelled(label);
};
if (locationId) {
const result = immediate(() =>
flyToPresetLocation(viewer, locationId, {
...(rangeM || args.viewMode === 'close'
? { range: rangeM || 250 }
: { viewMode: 'overview' }),
duration: 2.2,
onStart: immediateOnStart,
...arrivalHooks,
}),
);
if (result === false)
return cancelled(CITY_POIS[locationId]?.name || locationId);
const response = {
ok: Boolean(result),
action: 'fly_to_location',
locationId,
label: CITY_POIS[locationId]?.name || locationId,
rangeM: result?.range ? Math.round(result.range) : rangeM || null,
navigationMode: rangeM
? 'explicit-range'
: args.viewMode === 'close'
? 'city-close'
: result?.navigationMode || 'city-overview',
};
return afterArrival(response, response.label);
}
const latitude = Number(args.latitude);
const longitude = Number(args.longitude);
if (Number.isFinite(latitude) && Number.isFinite(longitude)) {
const result = immediate(() =>
flyToLandmark(viewer, latitude, longitude, {
range: rangeM || 250,
pitch: -35,
heading: 0,
buildingHeight: 0,
duration: 2.2,
onStart: immediateOnStart,
...arrivalHooks,
}),
);
if (result === false)
return cancelled(`${latitude.toFixed(4)}, ${longitude.toFixed(4)}`);
const response = {
ok: true,
action: 'fly_to_location',
latitude,
longitude,
label: `${latitude.toFixed(4)}, ${longitude.toFixed(4)}`,
rangeM: Math.round(rangeM || 250),
navigationMode: rangeM ? 'explicit-range' : 'close-coordinate',
};
return afterArrival(response, response.label);
}
const query = String(args.query || '').trim();
if (query) {
// A query that names a curated preset POI ("the Texas State Capitol", "Golden Gate Bridge")
// flies to its hand-tuned camera pose — the same beautiful framing as clicking the LOCATIONS
// panel button — instead of generic geocode framing. An explicit rangeM still overrides distance.
const poiMatch = findPoiByName(query);
if (poiMatch) {
const result = immediate(() =>
flyToPOI(viewer, poiMatch.cityId, poiMatch.index, {
duration: 2.2,
onStart: immediateOnStart,
...arrivalHooks,
...(rangeM ? { range: rangeM } : {}),
}),
);
const poi = CITY_POIS[poiMatch.cityId]?.pois?.[poiMatch.index];
if (result === false) return cancelled(poi?.name || query);
const response = {
ok: Boolean(result),
action: 'fly_to_location',
query,
label: poi?.name || query,
navigationMode: rangeM ? 'preset-poi-range' : 'preset-poi',
rangeM: result?.range ? Math.round(result.range) : rangeM || null,
};
return afterArrival(response, response.label);
}
const generation =
typeof beginDeferred === 'function' ? beginDeferred() : null;
if (generation === false) return cancelled(query);
onProgress('search', query);
const managedDeferred = typeof reassertDeferred === 'function';
const destination = await searchNavigation(viewer, query, {
placeSearch,
signal,
...(rangeM ? { range: rangeM } : {}),
forceClose: args.viewMode === 'close',
// 'overview' frames the geocode viewport even for precise-place results —
// previously dropped here, so "overview of Zilker Park" flew to a rooftop.
viewMode: args.viewMode || null,
duration: 2.2,
...arrivalHooks,
beforeFly: managedDeferred ? () => reassertDeferred(generation) : null,
onStart: managedDeferred ? null : onStart,
});
if (destination?.cancelled) return cancelled(query);
const response = {
ok: Boolean(destination),
action: 'fly_to_location',
query,
label: destination?.label || query,
navigationMode: destination?.navigationMode || null,
rangeM: destination?.rangeM || rangeM || null,
};
return afterArrival(response, response.label);
}
throw new Error(
'fly_to_location needs a locationId, query, or latitude/longitude',
);
}
function normalizeLocationId(value) {
const raw = String(value || '')
.trim()
.toLowerCase();
if (!raw) return null;
if (CITY_POIS[raw]) return raw;
if (CITY_ALIASES.has(raw)) return CITY_ALIASES.get(raw);
return null;
}
function getCurrentViewState(
viewer,
styleManager,
dataManager,
sceneDirector = null,
) {
return getWorldPacket({
viewer,
styleManager,
dataManager,
sceneDirector,
});
}
async function getEntityContext(
viewer,
dataManager,
styleManager,
args = {},
service = defaultGeospatial,
) {
const startedAt = performance.now();
const scope = String(args.scope || 'auto').toLowerCase();
if (scope === 'target' && args.target) {
return getTargetContext(viewer, dataManager, styleManager, args, service);
}
const layerId = normalizeLayerId(args.layerId || args.layer);
const limit = Math.round(clampNumber(args.limit, 1, 12, 5));
const selected = selectedEntityContext(dataManager);
const cameraHeightM = viewer.camera.positionCartographic.height;
const viewTarget = getViewTargetCartographic(viewer);
const scenePromise = getSceneContext(
viewer,
styleManager,
dataManager,
viewTarget,
service,
);
const selectedWillBeReturned =
selected && (scope === 'selected' || scope === 'auto');
const visible =
!selectedWillBeReturned && shouldScanVisibleEntities(cameraHeightM)
? visibleEntityContexts(viewer, dataManager, {
layerId,
limit,
target: viewTarget,
})
: [];
const scene = await scenePromise;
if ((scope === 'selected' || scope === 'auto') && selected) {
logSlowContext(startedAt, 'selected');
return {
ok: true,
action: 'get_entity_context',
scope: 'selected',
scene,
selected,
};
}
logSlowContext(startedAt, 'in_view');
return {
ok: true,
action: 'get_entity_context',
scope: 'in_view',
scene,
selected: selected || null,
visible,
count: visible.length,
visibleScanSkipped: !shouldScanVisibleEntities(cameraHeightM),
};
}
const AIRCRAFT_LAYERS = new Set(['flights', 'military']);
/** Live position of a layer record by id, or null when it is gone. */
function locateLayerEntity(dataManager, { layerId, id, lat, lon }) {
if (!layerId || !id) return null;
if (dataManager?.isEnabled && !dataManager.isEnabled(layerId)) return null;
let found = null;
try {
found =
dataManager?.layers?.get(layerId)?.module?.findByQuery?.(String(id)) ||
null;
} catch {
found = null;
}
if (Number.isFinite(found?.latitude) && Number.isFinite(found?.longitude))
return { lat: found.latitude, lon: found.longitude, found };
const record = getContextStore().entities.get(String(id));
if (
record?.layerId === layerId &&
Number.isFinite(record.latitude) &&
Number.isFinite(record.longitude)
)
return { lat: record.latitude, lon: record.longitude, record };
// Analyst-only layers (e.g. bikeshare) have no pick/context lookup. Read
// their current snapshot, so a removed or moved record is not resurrected.
try {
const matches =
dataManager?.layers
?.get(layerId)
?.module?.getAnalystRecords?.()
.filter(
(item) =>
String(item.icao24 ?? item.mmsi ?? item.noradId ?? item.id) ===
String(id),
) || [];
// Bikeshare uses station names as IDs: retain the coordinate identity
// even if a namesake disappears. Moving records use their fresh fix.
const row =
matches.length === 1 && layerId !== 'bikeshare'
? matches[0]
: matches.find((item) => item.lat === lat && item.lon === lon);
if (Number.isFinite(row?.lat) && Number.isFinite(row?.lon))
return {
lat: row.lat,
lon: row.lon,
found: { ...row, latitude: row.lat, longitude: row.lon },
};
} catch {
// A missing or unavailable snapshot cannot establish a live position.
}
return null;
}
/**
* Describe one pointed or referenced thing without changing the selection:
* a live aircraft descriptor for contacts, the context-store record for
* everything else. Aircraft fields use the same names as a selected contact,
* so the spoken identity line is identical.
*/
function describeTargetEntity(dataManager, target) {
if (!target?.layerId) return null;
const located = locateLayerEntity(dataManager, target);
if (located?.record)
return summarizeContextRecord(located.record, { includeProperties: true });
const found = located?.found;
if (!found) {
const record = getContextStore().entities.get(String(target.id));
return record
? summarizeContextRecord(record, { includeProperties: true })
: null;
}
const name = formatTrackedEntityLabel(found, target.label || target.id);
const aircraft = AIRCRAFT_LAYERS.has(target.layerId);
const route =
found.origin && found.destination
? `${found.origin} → ${found.destination}`
: '';
return {
id: String(target.id),
name,
layerId: target.layerId,
layerName: layerTitle(target.layerId),
source: null,
latitude: found.latitude ?? null,
longitude: found.longitude ?? null,
properties: compactProperties(
aircraft
? {
name,
callsign: found.callsign || '',
registration: found.registration || '',
operator: found.airline || '',
type: found.typeName || found.typeCode || '',
route,
altitude: Number.isFinite(found.altitudeM)
? `${Math.round(found.altitudeM * 3.28084)} ft`
: '',
}
: { name, mmsi: found.mmsi || '', noradId: found.noradId || '' },
),
active: true,
};
}
/** get_entity_context for a pointed/referenced target (scope 'target'). */
async function getTargetContext(
viewer,
dataManager,
styleManager,
args,
service,
) {
const target = args.target;
// Number(null) is 0: a coordinate-less referent must not become (0, 0).
const coordinate = (value) =>
value === null || value === undefined || value === '' ? NaN : Number(value);
let lat = coordinate(target.lat);
let lon = coordinate(target.lon);
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
const located = locateLayerEntity(dataManager, target);
lat = located?.lat ?? NaN;
lon = located?.lon ?? NaN;
}
const hasPosition = Number.isFinite(lat) && Number.isFinite(lon);
const viewTarget = hasPosition
? Cesium.Cartographic.fromDegrees(lon, lat)
: getViewTargetCartographic(viewer);
const selected = describeTargetEntity(dataManager, target);
const scene = await getSceneContext(
viewer,
styleManager,
dataManager,
viewTarget,
service,
);
if (selected) {
return {
ok: true,
action: 'get_entity_context',
scope: 'pointer',
scene,
selected,
};
}
const cameraHeightM = viewer.camera.positionCartographic.height;
const visible = shouldScanVisibleEntities(cameraHeightM)
? visibleEntityContexts(viewer, dataManager, {
layerId: normalizeLayerId(args.layerId || args.layer),
limit: Math.round(clampNumber(args.limit, 1, 12, 5)),
target: viewTarget,
})
: [];
return {
ok: true,
action: 'get_entity_context',
scope: 'pointer',
scene,
selected: null,
...(hasPosition
? {
pointer: {
latitude: Number(lat.toFixed(5)),
longitude: Number(lon.toFixed(5)),
},
}
: {}),
visible,
count: visible.length,
};
}
/**
* Answer an entity-centred "how many aircraft nearby" from the Contacts
* engine, or null when the question is not that.
*
* Applies only when the requested radius centre IS the active Contacts
* subject: that is precisely the case where the operator can see a number on
* the panel, so the spoken answer must be that number. Everything else — an
* explicit region, an arbitrary point, a named place — keeps the general
* record engine, which is what those questions actually mean.
* @param {object} args Tool arguments.
* @param {object} result The general engine's result, reused for scope text.
* @returns {object|null} A unified-count payload, or null.
*/
async function aircraftProximityWindowForQuery(
dataManager,
args,
result,
isCurrent = () => true,
) {
// Follow-ups re-filter the engine's retained answer. Re-projecting a fresh
// Contacts snapshot here would restore rows a previous filter removed.
if (result?.coverage?.followUp) return null;
const spec = normalizeAnalystSpec(args, args?.layers || []);
if (!spec) return null;
const scope = args?.scope;
if (String(scope?.kind || '').toLowerCase() !== 'radius') return null;
const layers = Array.isArray(args.layers) ? args.layers : [];
if (!layers.some((layer) => layer === 'flights' || layer === 'military'))
return null;
const awareness = dataManager?.layers?.get('military-awareness')?.module;
const snapshot = awareness?.getAircraftQuerySnapshot?.();
const subject = snapshot?.subject;
if (!subject?.position) return null;
// An explicit centre only qualifies when it IS the subject; otherwise the
// operator asked about somewhere else and must get that answer.
if (scope.center && !centerMatchesSubject(scope.center, subject.position))
return null;
const radiusM = Number.isFinite(scope.km)
? scope.km * 1000
: snapshot.radiusM || 250_000;
// A different radius is a different question. Let the general analyst
// engine answer it instead of relabelling the panel's 250 km snapshot.
if (
Number.isFinite(snapshot.radiusM) &&
Math.abs(radiusM - snapshot.radiusM) > 1
)
return null;
const label = subject.label || subject.id || 'the selected contact';
const radiusKm = Math.round(radiusM / 1000);
const wanted = layers.filter(
(layer) => layer === 'flights' || layer === 'military',
);
// Provenance belongs to the Awareness evaluation, not the current layer
// registry. The panel deliberately retains a cohort between render-cadence
// refreshes, so a live registry read here could already describe a newer
// source state than the count and items the operator is looking at.
const retainedProvenance = wanted
.map((key) => snapshot.cohorts?.[key]?.provenance)
.filter(Boolean);
const feedProvenance = feedProvenanceEnvelope(retainedProvenance);
const sourceSnapshotsById = new Map(
feedProvenance.layers.map((layer) => [layer.id, layer]),
);
// The cohort is the panel's; each contact is joined to its loaded analyst
// record so the same filters, ranking and item shape apply as everywhere
// else. The cohort still owns membership and distance.
const distanceKm = new WeakMap();
const groups = wanted.map((key) => {
const records = new Map();
for (const record of dataManager.layers
.get(key)
?.module?.getAnalystRecords?.(ANALYST_RECORD_CAP) || [])
if (record?.icao24) records.set(record.icao24, record);
const rows = (snapshot.cohorts?.[key]?.items || []).map((item) => {
const record = records.get(item.icao24);
let lat = record?.lat;
let lon = record?.lon;
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
const carto = item.position
? Cesium.Cartographic.fromCartesian(item.position)
: null;
lat = carto ? Cesium.Math.toDegrees(carto.latitude) : null;
lon = carto ? Cesium.Math.toDegrees(carto.longitude) : null;
}
const row = {
...(record || {
id: item.id,
icao24: item.icao24,
callsign: item.callsign ?? null,
altitudeM: item.altitudeM ?? null,
speedMps: item.velocityMps ?? null,
aircraftClass: item.aircraftClass ?? null,
}),
lat,
lon,
};
// Rank the retained cohort at its original precision; display rounding
// must not turn nearby contacts into artificial ID-sorted ties.
if (Number.isFinite(item.distance))
distanceKm.set(row, item.distance / 1000);
return row;
});
return { key, rows };
});
const selection = await selectAnalystRows(groups, {
filters: spec.filters,
sortBy: spec.sortBy,
sortDir: spec.sortDir,
limit: Math.round(clampNumber(args.limit, 1, 50, 12)),
layerKeys: wanted,
distanceOf: (row) => distanceKm.get(row),
isCurrent,
});
if (!selection || !isCurrent()) return null;
const items = selection.items.map(compactAnalystItem);
const complete = wanted.every((key) => {
const cohort = snapshot.cohorts?.[key];
return Number.isFinite(cohort?.count) && cohort.complete !== false;
});
const genericLayers = new Map(
(result?.coverage?.layersQueried || []).map((layer) => [
layer.layerKey,
layer,
]),
);
const layersQueried = wanted.map((key) => {
const cohort = snapshot.cohorts?.[key] || {};
const generic = genericLayers.get(key) || {};
const sourceSnapshot = sourceSnapshotsById.get(key);
const returned = Number.isFinite(cohort.count)
? cohort.count
: cohort.items?.length || 0;
return {
layerKey: key,
...(generic.status ? { status: generic.status } : {}),
...(generic.note ? { note: generic.note } : {}),
...(generic.window ? { window: generic.window } : {}),
returned,
total: cohort.complete === false ? null : returned,
truncated: cohort.complete === false,
...(cohort.source ? { source: cohort.source } : {}),
...(sourceSnapshot
? {
feedState: sourceSnapshot.feedState,
source: sourceSnapshot.source,
lastUpdate: sourceSnapshot.lastUpdate,
enabled: sourceSnapshot.enabled,
error: sourceSnapshot.error,
}
: {}),
};
});
const coverage = {
layersQueried,
records: {
returned: selection.count,
total: complete ? selection.count : null,
truncated: !complete,
},
scope: `window:${radiusKm}km@${label}`,
followUp: false,
feedProvenance,
note: `Contacts panel snapshot${snapshot.evaluatedAt ? ` at ${new Date(snapshot.evaluatedAt).toISOString()}` : ''} — the same retained cohort supplies the card, speech, and follow-up memory${complete ? '' : '; the count is a lower bound because at least one layer reached its retained limit'}.`,
};
const answer = {
ok: true,
action: 'analyst_query',
count: selection.count,
complete,
scopeLabel: `within ${radiusKm} km of ${label}`,
listed: items.length,
items,
summary: selection.summary,
...(result?.partial
? { partial: true, unanswered: [...(result.unanswered || [])] }
: {}),
display: {
scope: `within ${radiusKm} km of ${label} (Contacts window)`,
},
coverage,
// (D) The answer always says whose window it is and which engine produced it.
window: {
engine: 'contacts-window',
centeredOn: label,
radiusKm,
flights: snapshot.cohorts?.flights?.count ?? 'unknown',
military: snapshot.cohorts?.military?.count ?? 'unknown',
aircraft:
Number.isFinite(snapshot.cohorts?.flights?.count) &&
Number.isFinite(snapshot.cohorts?.military?.count)
? snapshot.cohorts.flights.count + snapshot.cohorts.military.count
: 'unknown',
complete,
evaluatedAt: snapshot.evaluatedAt || null,
},
...(snapshot.contactsWindow
? { contactsWindow: { ...snapshot.contactsWindow } }
: {}),
feedProvenance,
feedState: feedProvenance.overall,
};
return {
answer,
memory: {
matched: selection.matched,
// Follow-up ranking belongs to this retained Contacts evaluation, not
// the camera's current position. Keep the accessor with its row owners.
distanceOf: (row) => distanceKm.get(row),
layerKeys: wanted,
coverage,
unanswered: [...(result?.unanswered || [])],
scopeLabel: answer.scopeLabel,
scopeDetail: answer.display.scope,
},
};
}
/**
* Radius within which a requested centre counts as the Contacts subject's own
* position. Generous enough to absorb the fix-vs-display offset between what
* the model read off a card and where the contact is now, tight enough that a
* neighbouring landmark is never mistaken for the subject.
*/
const SUBJECT_CENTER_TOLERANCE_KM = 1;
/**
* Is this requested centre the Contacts subject's own position?
*
* Measured as true ground distance. A lat/lon delta box was wrong in a way
* that hid at the equator and widened toward it: a degree of longitude is
* ~111 km at the equator and ~78 km at 45°, so a fixed 0.01° box spans a
* different real distance at every latitude, and its diagonal admitted centres
* ~1.5 km away — far enough to be a different place, close enough to slip
* through and get answered as the subject's window.
* @param {{lat: number, lon: number}} center Requested centre.
* @param {Cesium.Cartesian3} subjectPosition Subject's world position.
* @returns {boolean} True when the two are the same place.
*/
function centerMatchesSubject(center, subjectPosition) {
if (!Number.isFinite(center?.lat) || !Number.isFinite(center?.lon))
return false;
const carto = Cesium.Cartographic.fromCartesian(subjectPosition);
if (!carto) return false;
const subjectLat = Cesium.Math.toDegrees(carto.latitude);
const subjectLon = Cesium.Math.toDegrees(carto.longitude);
return (
greatCircleKm(
{ lat: subjectLat, lon: subjectLon },
{ lat: center.lat, lon: center.lon },
) <= SUBJECT_CENTER_TOLERANCE_KM
);
}
function shouldScanVisibleEntities(cameraHeightM) {
return cameraHeightM <= 100000;
}
function selectedEntityContext(dataManager) {
const record = getSelectedEntityContext({ dataManager });
if (!record) return null;
return summarizeContextRecord(record, { includeProperties: true });
}
function visibleEntityContexts(
viewer,
dataManager,
{ layerId = null, limit = 5, target = null } = {},
) {
const nearbyRecords = [];
const canvas = viewer.scene.canvas;
const width = canvas.clientWidth || canvas.width || 0;
const height = canvas.clientHeight || canvas.height || 0;
const centerX = width / 2;
const centerY = height / 2;
const targetLat = target ? Cesium.Math.toDegrees(target.latitude) : null;
const targetLon = target ? Cesium.Math.toDegrees(target.longitude) : null;
const store = getContextStore();
const enabledLayerIds = new Set(
dataManager
.getAll()
.filter((layer) => layer.enabled)
.map((layer) => layer.id),
);
for (const record of store.entities.values()) {
if (layerId && record.layerId !== layerId) continue;
if (record.layerId && !enabledLayerIds.has(record.layerId)) continue;
if (record.entity?.show === false || record.dataSource?.show === false)
continue;
if (!Number.isFinite(record.latitude) || !Number.isFinite(record.longitude))
continue;
insertNearestRecord(
nearbyRecords,
{
record,
distanceScore: target
? approximateCoordinateDistanceSq(
targetLat,
targetLon,
record.latitude,
record.longitude,
)
: 0,
},
VISIBLE_ENTITY_SHORTLIST,
);
}
const candidates = [];
for (const { record } of nearbyRecords) {
const position = record.entity?.__localBaseCartesian;
if (!position) continue;
const screen = Cesium.SceneTransforms.worldToWindowCoordinates(
viewer.scene,
position,
);
if (
!screen ||
screen.x < 0 ||
screen.y < 0 ||
screen.x > width ||
screen.y > height
)
continue;
const dx = screen.x - centerX;
const dy = screen.y - centerY;
candidates.push({
summary: summarizeContextRecord(record, { includeProperties: true }),
distancePx: Math.sqrt(dx * dx + dy * dy),
});
}
return candidates
.sort((a, b) => a.distancePx - b.distancePx)
.slice(0, limit)
.map((item) => item.summary);
}
function insertNearestRecord(records, candidate, limit) {
if (records.length < limit) {
records.push(candidate);
records.sort((a, b) => a.distanceScore - b.distanceScore);
return;
}
if (candidate.distanceScore >= records[records.length - 1].distanceScore)
return;
let low = 0;
let high = records.length;
while (low < high) {
const middle = (low + high) >> 1;
if (records[middle].distanceScore <= candidate.distanceScore)
low = middle + 1;
else high = middle;
}
records.splice(low, 0, candidate);
records.pop();
}
async function getSceneContext(
viewer,
styleManager,
dataManager,
viewTarget = null,
service = defaultGeospatial,
) {
const cartographic = Cesium.Cartographic.fromCartesian(
viewer.camera.positionWC,
);
const basemap = await getBasemapContext(viewer, viewTarget, service);
const enabledLayers = dataManager
.getAll()
.filter((layer) => layer.enabled)
.map((layer) => ({
id: layer.id,
name: layer.name,
count: layer.stats?.count || 0,
source: layer.source,
}));
return {
camera: {
latitude: Number(Cesium.Math.toDegrees(cartographic.latitude).toFixed(6)),
longitude: Number(
Cesium.Math.toDegrees(cartographic.longitude).toFixed(6),
),
heightM: Math.round(cartographic.height),
},
basemap,
style: styleManager.activeStyle || 'normal',
enabledLayers,
};
}
async function getBasemapContext(
viewer,
viewTarget = null,
service = defaultGeospatial,
) {
const { reverseGeocodeCache, nearbyPlacesCache } = cachesFor(service);
const target = viewTarget;
const samples = sampleViewportCartographics(viewer);
const cameraCartographic = Cesium.Cartographic.fromCartesian(
viewer.camera.positionWC,
);
const cameraHeightM = cameraCartographic.height;
const viewScale = classifyViewScale(cameraHeightM);
const cachedViewportPlaces = viewportPlacesFromCache(
samples,
cameraHeightM,
service,
);
const viewportPlacesPromise = cachedViewportPlaces
? Promise.resolve(cachedViewportPlaces)
: reverseGeocodeViewportSamples(samples, cameraHeightM, service);
if (!target) {
const viewportPlaces = await resolveWithin(
viewportPlacesPromise,
BASEMAP_CONTEXT_WAIT_MS,
cachedViewportPlaces,
);
return {
source: 'Google Photorealistic 3D Tiles / Cesium basemap',
hasGoogle3DTiles: Boolean(window.__godsEyeView?.tileset),
viewScale,
viewportSamples: samples,
viewportPlaces,
target: null,
place: null,
};
}
const latitude = Number(Cesium.Math.toDegrees(target.latitude).toFixed(6));
const longitude = Number(Cesium.Math.toDegrees(target.longitude).toFixed(6));
const inferredCountry = inferCountryFromSamples(samples);
const knownLandmarks = nearbyKnownLandmarks(
latitude,
longitude,
cameraHeightM,
);
const fallbackPlace = coarseBasemapPlace(
viewScale,
latitude,
longitude,
inferredCountry,
);
const cachedPlace = shouldReverseGeocode(cameraHeightM)
? reverseGeocodeCache.get(
reverseGeocodeKey(latitude, longitude, service),
) || null
: null;
const nearbyCacheKey = nearbyPlacesCacheKey(
latitude,
longitude,
cameraHeightM,
service,
);
const cachedNearbyPlaces =
shouldFetchNearbyPlaces(cameraHeightM) &&
nearbyPlacesCache.has(nearbyCacheKey)
? nearbyPlacesCache.get(nearbyCacheKey)
: null;
const placePromise =
shouldReverseGeocode(cameraHeightM) && !cachedPlace
? reverseGeocode(latitude, longitude, service)
: Promise.resolve(cachedPlace);
const nearbyPlacesPromise =
shouldFetchNearbyPlaces(cameraHeightM) && !cachedNearbyPlaces
? fetchNearbyPlaces(latitude, longitude, cameraHeightM, service)
: Promise.resolve(cachedNearbyPlaces);
const [viewportPlaces, resolvedPlace, resolvedNearbyPlaces] =
await Promise.all([
resolveWithin(
viewportPlacesPromise,
BASEMAP_CONTEXT_WAIT_MS,
cachedViewportPlaces,
),
resolveWithin(placePromise, BASEMAP_CONTEXT_WAIT_MS, cachedPlace),
resolveWithin(
nearbyPlacesPromise,
BASEMAP_CONTEXT_WAIT_MS,
cachedNearbyPlaces,
),
]);
const place = resolvedPlace || fallbackPlace;
const nearbyPlaces = resolvedNearbyPlaces || [];
return {
source: 'Google Photorealistic 3D Tiles / Cesium basemap',
hasGoogle3DTiles: Boolean(window.__godsEyeView?.tileset),
viewScale,
viewportSamples: samples,
viewportPlaces,
target: {
latitude,
longitude,
heightM: Math.round(target.height || 0),
},
knownLandmarks,
nearbyPlaces,
place,
};
}
function classifyViewScale(cameraHeightM) {
if (cameraHeightM > 12000000) return 'global';
if (cameraHeightM > 3000000) return 'continental';
if (cameraHeightM > 750000) return 'regional';
if (cameraHeightM > 100000) return 'metro';
if (cameraHeightM > 10000) return 'city';
return 'local';
}
function shouldReverseGeocode(cameraHeightM) {
return cameraHeightM <= 750000;
}
function shouldReverseGeocodeViewport(cameraHeightM) {
return cameraHeightM <= 3000000;
}
function shouldFetchNearbyPlaces(cameraHeightM) {
return cameraHeightM <= 25000;
}
function nearbyKnownLandmarks(latitude, longitude, cameraHeightM) {
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return [];
const maxDistanceKm =
cameraHeightM <= 5000
? 2
: cameraHeightM <= 50000
? 10
: cameraHeightM <= 250000
? 35
: 0;
if (maxDistanceKm <= 0) return [];
const matches = [];
for (const [cityId, city] of Object.entries(CITY_POIS)) {
for (let poiIndex = 0; poiIndex < city.pois.length; poiIndex++) {
const poi = city.pois[poiIndex];
const distanceKm = greatCircleKm({ lat: latitude, lon: longitude }, poi);
if (distanceKm > maxDistanceKm) continue;
matches.push({
name: poi.name,
cityId,
city: city.name,
latitude: poi.lat,
longitude: poi.lon,
distanceKm: Number(distanceKm.toFixed(3)),
});
}
}
return matches.sort((a, b) => a.distanceKm - b.distanceKm).slice(0, 5);
}
function coarseBasemapPlace(
viewScale,
latitude,
longitude,
inferredCountry = null,
) {
if (viewScale === 'global') {
return {
formattedAddress: 'Global Earth view',
locality: null,
region: null,
country: null,
precision: 'global',
note: 'Camera is too far out for a precise street or city label; do not infer a local place from the center point.',
};
}
return {
formattedAddress: inferredCountry?.country
? `${viewScale[0].toUpperCase()}${viewScale.slice(1)} basemap view over ${inferredCountry.country}`
: `${viewScale[0].toUpperCase()}${viewScale.slice(1)} basemap view centered near ${latitude.toFixed(2)}, ${longitude.toFixed(2)}`,
locality: null,
region: null,
country: inferredCountry?.country || null,
precision: viewScale,
confidence: inferredCountry?.confidence || null,
note: 'Camera altitude is high, so this is approximate basemap context rather than a precise address.',
};
}
function getViewTargetCartographic(viewer) {
const signature = cameraViewSignature(viewer);
const cached = viewTargetCache.get(viewer);
if (
cached?.signature === signature &&
performance.now() - cached.cachedAt < 2500
) {
return cached.target;
}
const position = getViewTargetCartesian(viewer);
// `fromCartesian` still returns undefined for a point too near the ellipsoid
// center to project; normalize that to the same "no target" null the callers
// already handle for a missed pick.
const target = position
? Cesium.Cartographic.fromCartesian(position) || null
: null;
viewTargetCache.set(viewer, {
signature,
target,
cachedAt: performance.now(),
});
return target;
}
function cameraViewSignature(viewer) {
const camera = viewer.camera;
const cartographic = camera.positionCartographic;
return [
Cesium.Math.toDegrees(cartographic.latitude).toFixed(5),
Cesium.Math.toDegrees(cartographic.longitude).toFixed(5),
Math.round(cartographic.height / 2),
camera.heading.toFixed(3),
camera.pitch.toFixed(3),
].join(':');
}
/**
* World position under the center of the viewport, or null when the view has no
* target. Each stage of the cascade is validated before it is accepted: a depth
* pick over empty sky can return a NaN or center-of-the-earth Cartesian, and
* converting one of those throws deep inside Cesium. A degenerate pick is a
* MISSED pick, so it falls through to the next stage rather than poisoning
* every caller downstream.
*/
function getViewTargetCartesian(viewer) {
const scene = viewer.scene;
const canvas = scene.canvas;
const width = canvas.clientWidth || canvas.width || 0;
const height = canvas.clientHeight || canvas.height || 0;
const center = new Cesium.Cartesian2(width / 2, height / 2);
let position = null;
if (scene.pickPositionSupported && typeof scene.pickPosition === 'function') {
try {
position = scene.pickPosition(center);
} catch {
position = null;
}
}
if (
!isPickedWorldPosition(position) &&
viewer.camera &&
typeof viewer.camera.pickEllipsoid === 'function'
) {
try {
position = viewer.camera.pickEllipsoid(center, Cesium.Ellipsoid.WGS84);
} catch {
position = null;
}
}
if (
!isPickedWorldPosition(position) &&
viewer.camera &&
typeof viewer.camera.getPickRay === 'function'
) {
try {
const ray = viewer.camera.getPickRay(center);
position = scene.globe?.pick(ray, scene) || null;
} catch {
position = null;
}
}
return isPickedWorldPosition(position) ? position : null;
}
function sampleViewportCartographics(viewer) {
const scene = viewer.scene;
const canvas = scene.canvas;
const width = canvas.clientWidth || canvas.width || 0;
const height = canvas.clientHeight || canvas.height || 0;
if (!width || !height) return [];
const points = [
[0.5, 0.5],
[0.25, 0.35],
[0.75, 0.35],
[0.25, 0.65],
[0.75, 0.65],
[0.5, 0.25],
[0.5, 0.75],
];
const samples = [];
for (const [x, y] of points) {
const cartesian = viewer.camera.pickEllipsoid(
new Cesium.Cartesian2(width * x, height * y),
Cesium.Ellipsoid.WGS84,
);
if (!isPickedWorldPosition(cartesian)) continue;
const carto = Cesium.Cartographic.fromCartesian(cartesian);
if (!carto) continue;
samples.push({
latitude: Number(Cesium.Math.toDegrees(carto.latitude).toFixed(4)),
longitude: Number(Cesium.Math.toDegrees(carto.longitude).toFixed(4)),
});
}
return samples;
}
function inferCountryFromSamples(samples) {
if (!samples.length) return null;
const counts = new Map();
for (const sample of samples) {
const country = inferCountry(sample.latitude, sample.longitude);
if (!country) continue;
counts.set(country, (counts.get(country) || 0) + 1);
}
if (!counts.size) return null;
const [country, count] = [...counts.entries()].sort((a, b) => b[1] - a[1])[0];
return {
country,
confidence: Number((count / samples.length).toFixed(2)),
sampleCount: count,
totalSamples: samples.length,
};
}
function inferCountry(latitude, longitude) {
const regions = [
{ name: 'Iran', south: 24.0, north: 40.2, west: 44.0, east: 63.5 },
{ name: 'Iraq', south: 29.0, north: 37.5, west: 38.5, east: 49.0 },
{ name: 'Turkey', south: 35.5, north: 42.5, west: 25.5, east: 45.2 },
{ name: 'Saudi Arabia', south: 16.0, north: 32.5, west: 34.0, east: 56.5 },
{ name: 'Afghanistan', south: 29.0, north: 38.8, west: 60.0, east: 75.5 },
{ name: 'Pakistan', south: 23.0, north: 37.2, west: 60.5, east: 77.5 },
{ name: 'Turkmenistan', south: 35.0, north: 42.9, west: 52.0, east: 66.8 },
{ name: 'Azerbaijan', south: 38.3, north: 41.9, west: 44.6, east: 50.8 },
{ name: 'Armenia', south: 38.7, north: 41.4, west: 43.4, east: 46.7 },
{ name: 'Japan', south: 24.0, north: 46.5, west: 122.0, east: 146.5 },
{ name: 'South Korea', south: 33.0, north: 38.8, west: 124.0, east: 132.0 },
{ name: 'North Korea', south: 37.5, north: 43.2, west: 124.0, east: 131.0 },
{ name: 'China', south: 18.0, north: 53.8, west: 73.0, east: 135.2 },
{ name: 'Russia', south: 41.0, north: 82.0, west: 19.0, east: 180.0 },
{
name: 'United States',
south: 24.0,
north: 49.8,
west: -125.0,
east: -66.0,
},
];
const region = regions.find(
(item) =>
latitude >= item.south &&
latitude <= item.north &&
longitude >= item.west &&
longitude <= item.east,
);
return region?.name || null;
}
async function reverseGeocode(latitude, longitude, service) {
const { reverseGeocodeCache, reverseGeocodeInFlight } = cachesFor(service);
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return null;
const key = reverseGeocodeKey(latitude, longitude, service);
if (reverseGeocodeCache.has(key)) return reverseGeocodeCache.get(key);
if (reverseGeocodeInFlight.has(key)) return reverseGeocodeInFlight.get(key);
const request = (async () => {
try {
const place =
(await service.reverseGeocode?.(latitude, longitude)) || null;
if (place) {
reverseGeocodeCache.set(key, place);
if (reverseGeocodeCache.size > 256)
reverseGeocodeCache.delete(reverseGeocodeCache.keys().next().value);
}
return place;
} catch {
return null;
} finally {
reverseGeocodeInFlight.delete(key);
}
})();
reverseGeocodeInFlight.set(key, request);
return request;
}
async function reverseGeocodeViewportSamples(samples, cameraHeightM, service) {
if (!shouldReverseGeocodeViewport(cameraHeightM) || !samples.length)
return null;
// At building scale, center geocoding plus Nearby Places is more precise and
// avoids three redundant Google requests.
if (cameraHeightM <= 10000) return null;
const prioritySamples = [samples[0], samples[1], samples[2]].filter(Boolean);
const places = (
await Promise.all(
prioritySamples.map(async (sample) => {
const place = await reverseGeocode(
sample.latitude,
sample.longitude,
service,
);
if (!place) return null;
return {
latitude: sample.latitude,
longitude: sample.longitude,
formattedAddress: place.formattedAddress,
locality: place.locality,
region: place.region,
country: place.country,
types: place.types,
labels: place.labels,
streetLabels: place.streetLabels,
};
}),
)
).filter(Boolean);
return summarizeViewportPlaces(places);
}
function viewportPlacesFromCache(samples, cameraHeightM, service) {
const { reverseGeocodeCache } = cachesFor(service);
if (
!shouldReverseGeocodeViewport(cameraHeightM) ||
cameraHeightM <= 10000 ||
!samples.length
)
return null;
const places = [samples[0], samples[1], samples[2]]
.filter(Boolean)
.flatMap((sample) => {
const place = reverseGeocodeCache.get(
reverseGeocodeKey(sample.latitude, sample.longitude, service),
);
if (!place) return [];
return [
{
latitude: sample.latitude,
longitude: sample.longitude,
formattedAddress: place.formattedAddress,
locality: place.locality,
region: place.region,
country: place.country,
types: place.types,
labels: place.labels,
streetLabels: place.streetLabels,
},
];
});
return summarizeViewportPlaces(places);
}
function summarizeViewportPlaces(places) {
if (!places.length) return null;
return {
samples: places,
dominantCountry: dominantValue(
places.map((place) => place.country).filter(Boolean),
),
dominantRegion: dominantValue(
places.map((place) => place.region).filter(Boolean),
),
dominantLocality: dominantValue(
places.map((place) => place.locality).filter(Boolean),
),
visibleLabels: uniqueStrings(
places.flatMap((place) => place.labels || []),
).slice(0, 24),
streetLabels: uniqueStrings(
places.flatMap((place) => place.streetLabels || []),
).slice(0, 20),
};
}
async function fetchNearbyPlaces(latitude, longitude, cameraHeightM, service) {
const { nearbyPlacesCache, nearbyPlacesInFlight } = cachesFor(service);
const radiusM = nearbyPlacesRadiusM(cameraHeightM);
const cacheKey = nearbyPlacesCacheKey(
latitude,
longitude,
cameraHeightM,
service,
);
if (nearbyPlacesCache.has(cacheKey)) return nearbyPlacesCache.get(cacheKey);
if (nearbyPlacesInFlight.has(cacheKey))
return nearbyPlacesInFlight.get(cacheKey);
const request = (async () => {
try {
const places = (
(await service.nearby?.({ latitude, longitude, radiusM })) || []
)
.filter((place) => place?.name)
.slice(0, 12);
nearbyPlacesCache.set(cacheKey, places);
if (nearbyPlacesCache.size > 256)
nearbyPlacesCache.delete(nearbyPlacesCache.keys().next().value);
return places;
} catch {
return [];
} finally {
nearbyPlacesInFlight.delete(cacheKey);
}
})();
nearbyPlacesInFlight.set(cacheKey, request);
return request;
}
function uniqueStrings(values) {
return [
...new Set(values.map((value) => sanitizeLabel(value)).filter(Boolean)),
];
}
/**
* Normalize a feed-sourced label (place/street/POI name from OSM, geocoding,
* Google Places, etc.) before it enters the voice LLM's scene context.
* Collapses newlines/control chars to single spaces and hard-caps length, so
* crafted map data can't smuggle multi-line "instructions" into the prompt.
* Defense-in-depth — these are reference labels, not commands.
* @param {*} value - Raw label value.
* @returns {string} Sanitized single-line label (max 120 chars).
*/
function sanitizeLabel(value) {
const text = String(value || '');
let out = '';
for (const ch of text) {
const code = ch.codePointAt(0);
out += code < 0x20 || code === 0x7f ? ' ' : ch; // drop control chars incl. newlines
}
return out.replace(/\s+/g, ' ').trim().slice(0, 120);
}
function cachePrecision(service) {
return Number.isInteger(service?.cachePrecision)
? Math.max(3, Math.min(6, service.cachePrecision))
: 4;
}
function reverseGeocodeKey(latitude, longitude, service) {
return `${latitude.toFixed(cachePrecision(service))},${longitude.toFixed(cachePrecision(service))}`;
}
function nearbyPlacesCacheKey(latitude, longitude, cameraHeightM, service) {
const radiusM = nearbyPlacesRadiusM(cameraHeightM);
return `${latitude.toFixed(cachePrecision(service))},${longitude.toFixed(cachePrecision(service))},${radiusM}`;
}
function nearbyPlacesRadiusM(cameraHeightM) {
if (cameraHeightM <= 1000) return 500;
if (cameraHeightM <= 5000) return 2000;
return 5000;
}
async function resolveWithin(promise, timeoutMs, fallback) {
let timeout = null;
try {
return await Promise.race([
Promise.resolve(promise).catch(() => fallback),
new Promise((resolve) => {
timeout = window.setTimeout(() => resolve(fallback), timeoutMs);
}),
]);
} finally {
if (timeout) window.clearTimeout(timeout);
}
}
function approximateCoordinateDistanceSq(latA, lonA, latB, lonB) {
const latDelta = latB - latA;
const lonDelta =
(lonB - lonA) * Math.cos(Cesium.Math.toRadians((latA + latB) / 2));
return latDelta * latDelta + lonDelta * lonDelta;
}
function logSlowContext(startedAt, scope) {
const durationMs = Math.round(performance.now() - startedAt);
if (durationMs >= 500) {
console.info(
`[GEV Voice] ${scope} scene context completed in ${durationMs}ms`,
);
}
}
function dominantValue(values) {
if (!values.length) return null;
const counts = new Map();
for (const value of values) counts.set(value, (counts.get(value) || 0) + 1);
const [value, count] = [...counts.entries()].sort((a, b) => b[1] - a[1])[0];
return {
value,
count,
confidence: Number((count / values.length).toFixed(2)),
};
}
function summarizeContextRecord(record, { includeProperties = false } = {}) {
return {
id: String(record.id || ''),
name:
cleanText(record.label || record.properties?.name) ||
layerTitle(record.layerId),
layerId: record.layerId || null,
layerName: record.layerName || layerTitle(record.layerId),
source: record.source || null,
latitude: record.latitude ?? null,
longitude: record.longitude ?? null,
properties: includeProperties
? compactProperties(record.properties || {})
: undefined,
active: isContextRecordActive(record),
};
}
function compactProperties(props) {
const preferredKeys = [
'name',
'operator',
'owner',
'brand',
'addr:city',
'addr:state',
'country',
'capacity',
'output',
'osm_id',
'source',
];
const flat = {
...props,
...(props.tags && typeof props.tags === 'object' ? props.tags : {}),
};
const result = {};
for (const key of preferredKeys) {
const value = cleanText(flat[key]);
if (value) result[key] = value;
}
for (const [key, value] of Object.entries(flat)) {
if (Object.keys(result).length >= 12) break;
if (key === 'tags' || result[key] !== undefined) continue;
const text = cleanText(value);
if (text) result[key] = text;
}
return result;
}
function cleanText(value) {
if (value == null || typeof value === 'object') return '';
const text = String(value).trim();
if (!text || text === 'undefined' || text === 'null') return '';
return text.length > 180 ? `${text.slice(0, 177)}...` : text;
}
function layerTitle(layerId) {
if (layerId === 'local-datacenters') return 'Datacenter';
if (layerId === 'local-dams') return 'Dam';
if (layerId === 'telegeography-submarine-cables') return 'Submarine Cable';
if (layerId === 'local-firms') return 'Active Fire';
return layerId || 'Entity';
}
function clampNumber(value, min, max, fallback) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) return fallback;
return Math.max(min, Math.min(max, numeric));
}
/**
* Analyst query — spoken questions over data already on the client
* ("how many flights over Texas?", "biggest fire near LA?", "which ships
* are headed to Oakland?"). The ENGINE (analystEngine.js) does the query
* logic; this wiring supplies live providers and compacts the result for
* the voice payload. One engine per runner keeps follow-up memory
* ("which of those is closest?") scoped to the session.
*/
/**
* Most records one layer hands a single query. Above every loaded feed seen
* so far (flights ~8k, vessels ~12k, a 24 h global FIRMS day ~160k), so
* answers count the whole loaded set; a feed past it reports
* returned/total/truncated rather than a silently short count.
*/
const ANALYST_RECORD_CAP = 250_000;
/** A voice-enabled layer is still filling its first poll for this long. */
const ANALYST_WARMUP_MS = 45_000;
function layerIsWarming(enabledAt, layerKey) {
const at = enabledAt.get(layerKey);
return Boolean(at && Date.now() - at < ANALYST_WARMUP_MS);
}
function analystProviders(
viewer,
dataManager,
{
recordLimitByLayer = null,
placeSearch = unavailablePlaceSearch,
resolveRegionRing = (name) =>
defaultAnnotationResolver.resolveRegionRingForQuery(
name,
undefined,
placeSearch,
),
isWarming = () => false,
} = {},
) {
// Per-layer truncation seen by the last getRecords call.
const truncatedBy = new Map();
return {
getRecords(layerKey) {
truncatedBy.delete(layerKey);
const layer = dataManager.layers.get(layerKey);
if (!layer || !dataManager.isEnabled(layerKey)) return [];
const mod = layer.module;
if (typeof mod?.getAnalystRecords !== 'function') return [];
const requestedLimit = recordLimitByLayer?.[layerKey];
const cap = Number.isFinite(requestedLimit)
? requestedLimit
: ANALYST_RECORD_CAP;
// One past the cap proves whether anything was left out.
const rows = mod.getAnalystRecords(Math.min(cap + 1, 2 ** 31)) || [];
if (rows.length > cap) {
truncatedBy.set(layerKey, true);
return rows.slice(0, cap);
}
return rows;
},
getLayerSnapshot(layerKey) {
const row = dataManager.getAll?.().find((layer) => layer.id === layerKey);
if (row) return layerSnapshot(row);
const module = dataManager.layers?.get(layerKey)?.module;
return layerSnapshot({
id: layerKey,
enabled: dataManager.isEnabled?.(layerKey),
stats: module?.getStats?.() || {},
});
},
getRecordCoverage(layerKey, rows) {
if (!truncatedBy.get(layerKey)) return { total: rows.length };
const loaded = dataManager.layers
.get(layerKey)
?.module?.getStats?.().count;
return {
total: Number.isFinite(loaded) && loaded > rows.length ? loaded : null,
truncated: true,
};
},
isWarming,
/** The layer's own status text for an empty result, e.g. "Fly below 400 km". */
getEmptyNote(layerKey) {
const stats =
dataManager.layers.get(layerKey)?.module?.getStats?.() || {};
const note = [stats.coverage, stats.statusMessage].find(
(value) => typeof value === 'string' && value.trim(),
);
return note ? note.trim() : null;
},
resolveRegionRing,
/**
* The active Contacts subject, when there is one — the centre the operator
* is reasoning about while Contacts is up. Null whenever Contacts is off,
* so view-centred behaviour is unchanged outside it.
* @returns {{lat: number, lon: number, label: string|null}|null} Subject centre.
*/
getContextSubject() {
const snapshot = dataManager?.layers
?.get('military-awareness')
?.module?.getContextSnapshot?.();
const subject = snapshot?.subject;
if (!subject?.position) return null;
const carto = Cesium.Cartographic.fromCartesian(subject.position);
if (!carto) return null;
return {
lat: Cesium.Math.toDegrees(carto.latitude),
lon: Cesium.Math.toDegrees(carto.longitude),
label: subject.label || subject.id || null,
};
},
getViewContext() {
const carto = viewer.camera.positionCartographic;
const altKm = carto.height / 1000;
// View radius scales with altitude: street-level asks stay local,
// country-level asks sweep wide. Clamped so "in view" is never absurd.
const viewRadiusKm = Math.max(25, Math.min(2500, altKm * 1.6));
return {
lat: Cesium.Math.toDegrees(carto.latitude),
lon: Cesium.Math.toDegrees(carto.longitude),
viewRadiusKm,
};
},
};
}
/** Identity fields every compact analyst item keeps when present. */
const ANALYST_IDENTITY_FIELDS = Object.freeze([
'icao24',
'mmsi',
'registration',
'label',
'callsign',
'name',
'noradId',
]);
/**
* Compact one analyst record for the voice payload: identity, position (so a
* follow-up can fly to it), distance, and that layer's manifest fields with
* times as ISO text. The full record set stays engine-side for follow-ups.
*
* `icao24`/`mmsi` ride along because the tool instructions tell the model to
* hand this result straight to track_entity, and `id` is a DISPLAY label
* (callsign, else registration, else hex). A callsign-less contact therefore
* handed track_entity a tail number the lookup could not resolve, and the
* model burned the turn on retries (owner field session 2026-08-21, 23:48).
* @param {object} r Engine record.
* @returns {object} Compact item.
*/
function compactAnalystItem(r) {
const compact = { layerKey: r.layerKey, id: r.id };
const present = (value) => value !== null && value !== undefined;
for (const key of ANALYST_IDENTITY_FIELDS)
if (present(r[key])) compact[key] = r[key];
if (Number.isFinite(r.lat) && Number.isFinite(r.lon)) {
compact.lat = Math.round(r.lat * 1e4) / 1e4;
compact.lon = Math.round(r.lon * 1e4) / 1e4;
}
if (present(r.distanceKm)) compact.distanceKm = r.distanceKm;
const fields = voiceLayer(r.layerKey)?.query?.fields || {};
for (const [key, spec] of Object.entries(fields)) {
if (!present(r[key]) || key in compact) continue;
if (spec.type === 'time') {
const ms = Number(r[key]);
// A finite value can still be past Date's range, where toISOString throws.
const time = new Date(ms);
if (Number.isFinite(time.getTime())) {
if (key === 'lastContactAtMs') compact.lastContactAtMs = ms;
else compact.time = time.toISOString();
}
} else compact[key] = r[key];
}
if (present(r.ageHours)) compact.ageHours = r.ageHours;
return compact;
}
/** Refusal fields the analyst engine owns and the model must receive. */
const ANALYST_REFUSAL_FIELDS = Object.freeze([
'allowed',
'field',
'expected',
'layers',
'layerStatus',
'cancelled',
]);
const VIEWPORT_COVERAGE_NOTE = 'the flights layer loads by viewport';
/** Resolve omitted follow-up scope from the engine's retained coverage. */
function effectiveAnalystScopeKind(args, coverage) {
const explicit = String(args.scope?.kind || '').toLowerCase();
if (explicit) return explicit;
const retained = String(coverage?.scope || '')
.split(':', 1)[0]
.toLowerCase();
if (retained === 'window') return 'radius';
if (['anywhere', 'radius', 'region', 'view'].includes(retained))
return retained;
return args.followUp ? 'anywhere' : 'view';
}
/** Add the viewport qualification without replacing retained provenance. */
function withViewportCoverageNote(coverage) {
if (!coverage) return coverage;
const current = typeof coverage.note === 'string' ? coverage.note : '';
if (current.includes(VIEWPORT_COVERAGE_NOTE)) return coverage;
const separator = current ? '; ' : '';
return {
...coverage,
note: current
? `${current}${separator}${VIEWPORT_COVERAGE_NOTE}`
: VIEWPORT_COVERAGE_NOTE,
};
}
/** Whether this query counts records from a viewport-loaded layer. */
function isViewportScopedAnalystResult(args, coverage) {
const scopeKind = effectiveAnalystScopeKind(args, coverage);
return (
(scopeKind === 'radius' || scopeKind === 'view') &&
(coverage?.layersQueried || []).some(
(layer) => voiceLayer(layer.layerKey)?.query?.viewportLoaded,
)
);
}
async function runAnalystQuery(
analystEngine,
dataManager,
args = {},
_layerEnabledAt,
isCurrent = () => true,
signal = undefined,
) {
let entityWindow = null;
const result = await analystEngine.query(
{
layers: Array.isArray(args.layers) ? args.layers : undefined,
scope: args.scope,
filters: Array.isArray(args.filters) ? args.filters : [],
sortBy: args.sortBy || null,
sortDir: args.sortDir,
limit: args.limit,
followUp: Boolean(args.followUp),
},
{
isCurrent,
signal,
beforeCommit: async (candidate) => {
const projected = await aircraftProximityWindowForQuery(
dataManager,
args,
candidate,
isCurrent,
);
if (
projected?.answer &&
projected?.memory &&
isViewportScopedAnalystResult(args, projected.answer.coverage)
) {
const coverage = withViewportCoverageNote(projected.answer.coverage);
projected.answer.coverage = coverage;
projected.memory.coverage = coverage;
}
entityWindow = projected?.answer || null;
return projected?.memory || null;
},
},
);
if (!result.ok) {
const refusal = {
ok: false,
action: 'analyst_query',
code: result.code || null,
error: result.error,
};
// The engine's semantic fields pass through unchanged: a cancellation
// stays a cancellation.
for (const key of ANALYST_REFUSAL_FIELDS)
if (result[key] !== undefined) refusal[key] = result[key];
if (result.coverage) refusal.coverage = result.coverage;
return refusal;
}
const items = result.items.map(compactAnalystItem);
// Warm-up honesty: a layer enabled seconds ago hasn't finished its first
// poll (entity layers render one interval behind live BY DESIGN) — so a low
// count is "still loading", not fact.
const warming = (result.coverage?.layersQueried || [])
.filter((l) => layerIsWarming(_layerEnabledAt, l.layerKey))
.map((l) => l.layerKey);
if (warming.length) {
const note = `${warming.join(', ')} just turned on — still loading, counts will rise`;
result.display.caveat = result.display.caveat
? `${note}; ${result.display.caveat}`
: note;
}
// A radius/view count over a viewport-loaded layer counts what is LOADED, and
// the flights layer reloads as the camera moves — so this number can sit well
// under the Contacts cohort without either being wrong.
const scopeKind = effectiveAnalystScopeKind(args, result.coverage);
const viewportScoped = isViewportScopedAnalystResult(args, result.coverage);
if (viewportScoped && result.coverage) {
// The engine's follow-up memory retains this same object, so update its
// note in place after deriving the qualified copy.
result.coverage.note = withViewportCoverageNote(result.coverage).note;
}
// ENTITY-CENTRED NEARBY: answered by the SAME engine that fills the Contacts
// panel, so the spoken number and the panel readout for one centre cannot
// differ. The generic record/scope engine still owns explicit regions and
// arbitrary points — only "how many aircraft around <this contact>" is
// unified, because that is the question the panel is already answering.
if (entityWindow) {
return entityWindow;
}
const contactsWindow = activeContactsWindow(dataManager);
const aircraftQueried = (result.coverage?.layersQueried || []).some(
(l) => l.layerKey === 'flights' || l.layerKey === 'military',
);
// Both numbers, and which one answers the question. The window counts have
// ridden along in `contactsWindow` for a while, and the owner's trial showed
// that is not enough on its own: with Contacts active and a DATACENTER in
// the selection slot, the model centred a radius on the datacenter, answered
// 15, and then explained away the 111 sitting in the same payload ("that
// number is from the Contacts window, and I wasn't using Contacts as the
// source"). So the payload now states the relationship instead of leaving it
// to be inferred from two bare numbers.
const windowAircraft = Number.isFinite(contactsWindow?.aircraft)
? contactsWindow.aircraft
: null;
const proximityScoped = scopeKind === 'radius' || scopeKind === 'view';
const countsReconciliation =
contactsWindow &&
aircraftQueried &&
proximityScoped &&
windowAircraft !== null
? `Contacts is ACTIVE: its window holds ${windowAircraft} aircraft within ` +
`${contactsWindow.radiusKm} km of ${contactsWindow.centeredOn}, and that is the answer to a bare ` +
`"how many aircraft are nearby". This query measured something else — ${result.count} ${result.scopeLabel}. ` +
'Give this one only if the operator asked about that specific area, and name both scopes if you give both.'
: null;
return {
ok: true,
action: 'analyst_query',
count: result.count,
// False when a layer was capped: the count is a floor ("at least").
complete: result.complete !== false,
...(result.partial
? { partial: true, unanswered: result.unanswered || [] }
: {}),
...(result.centeredOn ? { centeredOn: result.centeredOn } : {}),
// Every count names its scope in words; a bare number is what made two
// honest answers look like a contradiction.
scopeLabel: result.scopeLabel,
// More matched than listed; the count above is still complete.
listed: items.length,
items,
summary: result.summary,
// On-screen detail (scope, feed window, caveats); not for recital.
display: result.display,
coverage: result.coverage,
feedProvenance: result.coverage?.feedProvenance || null,
feedState: result.coverage?.feedProvenance?.overall || null,
// The panel's own numbers, carried so the answer can match what the
// operator is looking at regardless of how the model reads the note.
// Flattened alongside the object so the count and its subject cannot be
// missed inside a nested shape.
...(contactsWindow && aircraftQueried
? {
contactsWindow,
contactsWindowCount: windowAircraft,
contactsWindowSubject: contactsWindow.centeredOn || null,
}
: {}),
...(countsReconciliation ? { countsReconciliation } : {}),
};
}