Files
gods-eye-view/src/data/pickRegistry.js
T
Bilawal Sidhu a19897ad15 feat(voice): voice core — honest layer reach, spoken summary with on-screen card, narration, point-and-ask
- 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.
2026-09-28 21:19:23 -05:00

103 lines
3.6 KiB
JavaScript

/**
* Shared pick-ownership registry for layers that install their own
* ScreenSpaceEventHandler click handlers.
*
* Problem solved: each entity layer (commercial flights, military flights)
* receives every LEFT_CLICK. When the user clicks a military aircraft while
* a commercial flight is tracked, the commercial handler used to classify
* the pick as "empty space" and clear tracking (with a camera flight) at the
* same moment the military handler started tracking — two competing camera
* commands. Layers now register a predicate so siblings can recognize picks
* that belong to someone else and leave them alone.
*/
/** @type {Map<string, (pickedId: string) => boolean>} layerId -> ownership predicate */
const _owners = new Map();
/**
* Resolves a scene.pick() result to a String pick id for the ownership scan.
*
* Layers use heterogeneous pick ids: flights/military/bikeshare/CCTV use
* strings, satellites use numeric NORAD catalog ids, live AIS vessels attach
* the vessel record OBJECT (identity = its `mmsi`), and entity picks surface
* the Cesium Entity (identity = its string `id`). Everything is coerced to a
* String so predicates match against one canonical form.
*
* @param {object|null|undefined} picked - Result of `scene.pick()`.
* @returns {string|null} Canonical pick id, or null when the pick carries none.
*/
export function resolvePickId(picked) {
if (!picked) return null;
const unwrap = (id) => {
if (id === null || id === undefined) return undefined;
if (typeof id === 'object') {
// AIS vessel record (id object with .mmsi) or Cesium Entity (.id string)
if (typeof id.mmsi === 'string' || typeof id.mmsi === 'number')
return id.mmsi;
if (typeof id.id === 'string' || typeof id.id === 'number') return id.id;
return undefined;
}
return id;
};
let id = unwrap(picked.id);
if (id === undefined) id = unwrap(picked.primitive?.id);
return typeof id === 'string' || typeof id === 'number' ? String(id) : null;
}
/**
* Registers a pick-ownership predicate for a layer.
* @param {string} layerId - Owning layer id (e.g. 'flights').
* @param {(pickedId: string) => boolean} predicate - Returns true when the
* picked primitive/entity id belongs to this layer.
* @returns {void}
*/
export function registerPickOwner(layerId, predicate) {
if (!layerId || typeof predicate !== 'function') return;
_owners.set(layerId, predicate);
}
/**
* Removes a layer's ownership predicate (call on disable/destroy).
* @param {string} layerId - Owning layer id.
* @returns {void}
*/
export function unregisterPickOwner(layerId) {
_owners.delete(layerId);
}
/**
* The registered layer that owns a picked id, or null.
* @param {string} pickedId - Canonical pick id from {@link resolvePickId}.
* @returns {string|null} Owning layer id.
*/
export function findPickOwner(pickedId) {
if (!pickedId) return null;
for (const [ownerId, predicate] of _owners) {
try {
if (predicate(pickedId)) return ownerId;
} catch {
// a broken predicate must never break picking
}
}
return null;
}
/**
* True when some OTHER registered layer owns the picked id.
* @param {string} layerId - The asking layer's id (excluded from the scan).
* @param {string} pickedId - Picked primitive/entity id.
* @returns {boolean}
*/
export function isOwnedByOtherLayer(layerId, pickedId) {
if (!pickedId) return false;
for (const [ownerId, predicate] of _owners) {
if (ownerId === layerId) continue;
try {
if (predicate(pickedId)) return true;
} catch {
// a broken predicate must never break click handling
}
}
return false;
}