fix(annotations): repair the main merge and port the sampling tests (#1277)

The last merge of main kept four tests on a DOM harness that main had
removed, so they threw ReferenceError and CI failed. They now run on the
store-backed board. The merge had also dropped main's empty-board publish,
the leaving/byKey clear in destroy, and three unrelated CURRENT-STATE
paragraphs; all are restored. The projection now builds each height-cache
key once per point, and the changelog entry says what changed.
This commit is contained in:
Said Turksever committed 2026-11-10 23:54:40 +00:00
1 parent e0bc863a69
commit 0781b4a8de
4 files changed
+64 -37

No files matched your search

+4 -1
View File
@@ -1 +1,4 @@
- Cull and budget surface height sampling (asturksever, #1277).
- Stop screen annotations sampling surface height for marks that are hidden,
behind the camera or over the horizon. Visible marks still waiting for
terrain share 32 samples per projection pass and retry once a second, so a
large board no longer stalls the frame (asturksever, #1277).
+18
View File
@@ -127,6 +127,24 @@ broader suffix variants. Follow-ups retain boundary provenance, and cancelled
lookups preserve the previous analyst cohort and boundary source. Navigation
search keeps a successful geocode, with no search or view-based region recovery.
Manager-owned periodic network updates pause while the document is hidden. Returning to a visible tab runs one overdue update per enabled layer without overlapping an active refresh and restarts that layer's polling schedule from the return, so the next poll follows one interval later whether or not the catch-up update succeeds; explicit refresh requests remain available. Transit also defers camera-driven feed admission while hidden and rechecks coverage on return. Visibility listeners are released with their polling intervals.
Overpass reserves each normalized query before its disk-cache lookup. The
initial disk lookup, upstream quota admission, fetching and memory publication
are coalesced under that reservation. A caller that joined a reservation
rejected by another client's quota retries admission with its own quota.
Stale fallback runs per response after the reservation is released, and disk
writes finish asynchronously. Settled and rejected reservations are removed
only while they still own their map entry.
External ALPR tags, surveillance-tool fields, voice context text and compact
analyst strings become one line of at most 180 characters (scene labels: 120).
Empty ALPR values stay null, and the cap never splits a surrogate pair.
Canonical analyst identifiers such as `id` and `routeId` stay unchanged, because
voice follow-ups match on them and bikeshare uses the raw station name as its
ID. The text is still untrusted: normalization does not prevent prompt
injection.
Screen annotations skip hidden records and, before surface sampling, reject an
unsettled anchor only when its whole accepted height range (-430 to 9000 m) is
behind the camera or below the globe horizon; a settled height is tested as the
+13 -7
View File
@@ -153,9 +153,8 @@ export function createScreenAnnotationRenderer(
// Best-effort surface height under a coordinate (clamps onto the 3D tiles).
// Returns 0 until the tile loads, then caches the validated height.
// canSample=false returns the cached height (or 0) without surface sampling.
function groundHeight(lon, lat, canSample = true) {
const key = heightKey(lon, lat);
const cached = heightCache.get(key);
// `key` and `cached` are the caller's cache lookup for this coordinate.
function groundHeight(lon, lat, key, cached, canSample = true) {
if (cached && cached.settled) {
// Mark hot for this frame + bump to most-recently-used so heights for live
// marks survive eviction while places we've navigated away from age out.
@@ -413,9 +412,11 @@ export function createScreenAnnotationRenderer(
// A settled height is tested as the actual point below. An unknown height
// is rejected before GPU-backed surface sampling only when the whole
// accepted height range is proven hidden by one constraint.
const key = heightKey(lon, lat);
const cached = heightCache.get(key);
let height;
if (heightCache.get(heightKey(lon, lat))?.settled) {
height = groundHeight(lon, lat);
if (cached?.settled) {
height = groundHeight(lon, lat, key, cached);
} else {
if (heightRangeHidden(lon, lat)) return null;
// The frustum restricts sampling only; an endpoint outside it still
@@ -431,7 +432,7 @@ export function createScreenAnnotationRenderer(
const inFrustum =
cullingVolume?.computeVisibility(heightBounds) !==
Cesium.Intersect.OUTSIDE;
height = groundHeight(lon, lat, inFrustum);
height = groundHeight(lon, lat, key, cached, inFrustum);
}
const world = Cesium.Cartesian3.fromDegrees(lon, lat, height);
// Reject points behind the camera (they produce wrapped/extreme coords).
@@ -552,7 +553,10 @@ export function createScreenAnnotationRenderer(
}
function projectAll() {
if (!records.size) return;
if (!records.size) {
publish();
return;
}
// Serve the oldest refused keys first; the rest of the budget is open.
heightReserved = new Set();
for (const key of heightQueue) {
@@ -931,6 +935,8 @@ export function createScreenAnnotationRenderer(
store.publish(null);
}
records.clear();
leaving.clear();
byKey.clear();
clearHeights();
}
@@ -584,17 +584,7 @@ test('a mark hidden from Draw is not drawn or placed, and comes back when shown'
});
test('hidden and back-facing annotations do not sample; visible unresolved heights have bounded retries', (t) => {
const previous = globalThis.document;
const previousRaf = globalThis.requestAnimationFrame;
globalThis.document = fakeDocument();
globalThis.requestAnimationFrame = (fn) => {
fn();
return 1;
};
t.after(() => {
globalThis.document = previous;
globalThis.requestAnimationFrame = previousRaf;
});
const store = samplingBoard(t);
t.mock.timers.enable({ apis: ['setTimeout', 'Date'], now: 1000 });
t.mock.method(Cesium.SceneTransforms, 'worldToWindowCoordinates', () => ({
x: 100,
@@ -618,7 +608,7 @@ test('hidden and back-facing annotations do not sample; visible unresolved heigh
},
requestRender() {},
};
const renderer = createScreenAnnotationRenderer({ scene, camera });
const renderer = samplingRenderer(store, scene);
const mark = (id, lon) => ({
id,
type: 'label',
@@ -679,18 +669,29 @@ function sampledLon(world) {
return Number(Cesium.Math.toDegrees(carto.longitude).toFixed(5));
}
function installDom(t) {
const previous = globalThis.document;
/**
* A board with no view mounted, for tests that count surface samples: the
* renderer projects on `add` and on each post-render event, and the store
* holds what it would draw. Animation frames run at once.
*/
function samplingBoard(t) {
const previousRaf = globalThis.requestAnimationFrame;
globalThis.document = fakeDocument();
globalThis.requestAnimationFrame = (fn) => {
fn();
return 1;
};
t.after(() => {
globalThis.document = previous;
globalThis.requestAnimationFrame = previousRaf;
});
return useScreenAnnotationsStore(createPinia());
}
/** The renderer under test, on the given board and scene. */
function samplingRenderer(store, scene) {
return createScreenAnnotationRenderer(
{ scene, camera: scene.camera, trackedEntity: null },
{ store, activeTrackedReadoutId: () => null },
);
}
function squareFrustum() {
@@ -703,7 +704,7 @@ function squareFrustum() {
}
test('an arrow with one endpoint outside a real frustum keeps its visible connector', (t) => {
installDom(t);
const store = samplingBoard(t);
t.mock.method(Cesium.SceneTransforms, 'worldToWindowCoordinates', (_scene, world) =>
sampledLon(world) > 0.1 ? { x: 1100, y: 400 } : { x: 400, y: 400 },
);
@@ -739,7 +740,7 @@ test('an arrow with one endpoint outside a real frustum keeps its visible connec
volume.computeVisibility(new Cesium.BoundingSphere(far, 9500)),
Cesium.Intersect.OUTSIDE,
);
const renderer = createScreenAnnotationRenderer({ scene, camera });
const renderer = samplingRenderer(store, scene);
renderer.add({
id: 'partial-arrow',
type: 'arrow',
@@ -748,16 +749,16 @@ test('an arrow with one endpoint outside a real frustum keeps its visible connec
to: { lon: 0.2, lat: 0 },
});
postRender.raiseEvent();
const { group } = findAnnotationGroup(globalThis.document);
assert.equal(group.style.display, '', 'the arrow stays drawn');
const path = group.children.find((child) => child.classList.contains('gev-anno-arrow'));
assert.match(path.getAttribute('d'), /^M 400\.0 400\.0 Q .* 1100\.0 400\.0$/);
const [mark] = store.board.marks;
assert.equal(mark.display, '', 'the arrow stays drawn');
const path = mark.children.find((child) => child.attrs.class?.includes('gev-anno-arrow'));
assert.match(path.attrs.d, /^M 400\.0 400\.0 Q .* 1100\.0 400\.0$/);
assert.deepEqual([...new Set(sampled)], [0], 'only the in-frustum endpoint samples');
renderer.destroy();
});
test('a low-angle cached roof stays visible when range endpoints fail different tests', (t) => {
installDom(t);
const store = samplingBoard(t);
t.mock.method(Cesium.SceneTransforms, 'worldToWindowCoordinates', () => ({ x: 400, y: 380 }));
// Camera 1 m above the ellipsoid, heading east, pitched 25 degrees down.
const pitch = Cesium.Math.toRadians(25);
@@ -794,19 +795,18 @@ test('a low-angle cached roof stays visible when range endpoints fail different
assert.equal(ahead(20), true);
assert.equal(occluder.isPointVisible(Cesium.Cartesian3.fromDegrees(roofLon, 0, 20)), true);
const renderer = createScreenAnnotationRenderer({ scene, camera });
const renderer = samplingRenderer(store, scene);
renderer.add({ id: 'roof', type: 'label', label: 'Roof', anchor: { lon: roofLon, lat: 0 } });
const { group } = findAnnotationGroup(globalThis.document);
assert.equal(samples, 1, 'the unknown height is sampled, not culled');
assert.equal(group.style.display, '');
assert.equal(store.board.marks[0].display, '');
for (let i = 0; i < 5; i++) postRender.raiseEvent();
assert.equal(samples, 1, 'the settled height is reused');
assert.equal(group.style.display, '', 'the cached roof point is tested and shown');
assert.equal(store.board.marks[0].display, '', 'the cached roof point is tested and shown');
renderer.destroy();
});
test('height retries beyond the first budget are served under sparse passes', (t) => {
installDom(t);
const store = samplingBoard(t);
t.mock.timers.enable({ apis: ['setTimeout', 'Date'], now: 1000 });
t.mock.method(Cesium.SceneTransforms, 'worldToWindowCoordinates', () => ({ x: 100, y: 100 }));
const camera = {
@@ -830,7 +830,7 @@ test('height retries beyond the first budget are served under sparse passes', (t
return tilesReady && lon === last ? Cesium.Cartesian3.fromDegrees(lon, 0, 12) : undefined;
},
};
const renderer = createScreenAnnotationRenderer({ scene, camera });
const renderer = samplingRenderer(store, scene);
for (const lon of lons) {
renderer.add({ id: `p${lon}`, type: 'label', label: 'P', anchor: { lon, lat: 0 } });
}