Merge feat/voice-geometry onto main

Both stack parents (#837, #842) are on main, so the voice geometry branch
now builds on main directly. Conflict resolutions keep main's behaviour and
add the geometry work on top: osm_query gating on every provider, restored
admin identity for Nominatim outlines, merged analyst follow-up memory, and
the cancelled Globe View reset instruction ahead of the generic cancellation.
This commit is contained in:
Manjunath committed 2026-11-03 14:33:34 +05:30
commit ecaddd838b
102 files changed
+12925 -1006

No files matched your search

+216 -4
View File
@@ -29,6 +29,7 @@
* node scripts/qa-voice-routing.mjs --layer behavior --deixis-only --headful --shots qa-shots/point-and-ask
* node scripts/qa-voice-routing.mjs --layer deixis --budget 12 # pointer_context routing (model turns)
* node scripts/qa-voice-routing.mjs --layer deixis-live --url ... # in-app text turns (model turns)
* node scripts/qa-voice-routing.mjs --layer behavior --behavior geometry # areas, drawn counts, imagery
*
* House gotchas honored: camera.cancelFlight() before every teleport; puppeteer
* suites must run sequentially with other harnesses (SwiftShader saturation);
@@ -75,7 +76,7 @@ const LAYER = getOpt('--layer', 'all'); // routing | behavior | all | deixis | d
const TURN_BUDGET = Number(getOpt('--budget', '120'));
const PHRASES_PER_SESSION = Number(getOpt('--batch', '6'));
const ONLY = getOpt('--only', null); // substring filter on phrase text
const BEHAVIOR = getOpt('--behavior', 'all'); // all | outlines | acceptance
const BEHAVIOR = getOpt('--behavior', 'all'); // all | outlines | acceptance | geometry
const DEIXIS_ONLY = process.argv.includes('--deixis-only'); // behavior: point-and-ask block only
const HEADFUL = process.argv.includes('--headful'); // real GPU, visible window
const SHOT_DIR = getOpt('--shots', null); // point-and-ask evidence screenshots
@@ -164,7 +165,11 @@ async function mintSession() {
throw new Error(
`token mint returned no client secret: ${JSON.stringify(body).slice(0, 160)}`,
);
return { value, model };
// The tools this server offered (osm_query only with an operator Overpass).
const tools = new Set(
(body?.session?.tools || []).map((tool) => tool?.name).filter(Boolean),
);
return { value, model, tools };
}
/** One Realtime WS session that can run several text turns sequentially. */
@@ -371,8 +376,10 @@ async function runRoutingLayer() {
}
const batch = list.slice(i, i + PHRASES_PER_SESSION);
let session;
let offered = new Set();
try {
const { value, model } = await mintSession();
const { value, model, tools } = await mintSession();
offered = tools;
session = new RoutingSession(value, model, evidence);
await session.connect();
} catch (e) {
@@ -390,6 +397,14 @@ async function runRoutingLayer() {
skipped(`route: "${p.phrase}"`, 'turn budget exhausted');
continue;
}
const missing = (p.requires || []).filter((name) => !offered.has(name));
if (missing.length) {
skipped(
`route: "${p.phrase}"`,
`${missing.join(', ')} not offered (no configured Overpass)`,
);
continue;
}
turnsUsed += 1;
let calls = [];
try {
@@ -1361,7 +1376,11 @@ async function runBehaviorLayer() {
// The point-and-ask block skips the first-run launcher so its evidence
// screenshots show the map; the full run keeps the historical URL.
const behaviorUrl = new URL(APP_URL);
if (DEIXIS_ONLY || BEHAVIOR === 'acceptance')
if (
DEIXIS_ONLY ||
BEHAVIOR === 'acceptance' ||
BEHAVIOR === 'geometry'
)
behaviorUrl.searchParams.set('welcome', '0');
await page.goto(behaviorUrl.href, {
waitUntil: 'domcontentloaded',
@@ -1420,6 +1439,10 @@ async function runBehaviorLayer() {
await runOutlineAcceptance({ page, run, settle, overpassRequests });
return;
}
if (BEHAVIOR === 'geometry') {
await runGeometryScenarios({ page, run, settle, overpassRequests });
return;
}
// (1) fly_to_location lands near the target. Annotations follow IMMEDIATELY
// while the camera is local — the resolver's proximity gate rejects far
@@ -2340,6 +2363,8 @@ async function runBehaviorLayer() {
resolverLog,
packTimes,
});
// Voice geometry last: it moves the camera and turns layers off.
await runGeometryScenarios({ page, run, settle, overpassRequests });
const shotDir = path.join(ROOT, 'qa-shots');
fs.mkdirSync(shotDir, { recursive: true });
@@ -2483,6 +2508,193 @@ const OUTLINE_ASKS = [
{ entityKind: 'building', latitude: 30.27472, longitude: -97.74035 }, 'building'],
].map(([group, target, lat, lon, height, extra = {}, shot = null]) => ({ group, target, lat, lon, height, extra, shot }));
/**
* Voice geometry, runner-driven: reusable areas, drawn-area counts, a
* landmark area and recent imagery, all without a default Overpass request.
* Public Nominatim use is read from the server's daily count.
*/
async function runGeometryScenarios({ page, run, settle, overpassRequests }) {
const shotDir = path.join(ROOT, 'qa-shots', 'pr-geometry');
fs.mkdirSync(shotDir, { recursive: true });
const shot = (name) => page.screenshot({ path: path.join(shotDir, `${name}.png`) }).catch(() => {});
const nominatimStart = nominatimCount();
const overpassStart = overpassRequests.length;
const teleport = (lat, lon, height, pitch = -90) => page.evaluate(({ lat, lon, height, pitch }) => {
const { viewer } = window.__godsEyeView;
viewer.camera.cancelFlight();
viewer.camera.setView({
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: (lon * Math.PI) / 180, latitude: (lat * Math.PI) / 180, height,
}),
orientation: { heading: 0, pitch: (pitch * Math.PI) / 180, roll: 0 },
});
}, { lat, lon, height, pitch });
const timings = {};
// (G1) "Outline Bagmati, then count fires inside it".
await teleport(27.7, 85.4, 400_000);
await settle(1500);
let t0 = Date.now();
const bagmati = await run('resolve_area', { query: 'Bagmati province', draw: true, flyTo: false });
timings.bagmatiMs = Date.now() - t0;
const bagmatiRefused = [
'AREA_IDENTITY_MISMATCH',
'AREA_IDENTITY_UNVERIFIED',
].includes(bagmati?.code);
const bagmatiVerified =
bagmati?.ok === true &&
bagmati?.drawn === true &&
bagmati?.level === 'admin1' &&
bagmati?.name === 'Bagmati Province' &&
bagmati?.sourceIdentity === 'Bagmati Province';
report(
bagmatiVerified || bagmatiRefused,
'geometry: "outline Bagmati province" uses verified current identity or refuses honestly',
`areaId=${bagmati?.areaId} name=${bagmati?.name} sourceIdentity=${bagmati?.sourceIdentity} source=${bagmati?.source} km2=${bagmati?.areaKm2} ms=${timings.bagmatiMs} code=${bagmati?.code || ''} ${bagmati?.error || ''}`,
);
await settle(3000);
await shot('bagmati-outline');
t0 = Date.now();
let r = await run('resolve_area', { query: 'Bagmati province' });
report(
bagmatiVerified
? r?.ok === true &&
r?.cached === true &&
r?.ms < 300 &&
r?.sourceIdentity === 'Bagmati Province'
: bagmatiRefused &&
r?.ok === false &&
r?.code === bagmati?.code &&
!r?.areaId,
'geometry: a repeated Bagmati request reuses only verified identity, never refused geometry',
`ok=${r?.ok} code=${r?.code || ''} areaId=${r?.areaId || ''} sourceIdentity=${r?.sourceIdentity || ''} cached=${r?.cached} ms=${r?.ms} roundTrip=${Date.now() - t0}`,
);
// A city no bundled pack holds: the guarded Nominatim outline route.
t0 = Date.now();
const kathmandu = await run('resolve_area', { query: 'Kathmandu', draw: true, flyTo: false });
timings.kathmanduMs = Date.now() - t0;
report(kathmandu?.ok === true && kathmandu?.drawn === true && kathmandu?.source === 'osm' && kathmandu?.areaKm2 > 10 && kathmandu?.areaKm2 < 2000,
'geometry: "outline Kathmandu" comes from the guarded Nominatim outline',
`areaId=${kathmandu?.areaId} source=${kathmandu?.source} km2=${kathmandu?.areaKm2} parts=${kathmandu?.parts} ms=${timings.kathmanduMs} err=${kathmandu?.code || ''} ${kathmandu?.error || ''}`);
await settle(2500);
await shot('kathmandu-outline');
r = await run('resolve_area', { query: 'Georgia' });
report(r?.ok === false && r?.needsClarification === true && (r?.candidates || []).length >= 2,
'geometry: an ambiguous area asks with candidates instead of guessing',
`candidates=${(r?.candidates || []).map((c) => `${c.name}/${c.level}/${c.country || ''}`).join(', ')}`,
);
if (bagmatiVerified) {
await run('set_layer_visibility', {
layerId: 'local-firms',
enabled: true,
});
let fires = null;
for (let attempt = 0; attempt < 10; attempt += 1) {
fires = await run('analyst_query', {
layers: ['local-firms'],
scope: { kind: 'area', areaId: bagmati.areaId },
});
if (fires?.ok || !['NOT_READY', 'LAYER_OFF'].includes(fires?.code)) break;
await settle(4000);
}
if (fires?.code === 'FEED_UNAVAILABLE') {
skipped(
'geometry: "count fires inside it" counts inside the verified Bagmati area',
`fires feed unavailable: ${fires?.error}`,
);
} else {
report(
fires?.ok === true &&
Number.isFinite(fires?.count) &&
/Bagmati/i.test(fires?.scopeLabel || '') &&
fires?.coverage?.scope === `area:${bagmati.areaId}`,
'geometry: "count fires inside it" counts inside the verified Bagmati area',
`count=${fires?.count} scopeLabel="${fires?.scopeLabel}" scope=${fires?.coverage?.scope} err=${fires?.error || ''}`,
);
}
await run('set_layer_visibility', {
layerId: 'local-firms',
enabled: false,
});
} else {
skipped(
'geometry: fire count waits for a verified Bagmati area',
`boundary refused with ${bagmati?.code || 'unknown error'}`,
);
}
// (G2) "Count flights inside the area I drew": the draw tool's own spec.
await teleport(30.27, -97.74, 600_000);
await run('set_layer_visibility', { layerId: 'flights', enabled: true });
for (let attempt = 0; attempt < 12; attempt += 1) {
r = await run('analyst_query', { layers: ['flights'], scope: { kind: 'anywhere' } });
if (r?.ok && r.count > 0) break;
await settle(5000);
}
const drawnBox = [[-99, 29], [-96.5, 29], [-96.5, 31.5], [-99, 31.5], [-99, 29]];
await page.evaluate((ring) => window.__gevAnnotations.annotate([{ type: 'area', manual: true, ring, label: 'QA drawn box' }]), drawnBox);
r = await run('analyst_query', { layers: ['flights'], scope: { kind: 'drawn' } });
const loaded = await run('analyst_query', { layers: ['flights'], scope: { kind: 'anywhere' } });
report(r?.ok === true && r?.scopeLabel === 'inside QA drawn box' && Number.isFinite(r?.count) && r.count <= (loaded?.count ?? Infinity),
'geometry: "count flights inside the area I drew" counts inside the drawn outline',
`count=${r?.count} of ${loaded?.count} loaded scopeLabel="${r?.scopeLabel}" err=${r?.error || ''}`);
await settle(2500);
await shot('drawn-area-count');
// Thousands of aircraft starve a SwiftShader page; the landmark lookup
// below has a 10 s budget.
await run('set_layer_visibility', { layerId: 'flights', enabled: false });
await run('clear_annotations', {});
// (G3) "Draw an area around the Ferry Building": the enclosing mapped area
// (Nominatim grounds, then the OpenFreeMap area holding it), else an
// approximate buffer — either way a drawn, reusable area.
await teleport(37.7955, -122.3937, 2500, -60);
await settle(1500);
t0 = Date.now();
const ferry = await run('resolve_area', { query: 'Ferry Building, San Francisco', around: true, flyTo: false });
timings.ferryMs = Date.now() - t0;
const ferryNear = Array.isArray(ferry?.bbox) && Math.abs(ferry.bbox[1] - 37.795) < 0.05 && Math.abs(ferry.bbox[0] + 122.394) < 0.05;
report(ferry?.ok === true && ferry?.drawn === true && ferryNear && (ferry?.approximate === true || ['osm', 'openfreemap'].includes(ferry?.source)),
'geometry: "draw an area around the Ferry Building" draws a reusable landmark area',
`areaId=${ferry?.areaId} source=${ferry?.source} approximate=${ferry?.approximate} basis=${ferry?.display?.basis} km2=${ferry?.areaKm2} ms=${timings.ferryMs} err=${ferry?.error || ''}`);
await settle(3000);
await shot('ferry-building-area');
// (G4) "Find recent imagery of this area": the Recent Imagery catalog for
// that area, the best acquisition pinned through the layer itself.
t0 = Date.now();
r = await run('find_imagery', { areaId: ferry?.areaId || 'missing' });
timings.imageryMs = Date.now() - t0;
const pinned = await page.evaluate(() => window.__godsEyeView.dataManager.layers.get('recent-imagery')?.module?.getSnapshot?.().pins?.a?.key || null);
if (r?.code === 'CATALOG_UNAVAILABLE' || r?.code === 'SEARCH_TIMEOUT') {
skipped('geometry: "find recent imagery of this area" pins the best acquisition', `catalog: ${r?.error}`);
} else {
report(r?.ok === true && (r?.count === 0 ? r?.shown === null : Boolean(r?.shown?.date) && String(pinned).endsWith(`:${r.shown.date}`)),
'geometry: "find recent imagery of this area" pins the best acquisition',
`count=${r?.count} shown=${JSON.stringify(r?.shown)} pinned=${pinned} ms=${timings.imageryMs} err=${r?.error || ''}`);
}
await settle(4000);
await shot('recent-imagery-area');
await run('set_layer_visibility', { layerId: 'recent-imagery', enabled: false });
// (G5) Bulk place search without an operator Overpass: a plain refusal.
const configured = await page.evaluate(() => fetch('/api/overpass/status').then((res) => res.json()).catch(() => null));
r = await run('osm_query', { what: 'hospitals' });
if (configured?.configured) {
skipped('geometry: place search without an operator Overpass is refused', 'this server has OVERPASS_UPSTREAMS');
} else {
report(r?.ok === false && r?.code === 'OVERPASS_NOT_CONFIGURED',
'geometry: place search without an operator Overpass is refused', `code=${r?.code} error=${r?.error}`);
}
const nominatimUsed = nominatimCount() - nominatimStart;
const overpass = overpassRequests.slice(overpassStart);
console.log(` geometry timings: ${JSON.stringify(timings)}; public Nominatim requests: ${nominatimUsed}`);
report(overpass.length === 0, 'geometry: no Overpass request', overpass.slice(0, 3).join(' | ') || 'none');
report(nominatimUsed <= 15, 'geometry: public Nominatim requests within budget (15)', `used=${nominatimUsed}`);
await run('clear_annotations', {});
}
const NOMINATIM_STATE = path.join(ROOT, '.gev-cache', 'nominatim', 'state.json');
function nominatimCount() {
try {