mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
feat(outlines): restore voice outlines without public Overpass
use bundled place boundaries and shared OpenFreeMap tiles before a guarded Nominatim fallback preserve multipart shapes, holes and cancellation, and keep pending annotations visible while ground sampling finishes across frames keep non-Latin name distinctions when selecting provider results, and retain an outline anchor only when it lies in filled geometry
This commit is contained in:
commit
d4434aa6d7
1489 files changed
+196442
-12353
No files matched your search
@@ -216,9 +216,16 @@ test('outline queue: an 8-spec batch keeps at most two FIFO upgrades in flight',
|
||||
});
|
||||
|
||||
test('outline queue: clear drops queued-but-unstarted upgrades without a later fetch', async (t) => {
|
||||
let engine;
|
||||
// Destroy while the browser/timer stubs are still installed.
|
||||
t.after(() => engine?.destroy());
|
||||
installAnimationFrameStubs(t);
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
t.after(() => t.mock.timers.reset());
|
||||
const { renderer } = fakeRenderer();
|
||||
renderer.destroy = () => {};
|
||||
let fetchesStarted = 0;
|
||||
const releases = [];
|
||||
const resolveTarget = async ({ target, signal }) => ({
|
||||
lon: Number(target.slice('clear-'.length)),
|
||||
lat: 10,
|
||||
@@ -226,26 +233,57 @@ test('outline queue: clear drops queued-but-unstarted upgrades without a later f
|
||||
label: target,
|
||||
source: 'fake',
|
||||
ring: null,
|
||||
resolveOutline: () => new Promise((resolve) => {
|
||||
fetchesStarted += 1;
|
||||
const finish = () => resolve(undefined);
|
||||
if (signal.aborted) finish();
|
||||
else signal.addEventListener('abort', finish, { once: true });
|
||||
}),
|
||||
resolveOutline: () =>
|
||||
new Promise((resolve) => {
|
||||
fetchesStarted += 1;
|
||||
const finish = () => resolve(undefined);
|
||||
releases.push(finish);
|
||||
if (signal.aborted) finish();
|
||||
else signal.addEventListener('abort', finish, { once: true });
|
||||
}),
|
||||
});
|
||||
engine = createAnnotationEngine({
|
||||
viewer: {},
|
||||
renderer,
|
||||
resolveTarget,
|
||||
});
|
||||
const engine = createAnnotationEngine({ viewer: {}, renderer, resolveTarget });
|
||||
|
||||
await engine.annotate(Array.from({ length: 8 }, (_, i) => ({
|
||||
type: 'area',
|
||||
target: `clear-${i}`,
|
||||
footprint: true,
|
||||
})));
|
||||
await engine.annotate(
|
||||
Array.from({ length: 8 }, (_, i) => ({
|
||||
type: 'area',
|
||||
target: `clear-${i}`,
|
||||
footprint: true,
|
||||
})),
|
||||
);
|
||||
assert.equal(fetchesStarted, 2);
|
||||
|
||||
// Both workers enter their real backoff while the remaining six stay queued.
|
||||
releases[0]();
|
||||
releases[1]();
|
||||
await flushMicrotasks();
|
||||
|
||||
engine.clear();
|
||||
await flushMicrotasks();
|
||||
assert.equal(engine.count(), 0);
|
||||
assert.equal(fetchesStarted, 2, 'the six queued upgrades were dropped on clear');
|
||||
assert.equal(
|
||||
fetchesStarted,
|
||||
2,
|
||||
'the six queued upgrades were dropped on clear',
|
||||
);
|
||||
|
||||
await engine.annotate(
|
||||
[8, 9].map((i) => ({
|
||||
type: 'area',
|
||||
target: `clear-${i}`,
|
||||
footprint: true,
|
||||
})),
|
||||
);
|
||||
await flushMicrotasks();
|
||||
assert.equal(
|
||||
fetchesStarted,
|
||||
4,
|
||||
'cleared backoff timers release both workers without advancing time',
|
||||
);
|
||||
});
|
||||
|
||||
test('retry: HTTP 429 Retry-After 5s gets one ladder-spaced retry; a second 429 stops', async (t) => {
|
||||
@@ -313,7 +351,9 @@ test('retry: a plain HTTP 500 still exhausts the existing 8s/25s transient ladde
|
||||
t.mock.timers.enable({ apis: ['setTimeout', 'Date'], now: 100_000 });
|
||||
globalThis.window = { setTimeout: globalThis.setTimeout, clearTimeout: globalThis.clearTimeout };
|
||||
const requestTimes = [];
|
||||
globalThis.fetch = async () => {
|
||||
globalThis.fetch = async (url) => {
|
||||
if (String(url).endsWith('/status'))
|
||||
return Response.json({ configured: true });
|
||||
requestTimes.push(Date.now());
|
||||
return httpFailure(500);
|
||||
};
|
||||
@@ -402,7 +442,7 @@ test('outline upgrade updates the rendered element in place without remove/add',
|
||||
};
|
||||
let overpassCall = 0;
|
||||
globalThis.fetch = async (url) => {
|
||||
if (String(url).startsWith('https://maps.googleapis.com/')) {
|
||||
if (String(url).startsWith('/api/google/geocode')) {
|
||||
return { json: async () => ({
|
||||
status: 'OK',
|
||||
results: [{
|
||||
@@ -516,6 +556,360 @@ function throwingRendererHarness() {
|
||||
|
||||
const FIXED_POINT = { lon: -97.7431, lat: 30.2672, height: 0 };
|
||||
|
||||
// Building installation is a renderer-owned async interval after location resolution.
|
||||
function controlledBuildingRenderer() {
|
||||
const { renderer, calls } = fakeRenderer();
|
||||
const pending = [];
|
||||
const live = new Set();
|
||||
const enqueue = (anno, options = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
const finish = (status = 'installed', error) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
options.signal?.removeEventListener('abort', abort);
|
||||
if (status === 'installed' && (options.isCurrent?.() ?? true))
|
||||
live.add(anno.id);
|
||||
if (error) reject(error);
|
||||
else
|
||||
resolve({
|
||||
status,
|
||||
timing: { workMs: 1, maxSliceMs: 1, lastSliceMs: 1 },
|
||||
});
|
||||
};
|
||||
const abort = () => finish('cancelled');
|
||||
options.signal?.addEventListener('abort', abort, { once: true });
|
||||
pending.push({ anno, options, finish });
|
||||
});
|
||||
renderer.add = (anno, options) => {
|
||||
calls.add += 1;
|
||||
if (anno.footprintKind === 'building' && anno.ring)
|
||||
return enqueue(anno, options);
|
||||
live.add(anno.id);
|
||||
};
|
||||
renderer.update = (anno, options) => {
|
||||
calls.update += 1;
|
||||
if (anno.footprintKind === 'building' && anno.ring)
|
||||
return enqueue(anno, options);
|
||||
};
|
||||
renderer.remove = (anno) => {
|
||||
calls.remove += 1;
|
||||
live.delete(anno.id);
|
||||
pending
|
||||
.filter((item) => item.anno === anno)
|
||||
.forEach((item) => item.finish('cancelled'));
|
||||
};
|
||||
renderer.destroy = () => pending.forEach((item) => item.finish('cancelled'));
|
||||
return { renderer, calls, pending, live };
|
||||
}
|
||||
const BUILDING_FP = {
|
||||
...FIXED_POINT,
|
||||
ring: [
|
||||
[-97.744, 30.267],
|
||||
[-97.743, 30.267],
|
||||
[-97.743, 30.268],
|
||||
[-97.744, 30.267],
|
||||
],
|
||||
footprintKind: 'building',
|
||||
buildingHeight: 20,
|
||||
};
|
||||
|
||||
test('fresh async building batch reserves slots together and releases failed/cleared installs', async (t) => {
|
||||
installAnimationFrameStubs(t);
|
||||
_resetRenderGovernorForTest();
|
||||
t.after(() => _resetRenderGovernorForTest());
|
||||
const h = controlledBuildingRenderer();
|
||||
const engine = createAnnotationEngine({
|
||||
viewer: {},
|
||||
renderer: h.renderer,
|
||||
resolveTarget: async ({ target }) => ({
|
||||
...BUILDING_FP,
|
||||
lon: target === 'second' ? -97.7 : BUILDING_FP.lon,
|
||||
}),
|
||||
});
|
||||
const result = engine.annotate([
|
||||
{ type: 'area', target: 'first', label: 'First' },
|
||||
{ type: 'area', target: 'second', label: 'Second' },
|
||||
]);
|
||||
await flushMicrotasks();
|
||||
assert.equal(
|
||||
h.pending.length,
|
||||
2,
|
||||
'both installs enqueue without serial awaits',
|
||||
);
|
||||
assert.equal(engine.count(), 2, 'pending marks reserve the cap');
|
||||
h.pending[0].finish('failed', new Error('async WebGL failure'));
|
||||
h.pending[1].finish('timeout');
|
||||
const completed = await result;
|
||||
assert.equal(completed.drawn, 1);
|
||||
assert.equal(completed.failed, 1);
|
||||
assert.equal(engine.count(), 1);
|
||||
assert.equal(engine.list()[0].outlineUnavailable, true);
|
||||
assert.equal(engine.list()[0].ring, null);
|
||||
assert.equal(engine.list()[0].pendingOutline, false);
|
||||
engine.clear();
|
||||
assert.equal(engine.count(), 0);
|
||||
assert.ok(!getRenderGovernorDiagnostics().holds.includes('annotations'));
|
||||
|
||||
const cancelled = engine.annotate([{ type: 'area', target: 'third' }]);
|
||||
await flushMicrotasks();
|
||||
const task = h.pending.at(-1);
|
||||
engine.clear();
|
||||
assert.equal(task.options.signal.aborted, true);
|
||||
task.finish();
|
||||
assert.equal((await cancelled).aborted, true);
|
||||
assert.equal(engine.count(), 0);
|
||||
assert.equal(h.live.size, 0);
|
||||
});
|
||||
|
||||
test('outline workers retain completion ownership and clear cancels resolved-but-uninstalled geometry', async (t) => {
|
||||
installAnimationFrameStubs(t);
|
||||
const h = controlledBuildingRenderer();
|
||||
const started = [];
|
||||
const engine = createAnnotationEngine({
|
||||
viewer: {},
|
||||
renderer: h.renderer,
|
||||
resolveTarget: async ({ target }) => ({
|
||||
...FIXED_POINT,
|
||||
lon: -97 + Number(target),
|
||||
resolveOutline: async () => {
|
||||
started.push(target);
|
||||
return { ...BUILDING_FP, lon: -97 + Number(target) };
|
||||
},
|
||||
}),
|
||||
});
|
||||
const events = [];
|
||||
engine.onOutlineEvent((event) => events.push(event));
|
||||
const result = await engine.annotate(
|
||||
['0', '1', '2'].map((target) => ({ type: 'area', target })),
|
||||
);
|
||||
assert.equal(result.drawn, 3);
|
||||
await flushMicrotasks();
|
||||
assert.equal(h.pending.length, 2);
|
||||
assert.deepEqual(started, ['0', '1']);
|
||||
assert.equal(events.length, 0, 'resolver finish is not installation finish');
|
||||
assert.ok(engine.list().every((anno) => anno.pendingOutline));
|
||||
h.pending[0].finish();
|
||||
await flushMicrotasks();
|
||||
assert.equal(events.length, 1);
|
||||
assert.equal(events[0].status, 'resolved');
|
||||
assert.deepEqual(started, ['0', '1', '2']);
|
||||
engine.clear();
|
||||
await flushMicrotasks();
|
||||
h.pending.forEach((task) => {
|
||||
assert.equal(task.options.signal.aborted, true);
|
||||
task.finish();
|
||||
});
|
||||
await flushMicrotasks();
|
||||
assert.equal(events.length, 1, 'no late outcome on cleared marks');
|
||||
assert.equal(engine.count(), 0);
|
||||
assert.equal(h.live.size, 0);
|
||||
});
|
||||
|
||||
test('building recaption timeout retains old route and a newer replacement owns the swap', async (t) => {
|
||||
installAnimationFrameStubs(t);
|
||||
const h = controlledBuildingRenderer();
|
||||
const engine = createAnnotationEngine({
|
||||
viewer: {},
|
||||
renderer: h.renderer,
|
||||
resolveTarget: async () => ({ ...BUILDING_FP }),
|
||||
});
|
||||
const initial = engine.annotate([
|
||||
{ type: 'area', target: 'Capitol', label: 'Old' },
|
||||
]);
|
||||
await flushMicrotasks();
|
||||
h.pending[0].finish();
|
||||
await initial;
|
||||
const old = engine.list()[0];
|
||||
const refresh = (label) =>
|
||||
engine.annotate([{ type: 'area', target: 'Capitol', label }]);
|
||||
const timed = refresh('Timed');
|
||||
await flushMicrotasks();
|
||||
assert.equal(engine.list()[0], old);
|
||||
assert.ok(h.live.has(old.id));
|
||||
h.pending.at(-1).finish('timeout');
|
||||
assert.equal((await timed).failed, 1);
|
||||
assert.equal(engine.list()[0], old);
|
||||
assert.equal(old.label, 'Old');
|
||||
assert.ok(h.live.has(old.id));
|
||||
|
||||
const losing = refresh('Losing');
|
||||
await flushMicrotasks();
|
||||
const loser = h.pending.at(-1);
|
||||
const winning = refresh('Winning');
|
||||
await flushMicrotasks();
|
||||
const winner = h.pending.at(-1);
|
||||
assert.notEqual(loser.anno, winner.anno);
|
||||
assert.equal(loser.options.isCurrent(), false);
|
||||
winner.finish();
|
||||
loser.finish();
|
||||
assert.equal((await losing).failed, 1);
|
||||
assert.equal((await winning).drawn, 1);
|
||||
assert.equal(engine.list()[0].label, 'Winning');
|
||||
assert.equal(h.live.size, 1);
|
||||
assert.ok(!h.live.has(old.id));
|
||||
|
||||
const late = refresh('After clear');
|
||||
await flushMicrotasks();
|
||||
const lateTask = h.pending.at(-1);
|
||||
engine.clear();
|
||||
lateTask.finish();
|
||||
assert.equal((await late).aborted, true);
|
||||
assert.equal(engine.count(), 0);
|
||||
assert.equal(h.live.size, 0);
|
||||
});
|
||||
|
||||
|
||||
|
||||
test('replacement cleanup failure still removes the old route and retains failed cleanup ownership', async (t) => {
|
||||
installAnimationFrameStubs(t);
|
||||
const h = controlledBuildingRenderer();
|
||||
const engine = createAnnotationEngine({
|
||||
viewer: {}, renderer: h.renderer, resolveTarget: async () => ({ ...BUILDING_FP }),
|
||||
});
|
||||
const initial = engine.annotate([{ type: 'area', target: 'Capitol', label: 'Old' }]);
|
||||
await flushMicrotasks();
|
||||
h.pending[0].finish();
|
||||
await initial;
|
||||
const old = engine.list()[0];
|
||||
const refresh = engine.annotate([{ type: 'area', target: 'Capitol', label: 'New' }]);
|
||||
await flushMicrotasks();
|
||||
const replacement = h.pending.at(-1).anno;
|
||||
const originalRemove = h.renderer.remove;
|
||||
const removals = [];
|
||||
let fail = true;
|
||||
h.renderer.remove = (anno) => {
|
||||
removals.push(anno);
|
||||
if (anno === replacement && fail) throw new Error('replacement cleanup failed');
|
||||
originalRemove(anno);
|
||||
};
|
||||
assert.throws(() => engine.clear(), /replacement cleanup failed/);
|
||||
assert.ok(removals.includes(old), 'old removal is attempted even when replacement removal throws');
|
||||
assert.equal(old._pendingReplacement, null, 'cleanup failure cannot retain install authority');
|
||||
assert.ok(old._retiredRoutes.has(replacement));
|
||||
assert.equal(engine.list()[0], old, 'failed cleanup remains reachable');
|
||||
await flushMicrotasks();
|
||||
fail = false;
|
||||
engine.clear();
|
||||
await refresh;
|
||||
assert.equal(engine.count(), 0);
|
||||
assert.equal(h.live.size, 0);
|
||||
assert.equal(old._pendingReplacement, null);
|
||||
});
|
||||
|
||||
|
||||
|
||||
test('identical refresh revokes an older building replacement even if cancellation cleanup fails', async (t) => {
|
||||
installAnimationFrameStubs(t);
|
||||
const h = controlledBuildingRenderer();
|
||||
const engine = createAnnotationEngine({
|
||||
viewer: {}, renderer: h.renderer, resolveTarget: async () => ({ ...BUILDING_FP }),
|
||||
});
|
||||
const initial = engine.annotate([{ type: 'area', target: 'Capitol', label: 'Old' }]);
|
||||
await flushMicrotasks(); h.pending[0].finish(); await initial;
|
||||
const old = engine.list()[0];
|
||||
const pending = engine.annotate([{ type: 'area', target: 'Capitol', label: 'New' }]);
|
||||
await flushMicrotasks();
|
||||
const task = h.pending.at(-1);
|
||||
const remove = h.renderer.remove;
|
||||
let fail = true;
|
||||
h.renderer.remove = (anno) => {
|
||||
if (anno === task.anno && fail) throw new Error('cancel cleanup failed');
|
||||
remove(anno);
|
||||
};
|
||||
const identical = await engine.annotate([{ type: 'area', target: 'Capitol', label: 'Old' }]);
|
||||
assert.equal(identical.results[0].id, old.id);
|
||||
assert.equal(identical.results[0].cleanupPending, true);
|
||||
assert.equal(task.options.isCurrent(), false, 'newest identical ask revokes install authority immediately');
|
||||
task.finish();
|
||||
assert.equal((await pending).failed, 1);
|
||||
assert.equal(engine.list()[0], old);
|
||||
assert.equal(old.label, 'Old');
|
||||
fail = false;
|
||||
engine.clear();
|
||||
assert.equal(engine.count(), 0);
|
||||
});
|
||||
|
||||
test('successful replacement survives failed old-route retirement and keeps cleanup addressable', async (t) => {
|
||||
installAnimationFrameStubs(t);
|
||||
const h = controlledBuildingRenderer();
|
||||
const engine = createAnnotationEngine({
|
||||
viewer: {}, renderer: h.renderer, resolveTarget: async () => ({ ...BUILDING_FP }),
|
||||
});
|
||||
const initial = engine.annotate([{ type: 'area', target: 'Capitol', label: 'Old' }]);
|
||||
await flushMicrotasks(); h.pending[0].finish(); await initial;
|
||||
const old = engine.list()[0];
|
||||
const remove = h.renderer.remove;
|
||||
let fail = true;
|
||||
h.renderer.remove = (anno) => {
|
||||
if (anno === old && fail) throw new Error('retirement failed');
|
||||
remove(anno);
|
||||
};
|
||||
const refresh = engine.annotate([{ type: 'area', target: 'Capitol', label: 'New' }]);
|
||||
await flushMicrotasks(); h.pending.at(-1).finish();
|
||||
const completed = await refresh;
|
||||
const current = engine.list()[0];
|
||||
assert.equal(completed.drawn, 1);
|
||||
assert.equal(completed.results[0].cleanupPending, true);
|
||||
assert.equal(current.label, 'New');
|
||||
assert.ok(h.live.has(current.id), 'valid installed replacement is not rolled back by old teardown failure');
|
||||
assert.equal(current._pendingReplacement, undefined);
|
||||
assert.ok(current._retiredRoutes.has(old));
|
||||
fail = false;
|
||||
engine.clear();
|
||||
assert.equal(engine.count(), 0);
|
||||
assert.equal(h.live.size, 0);
|
||||
});
|
||||
|
||||
test('pending fresh predecessor failure cannot erase a newer building intent', async (t) => {
|
||||
installAnimationFrameStubs(t);
|
||||
const frames = [];
|
||||
globalThis.requestAnimationFrame = (callback) => { frames.push(callback); return frames.length; };
|
||||
const h = controlledBuildingRenderer();
|
||||
const engine = createAnnotationEngine({
|
||||
viewer: {}, renderer: h.renderer, resolveTarget: async () => ({ ...BUILDING_FP }),
|
||||
});
|
||||
const first = engine.annotate([{ type: 'area', target: 'Capitol', label: 'First' }]);
|
||||
await flushMicrotasks();
|
||||
const predecessor = h.pending[0];
|
||||
assert.ok(frames.length > 0, 'fresh pending point starts its existing fade tick before batch settlement');
|
||||
const remove = h.renderer.remove;
|
||||
h.renderer.remove = (anno) => {
|
||||
if (anno !== predecessor.anno) remove(anno);
|
||||
};
|
||||
const second = engine.annotate([{ type: 'area', target: 'Capitol', label: 'Second' }]);
|
||||
await flushMicrotasks();
|
||||
const successor = h.pending.at(-1);
|
||||
assert.notEqual(successor.anno, predecessor.anno);
|
||||
predecessor.finish('failed', new Error('late old install failure'));
|
||||
await flushMicrotasks();
|
||||
assert.equal(engine.list()[0], successor.anno);
|
||||
assert.equal(successor.options.isCurrent(), true);
|
||||
successor.finish();
|
||||
assert.equal((await first).failed, 1);
|
||||
assert.equal((await second).drawn, 1);
|
||||
assert.equal(engine.list()[0].label, 'Second');
|
||||
engine.clear();
|
||||
});
|
||||
|
||||
test('duplicate pending building results reflect final failure rather than a dead id', async (t) => {
|
||||
installAnimationFrameStubs(t);
|
||||
const h = controlledBuildingRenderer();
|
||||
const engine = createAnnotationEngine({
|
||||
viewer: {}, renderer: h.renderer, resolveTarget: async () => ({ ...BUILDING_FP }),
|
||||
});
|
||||
const spec = { type: 'area', target: 'Capitol', label: 'Same' };
|
||||
const batch = engine.annotate([spec, spec]);
|
||||
await flushMicrotasks();
|
||||
assert.equal(h.pending.length, 1);
|
||||
h.pending[0].finish('failed', new Error('failed shared install'));
|
||||
const result = await batch;
|
||||
assert.equal(result.drawn, 0);
|
||||
assert.equal(result.failed, 2);
|
||||
assert.deepEqual(result.ids, []);
|
||||
assert.equal(engine.count(), 0);
|
||||
});
|
||||
|
||||
test('duplicate-replacement: a renderer throw leaves no phantom mark and no leaked hold', async (t) => {
|
||||
installAnimationFrameStubs(t);
|
||||
_resetRenderGovernorForTest();
|
||||
|
||||
Reference in new issue
Block a user