mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
- Voice layer manifest (src/voice/layerManifest.js) generates the layer enums, aliases and analyst field hints; a test fails when a catalog layer has no entry. - analyst_query validates fields, operators, units, values and scopes, counts up to 250,000 loaded records per layer, reports complete/partial/unanswered, tells LAYER_OFF / NOT_READY / FEED_UNAVAILABLE apart from zero, refuses mismatched follow-ups and never lets a superseded query replace follow-up memory. Seven more layers expose analyst records. - Tool results carry a code-built say line and a display card; the voice card in the mic control shows captions, plan steps and the result (src/voice/resultDisplay.js is the one adapter). Slow tools narrate their current step out of band; holding Space while the assistant talks cuts it off. Turn spans go to the debug log; caption transcription is metered; the log omits what was said unless GEV_VOICE_LOG_CONTENT=1. - Point and ask: the Space-down pointer snapshot resolves "this", "that one" and "around here" through 'pointer' targets, and "the second one" through referent:n from the last numbered list. - Prompt states each policy once (instructions + tools 48.5k -> 46.7k chars). - Voice benchmark harness (scripts/qa-voice-bench.mjs, scripts/voice-bench/) shares its phrase table and grading with scripts/qa-voice-routing.mjs.
129 lines
3.9 KiB
JavaScript
129 lines
3.9 KiB
JavaScript
import { getContextStore, isContextRecordActive } from './contextStore.js';
|
|
import { findPickOwner, resolvePickId } from './pickRegistry.js';
|
|
|
|
/**
|
|
* Resolve a `scene.pick()` result to the identity a person would name: which
|
|
* layer, which record, and its display label.
|
|
*
|
|
* Two lanes, in order:
|
|
* 1. Context-store carriers. Entity-backed layers (infrastructure GeoJSON,
|
|
* selected vessels, fires, installations) stamp `__gevContextId` on the
|
|
* picked object, and the store record already carries the label and
|
|
* position voice uses everywhere else.
|
|
* 2. Pick owners. Primitive-backed layers (aircraft, satellites, vessels)
|
|
* register an ownership predicate in `pickRegistry`; the owning layer's
|
|
* `findByQuery` turns the pick id into a live descriptor.
|
|
*
|
|
* Trails claim their pick ids only to make clicks inert, so they never
|
|
* resolve. Disabled layers never resolve: what the user cannot see is not
|
|
* what they are pointing at.
|
|
*/
|
|
|
|
const KIND_BY_LAYER = Object.freeze({
|
|
flights: 'aircraft',
|
|
military: 'aircraft',
|
|
'local-adsb': 'aircraft',
|
|
'ais-live-vessels': 'vessel',
|
|
satellites: 'satellite',
|
|
'local-firms': 'fire',
|
|
});
|
|
|
|
const IGNORED_OWNERS = new Set(['trails']);
|
|
|
|
function text(value) {
|
|
return String(value ?? '').trim();
|
|
}
|
|
|
|
function finite(value) {
|
|
if (value === null || value === undefined || value === '') return null;
|
|
const number = Number(value);
|
|
return Number.isFinite(number) ? number : null;
|
|
}
|
|
|
|
/** Kind of thing a layer holds, for deixis ("this aircraft", "this place"). */
|
|
export function pickedKindForLayer(layerId) {
|
|
return KIND_BY_LAYER[layerId] || 'feature';
|
|
}
|
|
|
|
function contextCarrierRecord(picked, dataManager) {
|
|
const store = getContextStore();
|
|
for (const carrier of [
|
|
picked?.id,
|
|
picked?.primitive?.id,
|
|
picked?.primitive,
|
|
]) {
|
|
const contextId =
|
|
carrier && typeof carrier === 'object' ? carrier.__gevContextId : null;
|
|
if (!contextId) continue;
|
|
const record = store.entities.get(contextId);
|
|
if (record && isContextRecordActive(record, dataManager)) return record;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function fromRecord(record) {
|
|
const layerId = record.layerId || null;
|
|
const id =
|
|
layerId === 'ais-live-vessels' && record.properties?.mmsi
|
|
? String(record.properties.mmsi)
|
|
: String(record.id);
|
|
return {
|
|
layerId,
|
|
id,
|
|
label:
|
|
text(record.label) ||
|
|
text(record.properties?.name) ||
|
|
text(record.layerName) ||
|
|
id,
|
|
kind: pickedKindForLayer(layerId),
|
|
lat: finite(record.latitude),
|
|
lon: finite(record.longitude),
|
|
};
|
|
}
|
|
|
|
function descriptorLabel(found, fallback) {
|
|
return (
|
|
text(found?.callsign) ||
|
|
text(found?.registration) ||
|
|
text(found?.name) ||
|
|
text(found?.icao24) ||
|
|
text(found?.mmsi) ||
|
|
text(found?.noradId) ||
|
|
fallback
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param {object|null} picked Result of `scene.pick()`.
|
|
* @param {object} [options]
|
|
* @param {object} [options.dataManager] Layer lifecycle (enabled state, modules).
|
|
* @returns {{layerId: string, id: string, label: string, kind: string, lat: number|null, lon: number|null, altitudeM?: number|null}|null}
|
|
*/
|
|
export function resolvePickedContext(picked, { dataManager = null } = {}) {
|
|
if (!picked) return null;
|
|
const record = contextCarrierRecord(picked, dataManager);
|
|
if (record) return fromRecord(record);
|
|
|
|
const pickId = resolvePickId(picked);
|
|
if (!pickId) return null;
|
|
const layerId = findPickOwner(pickId);
|
|
if (!layerId || IGNORED_OWNERS.has(layerId)) return null;
|
|
if (dataManager?.isEnabled && !dataManager.isEnabled(layerId)) return null;
|
|
const layer = dataManager?.layers?.get?.(layerId);
|
|
let found = null;
|
|
try {
|
|
found = layer?.module?.findByQuery?.(pickId) || null;
|
|
} catch {
|
|
found = null;
|
|
}
|
|
return {
|
|
layerId,
|
|
id: pickId,
|
|
label: descriptorLabel(found, text(layer?.name) || pickId),
|
|
kind: pickedKindForLayer(layerId),
|
|
lat: finite(found?.latitude),
|
|
lon: finite(found?.longitude),
|
|
altitudeM: finite(found?.altitudeM),
|
|
};
|
|
}
|