Files
gods-eye-view/src/layers/awareness/controls.js
T

127 lines
3.8 KiB
JavaScript

import { AWARENESS_RADIUS_M } from '../../data/militaryAwarenessEngine.js';
export function createControls({ state: layerState, services, parts, source }) {
const flightsLayer = services.flights;
const militaryFlightsLayer = services.military;
const aisLiveVesselsLayer = services.vessels;
const methods = {
id: 'military-awareness',
name: 'Global Context',
icon: '◎',
source: 'Open-source proximity context',
// Context is entered from its dedicated right rail, not as a raw layer.
showInTogglePanel: false,
updateInterval: 0,
statsRefreshInterval: 1000,
attachDataManager(dataManager) {
layerState.dataManager = dataManager;
},
setParams(params = {}) {
if (typeof params.passive !== 'boolean') return;
const wasPassive = layerState.passive;
layerState.passive = params.passive;
if (layerState.enabled && wasPassive && !layerState.passive)
parts.dependencies.activateOperationalContext();
},
/** @returns {{ passive: boolean }} Current runtime parameters. */
getParams() {
return { passive: layerState.passive };
},
getStats() {
return {
count: layerState.results ? 1 : 0,
lastUpdate: layerState.results?.evaluatedAt || null,
stale: false,
error: null,
status: layerState.enabled ? 'ready' : 'idle',
};
},
/** Return the latest read-only context result for compact HUD consumers. */
getContextSnapshot() {
if (!layerState.enabled || !layerState.subject) return null;
if (!layerState.results) {
return parts.model.buildAwarenessContextSnapshot(
{
subject: { ...layerState.subject },
evaluatedAt: null,
radiusM: AWARENESS_RADIUS_M,
cohorts: [],
},
parts.model.navigationState(),
{
subjectPresent: !layerState.subjectMissing,
},
);
}
return parts.model.buildAwarenessContextSnapshot(
layerState.results,
parts.model.navigationState(),
{
subjectPresent: !layerState.subjectMissing,
},
);
},
/**
* Release Contact-owned camera tracking without discarding the selected
* subject. Reset-to-globe uses this route so the normal Context FOCUS action
* can explicitly return to the same contact, while delayed activation work
* cannot silently reclaim the camera after the reset.
* @returns {boolean} Whether a Contact subject remains selected.
*/
releaseCameraOwnership({
preserveVesselSelection = false,
origin = 'programmatic',
} = {}) {
++layerState.activationId;
layerState.autoFocusAttempted = true;
layerState.autoFocusRetryPending = false;
const preservedSelectionKey =
parts.subject.subjectKey(layerState.subject) || 'camera-release';
layerState.pendingSelectionKey = preservedSelectionKey;
try {
flightsLayer.stopTracking?.({ origin });
militaryFlightsLayer.stopTracking?.({ origin });
if (!preserveVesselSelection) aisLiveVesselsLayer.clearSelection?.();
} finally {
if (layerState.pendingSelectionKey === preservedSelectionKey) {
layerState.pendingSelectionKey = null;
}
}
return Boolean(layerState.subject);
},
navigatePrevious(options = {}) {
return parts.history.navigateHistory(-1, options);
},
focusCurrent(options = {}) {
return parts.focus.focusCurrentSubject(options);
},
navigateNext(options = {}) {
return parts.history.navigateHistory(1, options);
},
/** Select a context target through its owning layer's established tracker. */
focusTarget(layerId, id, options = {}) {
return parts.focus.requestFocus(layerId, id, false, options);
},
};
return { methods };
}