import * as Cesium from 'cesium'; import { feedProvenanceEnvelope, layerSnapshot, layerSnapshots, } from '../data/layerSnapshot.js'; import { contextModeWord } from '../contextModePolicy.js'; export const TRACKABLE_FAMILIES = Object.freeze([ { layerId: 'flights', kind: 'aircraft' }, { layerId: 'military', kind: 'aircraft' }, { layerId: 'ais-live-vessels', kind: 'vessel' }, { layerId: 'satellites', kind: 'satellite' }, ]); const CONTEXT_MODE_RESULT_FIELDS = Object.freeze([ { field: 'mode', emptyAs: 'off' }, { field: 'entering', emptyAs: null }, { field: 'priorMode', emptyAs: null }, ]); const NESTED_CONTEXT_RESULT_FIELDS = Object.freeze([ 'context', 'contextRollback', ]); /** * Translate context-mode payloads into the public tool vocabulary while * retaining the internal id beside each translated field. */ export function withContextModeVocabulary(state) { if (!state || typeof state !== 'object') return state; let out = state; const mutable = () => { if (out === state) out = { ...state }; return out; }; for (const { field, emptyAs } of CONTEXT_MODE_RESULT_FIELDS) { if (!(field in state)) continue; const internal = state[field] ?? null; const target = mutable(); target[field] = contextModeWord(internal, { emptyAs }); target[`${field}Internal`] = internal; } for (const field of NESTED_CONTEXT_RESULT_FIELDS) { const nested = state[field]; if (!nested || typeof nested !== 'object') continue; const translated = withContextModeVocabulary(nested); if (translated !== nested) mutable()[field] = translated; } return out; } /** Read tracked entities from their canonical layer owners. */ export function collectTrackedEntities(dataManager) { const tracked = []; for (const family of TRACKABLE_FAMILIES) { const module = dataManager?.layers?.get(family.layerId)?.module; if (!module) continue; try { const info = family.kind === 'vessel' ? module.getSelectedInfo?.() : module.getTrackedInfo?.(); if (info) tracked.push({ kind: family.kind, layerId: family.layerId, ...info, }); } catch { // A not-yet-ready layer contributes no tracked observation. } } return tracked; } /** Read the active Contacts window from its owning awareness layer. */ export function activeContactsWindow(dataManager) { try { const layer = dataManager?.layers?.get('military-awareness')?.module; return ( layer?.contactsWindowFromSnapshot?.(layer.getContextSnapshot?.()) ?? null ); } catch { return null; } } /** * Build the model-free fast World Packet from existing application owners. * * This performs no network requests and intentionally mirrors the historical * get_current_view_state payload so voice can delegate without behavior drift. */ export function getWorldPacket(options = {}) { const { viewer, styleManager, dataManager, sceneDirector = null, now: requestedNow, } = options ?? {}; // A default parameter only covers undefined; null or NaN would age every // feed against the epoch and report stale data as "just now". const now = Number.isFinite(requestedNow) ? requestedNow : Date.now(); if (!viewer?.camera?.positionWC) throw new TypeError('World Packet requires a viewer camera'); if (!dataManager || typeof dataManager.getAll !== 'function') throw new TypeError('World Packet requires a data manager'); const cartographic = Cesium.Cartographic.fromCartesian( viewer.camera.positionWC, ); // Read the roster once. Voice must not rewalk disabled layers: preserving the // current enabled-only view keeps the model's context budget bounded. const rows = dataManager.getAll(); const enabledRows = rows.filter((layer) => layer.enabled); const contactsWindow = activeContactsWindow(dataManager); const contextState = typeof styleManager?.getContextModeState === 'function' ? withContextModeVocabulary(styleManager.getContextModeState()) : null; return { ok: true, action: 'get_current_view_state', camera: { latitude: Cesium.Math.toDegrees(cartographic.latitude), longitude: Cesium.Math.toDegrees(cartographic.longitude), heightM: cartographic.height, }, style: styleManager?.activeStyle || 'normal', context: contextState ? { ...contextState, ...(contactsWindow ? { contactsWindow } : {}), } : null, cockpit: typeof styleManager?.getCockpitState === 'function' ? styleManager.getCockpitState() : null, controls: typeof styleManager?.getControlState === 'function' ? styleManager.getControlState() : null, scenePlayback: sceneDirector?.getPlaybackStatus?.() || null, tracked: collectTrackedEntities(dataManager), disabledLayerCount: rows.length - enabledRows.length, // Preserve get_current_view_state's existing compact enabled-only shape. layers: enabledRows.map((layer) => { const snapshot = layerSnapshot(layer, { now }); return { id: layer.id, name: layer.name, enabled: layer.enabled, count: layer.stats?.count || 0, error: layer.stats?.error || null, feedState: snapshot.feedState, source: snapshot.source, lastUpdate: snapshot.lastUpdate, }; }), feedProvenance: feedProvenanceEnvelope( layerSnapshots(rows, { now }).filter((layer) => layer.enabled), { now }, ), }; }