mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
A default parameter only covers undefined, so now: null aged every feed against the epoch and reported stale data as just now. Co-Authored-By: Claude Opus 5.5 <[email protected]>
171 lines
5.4 KiB
JavaScript
171 lines
5.4 KiB
JavaScript
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 },
|
|
),
|
|
};
|
|
}
|