mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
feat(cctv): seven camera packs, semantic labels, terrain-clear monitor planes (#433)
Seven contributed CCTV camera packs (Ontario 511, Fintraffic Finland, DriveBC, TxDOT Texas, Estonia Tallinn + Tarktee, Warendorf, Live Traffic NSW) on a fair per-pack catalog cap; semantic camera labels; monitor planes lifted clear of the terrain from a precomputed, bundled ground-height dataset. Default catalog 800 → 3,446 cameras. Full context in PR #433. Co-authored-by: sendmebits <[email protected]> Co-authored-by: avocadoAgent <[email protected]> Co-authored-by: Alex Harvey <[email protected]> Co-authored-by: Jason Meredith <[email protected]> Co-authored-by: Daniel Smith <[email protected]> Co-authored-by: Vladimir Sobolev <[email protected]> Co-authored-by: Matthias Schneider <[email protected]> Co-authored-by: Cyber Knight <[email protected]> Co-authored-by: NovaSoft Australia <[email protected]>
This commit is contained in:
51 files changed
+78682
-939
No files matched your search
+215
-74
@@ -41,7 +41,9 @@ test('cctvLodBudgets scales the card budget from 20 to 40 with view height', ()
|
||||
assert.equal(CCTV_AMBIENT_CARD_MAX, 40);
|
||||
assert.deepEqual(cctvLodBudgets(500), { cardLimit: CCTV_AMBIENT_CARD_MIN });
|
||||
assert.deepEqual(cctvLodBudgets(8_000), { cardLimit: CCTV_AMBIENT_CARD_MID });
|
||||
assert.deepEqual(cctvLodBudgets(30_000), { cardLimit: CCTV_AMBIENT_CARD_MAX });
|
||||
assert.deepEqual(cctvLodBudgets(30_000), {
|
||||
cardLimit: CCTV_AMBIENT_CARD_MAX,
|
||||
});
|
||||
});
|
||||
|
||||
test('selectCctvLod caps ambient cards at the 40 hard cap regardless of catalog size', () => {
|
||||
@@ -54,19 +56,30 @@ test('selectCctvLod uses only in-view cameras and orders by viewer distance', ()
|
||||
const input = candidates(20, { hiddenAt: 0 }).reverse();
|
||||
const selected = selectCctvLod(input, { cameraHeightM: 500 });
|
||||
assert.equal(selected.cardIds.includes('cam-000'), false);
|
||||
assert.deepEqual(selected.cardIds.slice(0, 3), ['cam-001', 'cam-002', 'cam-003']);
|
||||
assert.deepEqual(selected.cardIds.slice(0, 3), [
|
||||
'cam-001',
|
||||
'cam-002',
|
||||
'cam-003',
|
||||
]);
|
||||
});
|
||||
|
||||
test('video cameras never consume ambient card slots', () => {
|
||||
const selected = selectCctvLod(candidates(30, { videoAt: 0 }), { cameraHeightM: 500 });
|
||||
const selected = selectCctvLod(candidates(30, { videoAt: 0 }), {
|
||||
cameraHeightM: 500,
|
||||
});
|
||||
assert.equal(selected.cardIds.includes('cam-000'), false);
|
||||
assert.equal(selected.cardIds.length, CCTV_AMBIENT_CARD_MIN);
|
||||
});
|
||||
|
||||
test('selectCctvLod tolerates malformed candidate rows', () => {
|
||||
const selected = selectCctvLod(
|
||||
[null, {}, { id: '', inView: true }, { id: 'cam-ok', inView: true, distanceKm: NaN }],
|
||||
{ cameraHeightM: 500 }
|
||||
[
|
||||
null,
|
||||
{},
|
||||
{ id: '', inView: true },
|
||||
{ id: 'cam-ok', inView: true, distanceKm: NaN },
|
||||
],
|
||||
{ cameraHeightM: 500 },
|
||||
);
|
||||
assert.deepEqual(selected.cardIds, ['cam-ok']);
|
||||
});
|
||||
@@ -84,7 +97,9 @@ test('selectCctvLod: a carded camera outranks a slightly-nearer non-carded one',
|
||||
// incumbent at 5.0 km holds its card against a newcomer at 4.5 km (only
|
||||
// 10% closer).
|
||||
const anchors = Array.from({ length: CCTV_AMBIENT_CARD_MIN - 1 }, (_, i) => ({
|
||||
id: `anchor-${String(i).padStart(2, '0')}`, distanceKm: i / 100, inView: true,
|
||||
id: `anchor-${String(i).padStart(2, '0')}`,
|
||||
distanceKm: i / 100,
|
||||
inView: true,
|
||||
}));
|
||||
const contested = [
|
||||
{ id: 'incumbent', distanceKm: 5.0, inView: true },
|
||||
@@ -101,7 +116,9 @@ test('selectCctvLod: a carded camera outranks a slightly-nearer non-carded one',
|
||||
|
||||
test('selectCctvLod: a meaningfully (>20%) closer camera still displaces an incumbent', () => {
|
||||
const anchors = Array.from({ length: CCTV_AMBIENT_CARD_MIN - 1 }, (_, i) => ({
|
||||
id: `anchor-${String(i).padStart(2, '0')}`, distanceKm: i / 100, inView: true,
|
||||
id: `anchor-${String(i).padStart(2, '0')}`,
|
||||
distanceKm: i / 100,
|
||||
inView: true,
|
||||
}));
|
||||
const contested = [
|
||||
{ id: 'incumbent', distanceKm: 5.0, inView: true },
|
||||
@@ -111,13 +128,19 @@ test('selectCctvLod: a meaningfully (>20%) closer camera still displaces an incu
|
||||
cameraHeightM: 500,
|
||||
incumbentIds: new Set(['incumbent']),
|
||||
});
|
||||
assert.ok(selected.cardIds.includes('newcomer'), 'meaningfully closer camera wins');
|
||||
assert.ok(
|
||||
selected.cardIds.includes('newcomer'),
|
||||
'meaningfully closer camera wins',
|
||||
);
|
||||
assert.equal(selected.cardIds.includes('incumbent'), false);
|
||||
});
|
||||
|
||||
test('selectCctvLod: no incumbents means plain nearest-first (unchanged behavior)', () => {
|
||||
const plain = selectCctvLod(candidates(20), { cameraHeightM: 500 });
|
||||
const withEmpty = selectCctvLod(candidates(20), { cameraHeightM: 500, incumbentIds: new Set() });
|
||||
const withEmpty = selectCctvLod(candidates(20), {
|
||||
cameraHeightM: 500,
|
||||
incumbentIds: new Set(),
|
||||
});
|
||||
assert.deepEqual(plain.cardIds, withEmpty.cardIds);
|
||||
});
|
||||
|
||||
@@ -129,25 +152,39 @@ test('distributeCctvCards spreads clustered candidates across cells', () => {
|
||||
// sits alone bottom-right. Nearest-first would take the whole cluster —
|
||||
// distribution gives every occupied cell its best first.
|
||||
const clustered = Array.from({ length: 4 }, (_, i) => ({
|
||||
id: `center-${i}`, sx: 50 + i * 10, sy: 50, rankKm: i,
|
||||
id: `center-${i}`,
|
||||
sx: 50 + i * 10,
|
||||
sy: 50,
|
||||
rankKm: i,
|
||||
}));
|
||||
const lonely = { id: 'periphery', sx: 950, sy: 750, rankKm: 30 };
|
||||
assert.deepEqual(
|
||||
distributeCctvCards([...clustered, lonely], { budget: 2, viewW: 1000, viewH: 800 }),
|
||||
['center-0', 'periphery']
|
||||
distributeCctvCards([...clustered, lonely], {
|
||||
budget: 2,
|
||||
viewW: 1000,
|
||||
viewH: 800,
|
||||
}),
|
||||
['center-0', 'periphery'],
|
||||
);
|
||||
});
|
||||
|
||||
test('distributeCctvCards: leftover budget falls back to global rank order', () => {
|
||||
const clustered = Array.from({ length: 4 }, (_, i) => ({
|
||||
id: `center-${i}`, sx: 50 + i * 10, sy: 50, rankKm: i,
|
||||
id: `center-${i}`,
|
||||
sx: 50 + i * 10,
|
||||
sy: 50,
|
||||
rankKm: i,
|
||||
}));
|
||||
const lonely = { id: 'periphery', sx: 950, sy: 750, rankKm: 30 };
|
||||
// 2 occupied cells, budget 4: both cell winners, then the cluster's next
|
||||
// two by rank.
|
||||
assert.deepEqual(
|
||||
distributeCctvCards([...clustered, lonely], { budget: 4, viewW: 1000, viewH: 800 }),
|
||||
['center-0', 'periphery', 'center-1', 'center-2']
|
||||
distributeCctvCards([...clustered, lonely], {
|
||||
budget: 4,
|
||||
viewW: 1000,
|
||||
viewH: 800,
|
||||
}),
|
||||
['center-0', 'periphery', 'center-1', 'center-2'],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -155,64 +192,101 @@ test('distributeCctvCards: empty cells are skipped, budget respected', () => {
|
||||
// Everything in ONE cell — the 19 empty cells claim nothing; the single
|
||||
// occupied cell fills the whole budget in rank order.
|
||||
const oneCell = Array.from({ length: 5 }, (_, i) => ({
|
||||
id: `c-${i}`, sx: 20 + i * 5, sy: 20, rankKm: i,
|
||||
id: `c-${i}`,
|
||||
sx: 20 + i * 5,
|
||||
sy: 20,
|
||||
rankKm: i,
|
||||
}));
|
||||
const ids = distributeCctvCards(oneCell, { budget: 3, viewW: 1000, viewH: 800 });
|
||||
const ids = distributeCctvCards(oneCell, {
|
||||
budget: 3,
|
||||
viewW: 1000,
|
||||
viewH: 800,
|
||||
});
|
||||
assert.deepEqual(ids, ['c-0', 'c-1', 'c-2']);
|
||||
});
|
||||
|
||||
test('distributeCctvCards: an under-budget cut keeps the nearest cell winners', () => {
|
||||
const ids = distributeCctvCards([
|
||||
{ id: 'far-cell', sx: 900, sy: 700, rankKm: 9 },
|
||||
{ id: 'near-cell', sx: 100, sy: 100, rankKm: 1 },
|
||||
], { budget: 1, viewW: 1000, viewH: 800 });
|
||||
const ids = distributeCctvCards(
|
||||
[
|
||||
{ id: 'far-cell', sx: 900, sy: 700, rankKm: 9 },
|
||||
{ id: 'near-cell', sx: 100, sy: 100, rankKm: 1 },
|
||||
],
|
||||
{ budget: 1, viewW: 1000, viewH: 800 },
|
||||
);
|
||||
assert.deepEqual(ids, ['near-cell']);
|
||||
});
|
||||
|
||||
test('distributeCctvCards: incumbency (effective distance) is honored inside a cell', () => {
|
||||
// Same cell: the incumbent at 5.0 km (discounted to 4.0) beats the
|
||||
// non-carded newcomer at 4.5 km for the cell slot.
|
||||
const ids = distributeCctvCards([
|
||||
{ id: 'newcomer', sx: 100, sy: 100, rankKm: incumbentRankKm(4.5, false) },
|
||||
{ id: 'incumbent', sx: 120, sy: 100, rankKm: incumbentRankKm(5.0, true) },
|
||||
], { budget: 1, viewW: 1000, viewH: 800 });
|
||||
const ids = distributeCctvCards(
|
||||
[
|
||||
{ id: 'newcomer', sx: 100, sy: 100, rankKm: incumbentRankKm(4.5, false) },
|
||||
{ id: 'incumbent', sx: 120, sy: 100, rankKm: incumbentRankKm(5.0, true) },
|
||||
],
|
||||
{ budget: 1, viewW: 1000, viewH: 800 },
|
||||
);
|
||||
assert.deepEqual(ids, ['incumbent']);
|
||||
});
|
||||
|
||||
test('distributeCctvCards: malformed rows dropped; offscreen anchors clamp to edge cells', () => {
|
||||
const ids = distributeCctvCards([
|
||||
null,
|
||||
{ id: '', sx: 10, sy: 10, rankKm: 0 },
|
||||
{ id: 'nan', sx: NaN, sy: 10, rankKm: 0 },
|
||||
{ id: 'ok', sx: 10, sy: 10, rankKm: 1 },
|
||||
// Margin candidates just outside the viewport land in the edge cells.
|
||||
{ id: 'edge', sx: -20, sy: 850, rankKm: 2 },
|
||||
], { budget: 5, viewW: 1000, viewH: 800 });
|
||||
const ids = distributeCctvCards(
|
||||
[
|
||||
null,
|
||||
{ id: '', sx: 10, sy: 10, rankKm: 0 },
|
||||
{ id: 'nan', sx: NaN, sy: 10, rankKm: 0 },
|
||||
{ id: 'ok', sx: 10, sy: 10, rankKm: 1 },
|
||||
// Margin candidates just outside the viewport land in the edge cells.
|
||||
{ id: 'edge', sx: -20, sy: 850, rankKm: 2 },
|
||||
],
|
||||
{ budget: 5, viewW: 1000, viewH: 800 },
|
||||
);
|
||||
assert.deepEqual(ids.sort(), ['edge', 'ok']);
|
||||
assert.deepEqual(distributeCctvCards([], { budget: 5, viewW: 1000, viewH: 800 }), []);
|
||||
assert.deepEqual(
|
||||
distributeCctvCards([], { budget: 5, viewW: 1000, viewH: 800 }),
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
test('selectCctvLod with viewport dims routes the fill through screen distribution', () => {
|
||||
// A cluster larger than the street budget sits mid-screen; a periphery
|
||||
// camera ranks past the budget. Nearest-first drops it — distribution
|
||||
// keeps it because its cell is otherwise empty.
|
||||
const clustered = Array.from({ length: CCTV_AMBIENT_CARD_MIN + 2 }, (_, i) => ({
|
||||
id: `center-${String(i).padStart(2, '0')}`,
|
||||
distanceKm: i / 10,
|
||||
const clustered = Array.from(
|
||||
{ length: CCTV_AMBIENT_CARD_MIN + 2 },
|
||||
(_, i) => ({
|
||||
id: `center-${String(i).padStart(2, '0')}`,
|
||||
distanceKm: i / 10,
|
||||
inView: true,
|
||||
sx: 400 + (i % 5) * 8,
|
||||
sy: 300 + Math.floor(i / 5) * 8,
|
||||
}),
|
||||
);
|
||||
const periphery = {
|
||||
id: 'periphery',
|
||||
distanceKm: 9,
|
||||
inView: true,
|
||||
sx: 400 + (i % 5) * 8,
|
||||
sy: 300 + Math.floor(i / 5) * 8,
|
||||
}));
|
||||
const periphery = { id: 'periphery', distanceKm: 9, inView: true, sx: 950, sy: 750 };
|
||||
sx: 950,
|
||||
sy: 750,
|
||||
};
|
||||
const selected = selectCctvLod([...clustered, periphery], {
|
||||
cameraHeightM: 500, viewW: 1000, viewH: 800,
|
||||
cameraHeightM: 500,
|
||||
viewW: 1000,
|
||||
viewH: 800,
|
||||
});
|
||||
assert.equal(selected.cardIds.length, CCTV_AMBIENT_CARD_MIN, 'budget respected');
|
||||
assert.ok(selected.cardIds.includes('periphery'), 'periphery cell holds a card');
|
||||
assert.equal(
|
||||
selected.cardIds.length,
|
||||
CCTV_AMBIENT_CARD_MIN,
|
||||
'budget respected',
|
||||
);
|
||||
assert.ok(
|
||||
selected.cardIds.includes('periphery'),
|
||||
'periphery cell holds a card',
|
||||
);
|
||||
// Without screen info the original nearest-first cap applies unchanged.
|
||||
const plain = selectCctvLod(
|
||||
[...clustered, periphery].map(({ sx, sy, ...rest }) => rest),
|
||||
{ cameraHeightM: 500 }
|
||||
{ cameraHeightM: 500 },
|
||||
);
|
||||
assert.equal(plain.cardIds.includes('periphery'), false);
|
||||
});
|
||||
@@ -253,11 +327,13 @@ test('non-finite positive viewport dimensions retain exact legacy nearest-first
|
||||
const legacy = selectCctvLod(input, { cameraHeightM: 500 });
|
||||
|
||||
assert.deepEqual(
|
||||
selectCctvLod(input, { cameraHeightM: 500, viewW: Infinity, viewH: 800 }).cardIds,
|
||||
selectCctvLod(input, { cameraHeightM: 500, viewW: Infinity, viewH: 800 })
|
||||
.cardIds,
|
||||
legacy.cardIds,
|
||||
);
|
||||
assert.deepEqual(
|
||||
selectCctvLod(input, { cameraHeightM: 500, viewW: 1000, viewH: Infinity }).cardIds,
|
||||
selectCctvLod(input, { cameraHeightM: 500, viewW: 1000, viewH: Infinity })
|
||||
.cardIds,
|
||||
legacy.cardIds,
|
||||
);
|
||||
|
||||
@@ -266,7 +342,8 @@ test('non-finite positive viewport dimensions retain exact legacy nearest-first
|
||||
{ id: 'normal', distanceKm: 2, inView: true },
|
||||
];
|
||||
assert.deepEqual(
|
||||
selectCctvLod(legacyEdgeIds, { cameraHeightM: 500, viewW: 0, viewH: 800 }).cardIds,
|
||||
selectCctvLod(legacyEdgeIds, { cameraHeightM: 500, viewW: 0, viewH: 800 })
|
||||
.cardIds,
|
||||
[' ', 'normal'],
|
||||
'invalid viewport preserves every non-empty string ID accepted by the legacy path',
|
||||
);
|
||||
@@ -294,18 +371,34 @@ test('center weighting deterministically favors center without starving peripher
|
||||
const { edge, center } = edgeVsCenterPool();
|
||||
const view = { cameraHeightM: 500, viewW: 1000, viewH: 800 };
|
||||
const selected = selectCctvLod([...edge, ...center], view);
|
||||
const reordered = selectCctvLod([...center].reverse().concat([...edge].reverse()), view);
|
||||
const reordered = selectCctvLod(
|
||||
[...center].reverse().concat([...edge].reverse()),
|
||||
view,
|
||||
);
|
||||
|
||||
assert.equal(selected.cardIds.length, CCTV_AMBIENT_CARD_MIN);
|
||||
assert.equal(selected.cardIds.filter((id) => id.startsWith('center-')).length, 10);
|
||||
assert.equal(selected.cardIds.filter((id) => id.startsWith('edge-')).length, 10);
|
||||
assert.deepEqual(reordered.cardIds, selected.cardIds, 'input order cannot affect winners or ordering');
|
||||
assert.equal(
|
||||
selected.cardIds.filter((id) => id.startsWith('center-')).length,
|
||||
10,
|
||||
);
|
||||
assert.equal(
|
||||
selected.cardIds.filter((id) => id.startsWith('edge-')).length,
|
||||
10,
|
||||
);
|
||||
assert.deepEqual(
|
||||
reordered.cardIds,
|
||||
selected.cardIds,
|
||||
'input order cannot affect winners or ordering',
|
||||
);
|
||||
|
||||
const legacy = selectCctvLod(
|
||||
[...edge, ...center].map(({ sx, sy, ...candidate }) => candidate),
|
||||
{ cameraHeightM: 500 },
|
||||
);
|
||||
assert.equal(legacy.cardIds.filter((id) => id.startsWith('center-')).length, 0);
|
||||
assert.equal(
|
||||
legacy.cardIds.filter((id) => id.startsWith('center-')).length,
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
test('ineligible video, hidden, and malformed-distance rows cannot alter eligible winners', () => {
|
||||
@@ -322,13 +415,26 @@ test('ineligible video, hidden, and malformed-distance rows cannot alter eligibl
|
||||
sx: 500,
|
||||
sy: 400,
|
||||
})),
|
||||
{ id: 'hidden-outlier', distanceKm: 9_000, inView: false, sx: 900, sy: 700 },
|
||||
{
|
||||
id: 'hidden-outlier',
|
||||
distanceKm: 9_000,
|
||||
inView: false,
|
||||
sx: 900,
|
||||
sy: 700,
|
||||
},
|
||||
{ id: 'bad-distance', distanceKm: NaN, inView: true, sx: 900, sy: 700 },
|
||||
{ id: '', distanceKm: 0, inView: true, sx: 500, sy: 400 },
|
||||
];
|
||||
|
||||
assert.deepEqual(selectCctvLod([...eligible, ...noise], view).cardIds, baseline);
|
||||
assert.deepEqual(selectCctvLod([...noise].reverse().concat([...eligible].reverse()), view).cardIds, baseline);
|
||||
assert.deepEqual(
|
||||
selectCctvLod([...eligible, ...noise], view).cardIds,
|
||||
baseline,
|
||||
);
|
||||
assert.deepEqual(
|
||||
selectCctvLod([...noise].reverse().concat([...eligible].reverse()), view)
|
||||
.cardIds,
|
||||
baseline,
|
||||
);
|
||||
});
|
||||
|
||||
test('malformed-distance rows remain slot-neutral when valid candidates leave spare capacity', () => {
|
||||
@@ -339,7 +445,9 @@ test('malformed-distance rows remain slot-neutral when valid candidates leave sp
|
||||
];
|
||||
|
||||
assert.deepEqual(selectCctvLod(candidates, view).cardIds, ['valid']);
|
||||
assert.deepEqual(selectCctvLod([...candidates].reverse(), view).cardIds, ['valid']);
|
||||
assert.deepEqual(selectCctvLod([...candidates].reverse(), view).cardIds, [
|
||||
'valid',
|
||||
]);
|
||||
});
|
||||
|
||||
test('duplicate IDs are slot-neutral and choose a deterministic representative', () => {
|
||||
@@ -353,7 +461,10 @@ test('duplicate IDs are slot-neutral and choose a deterministic representative',
|
||||
{ ...center[0], distanceKm: NaN, sx: 500, sy: 400 },
|
||||
];
|
||||
|
||||
assert.deepEqual(selectCctvLod([...eligible, ...duplicates], view).cardIds, baseline);
|
||||
assert.deepEqual(
|
||||
selectCctvLod([...eligible, ...duplicates], view).cardIds,
|
||||
baseline,
|
||||
);
|
||||
assert.deepEqual(
|
||||
selectCctvLod([...duplicates, ...eligible].reverse(), view).cardIds,
|
||||
baseline,
|
||||
@@ -362,13 +473,16 @@ test('duplicate IDs are slot-neutral and choose a deterministic representative',
|
||||
});
|
||||
|
||||
test('center weighting has deterministic ID ties and preserves invalid-anchor eligibility', () => {
|
||||
const anchors = Array.from({ length: CCTV_AMBIENT_CARD_MIN - 1 }, (_, index) => ({
|
||||
id: `anchor-${String(index).padStart(2, '0')}`,
|
||||
distanceKm: index / 100,
|
||||
inView: true,
|
||||
sx: 500,
|
||||
sy: 400,
|
||||
}));
|
||||
const anchors = Array.from(
|
||||
{ length: CCTV_AMBIENT_CARD_MIN - 1 },
|
||||
(_, index) => ({
|
||||
id: `anchor-${String(index).padStart(2, '0')}`,
|
||||
distanceKm: index / 100,
|
||||
inView: true,
|
||||
sx: 500,
|
||||
sy: 400,
|
||||
}),
|
||||
);
|
||||
const tied = [
|
||||
{ id: 'tie-b', distanceKm: 10, inView: true, sx: 500, sy: 400 },
|
||||
{ id: 'tie-a', distanceKm: 10, inView: true, sx: 500, sy: 400 },
|
||||
@@ -380,13 +494,26 @@ test('center weighting has deterministic ID ties and preserves invalid-anchor el
|
||||
viewH: 800,
|
||||
});
|
||||
assert.ok(selected.cardIds.includes('tie-a'));
|
||||
assert.equal(selected.cardIds.includes('tie-b'), false, 'ID breaks an exact contested tie');
|
||||
assert.equal(selected.cardIds.includes('invalid-anchor'), false, 'invalid anchor does not displace valid winners');
|
||||
assert.equal(
|
||||
selected.cardIds.includes('tie-b'),
|
||||
false,
|
||||
'ID breaks an exact contested tie',
|
||||
);
|
||||
assert.equal(
|
||||
selected.cardIds.includes('invalid-anchor'),
|
||||
false,
|
||||
'invalid anchor does not displace valid winners',
|
||||
);
|
||||
|
||||
const onlyInvalid = selectCctvLod([
|
||||
{ id: 'invalid-only', distanceKm: 1, inView: true, sx: NaN, sy: NaN },
|
||||
], { cameraHeightM: 500, viewW: 1000, viewH: 800 });
|
||||
assert.deepEqual(onlyInvalid.cardIds, ['invalid-only'], 'defensive top-up preserves eligibility');
|
||||
const onlyInvalid = selectCctvLod(
|
||||
[{ id: 'invalid-only', distanceKm: 1, inView: true, sx: NaN, sy: NaN }],
|
||||
{ cameraHeightM: 500, viewW: 1000, viewH: 800 },
|
||||
);
|
||||
assert.deepEqual(
|
||||
onlyInvalid.cardIds,
|
||||
['invalid-only'],
|
||||
'defensive top-up preserves eligibility',
|
||||
);
|
||||
});
|
||||
|
||||
test('applyEvictionGrace keeps a fallen-out card and clears grace when it returns', () => {
|
||||
@@ -480,7 +607,11 @@ test('applyEvictionGrace never exceeds the hard cap; graced cards go first, olde
|
||||
// cam-c is newly selected and enters immediately despite grace pressure.
|
||||
assert.ok(result.keepIds.includes('cam-c'));
|
||||
assert.ok(result.keepIds.includes('cam-new'), 'newest grace entry survives');
|
||||
assert.deepEqual(result.evictIds, ['cam-old'], 'oldest grace entry evicted for the cap');
|
||||
assert.deepEqual(
|
||||
result.evictIds,
|
||||
['cam-old'],
|
||||
'oldest grace entry evicted for the cap',
|
||||
);
|
||||
});
|
||||
|
||||
test('applyEvictionGrace with a full selection keeps no graced cards', () => {
|
||||
@@ -497,9 +628,19 @@ test('applyEvictionGrace with a full selection keeps no graced cards', () => {
|
||||
});
|
||||
|
||||
test('staticFrameRefreshMs follows known pack cadences and bounds explicit values', () => {
|
||||
assert.equal(staticFrameRefreshMs({ provider: 'Austin Transportation & Public Works' }), 300_000);
|
||||
assert.equal(staticFrameRefreshMs({ provider: 'Transport for London' }), 180_000);
|
||||
assert.equal(
|
||||
staticFrameRefreshMs({ provider: 'Austin Transportation & Public Works' }),
|
||||
300_000,
|
||||
);
|
||||
assert.equal(
|
||||
staticFrameRefreshMs({ provider: 'Transport for London' }),
|
||||
180_000,
|
||||
);
|
||||
assert.equal(staticFrameRefreshMs({ provider: 'Caltrans' }), 180_000);
|
||||
assert.equal(staticFrameRefreshMs({ provider: 'Ontario 511' }), 180_000);
|
||||
assert.equal(staticFrameRefreshMs({ provider: 'TxDOT' }), 180_000);
|
||||
// Digitraffic publishes one weathercam frame per 600 s collection interval.
|
||||
assert.equal(staticFrameRefreshMs({ provider: 'Fintraffic' }), 600_000);
|
||||
assert.equal(staticFrameRefreshMs({ frameRefreshMs: 5_000 }), 60_000);
|
||||
assert.equal(staticFrameRefreshMs({ frameRefreshMs: 3_000_000 }), 1_200_000);
|
||||
assert.equal(staticFrameRefreshMs({ provider: 'Unknown Provider' }), 300_000);
|
||||
|
||||
Reference in new issue
Block a user