mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 13:52:19 +00:00
Release hidden CCTV geometry and excuse level-of-detail path counts
CCTV kept the coverage and projection geometry of every camera the opening view passed on its way to the restored one; after each visibility update it now releases the coverage of cameras out of view and the planes of inactive cameras, keeping decoded media for reselection. The cold-start comparison accepts a field entry in the ledger allowlist that excuses one field of one set with a reason, and uses it for the infrastructure footprint counts: that level of detail keeps features chosen on the way to a view so they do not flicker while panning, which a cold start cannot reproduce. Presence checks ignore field entries. The fixtures give recent imagery a matching scene and date and traffic its deterministic starts, and traffic counts as drawn without every loaded vehicle visible.
This commit is contained in:
1 parent
ab41c765cc
commit
34b4fdce57
10 files changed
+312
-44
No files matched your search
@@ -1 +1,14 @@
|
||||
[]
|
||||
[
|
||||
{
|
||||
"id": "local-dams-footprints",
|
||||
"family": "ground",
|
||||
"field": "count",
|
||||
"reason": "Infrastructure level of detail keeps features chosen on the way to a view (incumbents and eviction grace, so footprints do not flicker while panning); a cold start reaches the view without that path."
|
||||
},
|
||||
{
|
||||
"id": "local-datacenters-footprints",
|
||||
"family": "ground",
|
||||
"field": "count",
|
||||
"reason": "Infrastructure level of detail keeps features chosen on the way to a view (incumbents and eviction grace, so footprints do not flicker while panning); a cold start reaches the view without that path."
|
||||
}
|
||||
]
|
||||
+25
-11
@@ -108,7 +108,7 @@ export function coldStartReady(layers, cold = false) {
|
||||
stats.count > 0 &&
|
||||
movers.some(
|
||||
(row) =>
|
||||
row.id === 'traffic-fill' && row.count === stats.count && row.shown,
|
||||
row.id === 'traffic-fill' && row.count > 0 && row.shown,
|
||||
)
|
||||
);
|
||||
if (['flights', 'military', 'transit'].includes(id))
|
||||
@@ -317,16 +317,30 @@ export function compareColdStart(
|
||||
const next = cold.ledger.find(
|
||||
(other) => other.family === row.family && other.id === row.id,
|
||||
);
|
||||
if (next)
|
||||
add(
|
||||
layerOf(row.family, row.id),
|
||||
row.family,
|
||||
row.id,
|
||||
compareProbe(
|
||||
{ kind: row.kind, count: row.count },
|
||||
{ kind: next.kind, count: next.count },
|
||||
),
|
||||
);
|
||||
if (next) {
|
||||
// A field entry excuses one field of one set, with its reason.
|
||||
const rows = compareProbe(
|
||||
{ kind: row.kind, count: row.count },
|
||||
{ kind: next.kind, count: next.count },
|
||||
).filter((difference) => {
|
||||
const exception = allow.find(
|
||||
(entry) =>
|
||||
entry.field === difference.field &&
|
||||
entry.id === row.id &&
|
||||
(!entry.family || entry.family === row.family),
|
||||
);
|
||||
if (exception)
|
||||
skipped.push({
|
||||
layer: layerOf(row.family, row.id),
|
||||
family: row.family,
|
||||
id: row.id,
|
||||
...difference,
|
||||
reason: exception.reason,
|
||||
});
|
||||
return !exception;
|
||||
});
|
||||
add(layerOf(row.family, row.id), row.family, row.id, rows);
|
||||
}
|
||||
}
|
||||
for (const layer of new Set([
|
||||
...Object.keys(switched.layers),
|
||||
|
||||
@@ -406,5 +406,47 @@ test('readiness waits for fixture publications, LOD and the locator reveal', ()
|
||||
assert.equal(ready('transit'), false);
|
||||
bus.set(0, { show: true });
|
||||
assert.equal(ready('transit'), true);
|
||||
entry('traffic');
|
||||
assert.equal(ready('traffic'), false);
|
||||
const cars = engine.movers.createSet({
|
||||
id: 'traffic-fill',
|
||||
kind: 'point',
|
||||
capacity: 10,
|
||||
point: { sizePx: 4 },
|
||||
});
|
||||
cars.setCount(9);
|
||||
assert.equal(ready('traffic'), true, 'fading vehicles need not all draw');
|
||||
for (let i = 0; i < 9; i++) cars.set(i, { show: false });
|
||||
assert.equal(ready('traffic'), false, 'hidden traffic is not ready');
|
||||
for (let i = 0; i < 9; i++) cars.set(i, { show: true });
|
||||
dataManager.layers.get('traffic').module.getStats = () => ({
|
||||
count: 10,
|
||||
loading: true,
|
||||
});
|
||||
assert.equal(ready('traffic'), false, 'population admission must finish');
|
||||
engine.destroy();
|
||||
});
|
||||
|
||||
test('a field entry excuses only that field of that set and never a missing set', () => {
|
||||
const a = snapshot(),
|
||||
b = snapshot();
|
||||
b.ledger[0].count = 2;
|
||||
const allow = [
|
||||
{ id: 'radio-selected', family: 'movers', field: 'count', reason: 'path' },
|
||||
];
|
||||
const excused = compareColdStart(a, b, { ...options, allow });
|
||||
assert.deepEqual(
|
||||
excused.differences.filter((row) => row.family === 'movers'),
|
||||
[],
|
||||
);
|
||||
assert.ok(excused.skipped.some((row) => row.reason === 'path'));
|
||||
// The radio marker is declared session state; compare it as plain content.
|
||||
b.ledger = [];
|
||||
assert.ok(
|
||||
compareColdStart(a, b, {
|
||||
...options,
|
||||
allow,
|
||||
sessionState: false,
|
||||
}).differences.some((row) => row.field === 'present'),
|
||||
);
|
||||
});
|
||||
@@ -25,9 +25,11 @@ export async function coldStartFixtures(origin, group) {
|
||||
const epoch =
|
||||
group === 'space'
|
||||
? SATELLITE_FIXTURE_EPOCH
|
||||
: group === 'urban'
|
||||
? TRANSIT_EPOCH
|
||||
: Date.now();
|
||||
: group === 'imagery'
|
||||
? Date.parse('2026-10-29T12:00:00Z')
|
||||
: group === 'urban'
|
||||
? TRANSIT_EPOCH
|
||||
: Date.now();
|
||||
const png = await sharp({
|
||||
create: { width: 256, height: 256, channels: 4, background: '#40506a' },
|
||||
})
|
||||
@@ -298,6 +300,9 @@ function installFeeds({ group, epoch, groups, ghost, launches, firms }) {
|
||||
return Math.floor(now());
|
||||
}
|
||||
};
|
||||
// Mid-road starts keep the traffic population visible through birth fades
|
||||
// without random road-end recycling during the comparison.
|
||||
if (group === 'traffic') Math.random = () => 0.5;
|
||||
localStorage.setItem('gev:first-run-mission:v1', 'suppressed');
|
||||
window.__coldStartUnexpectedApi = [];
|
||||
const original = window.fetch.bind(window);
|
||||
@@ -315,6 +320,23 @@ function installFeeds({ group, epoch, groups, ghost, launches, firms }) {
|
||||
window.fetch = (input, options) => {
|
||||
const url = new URL(input.url ?? input, location.href),
|
||||
path = url.pathname;
|
||||
// The image fixture also needs the snapshot API's explicit pixel proof.
|
||||
if (
|
||||
group === 'imagery' &&
|
||||
url.hostname === 'wvs.earthdata.nasa.gov' &&
|
||||
path.startsWith('/api/v1/snapshot')
|
||||
)
|
||||
return original(input, options).then((response) =>
|
||||
response.ok
|
||||
? new Response(response.body, {
|
||||
status: response.status,
|
||||
headers: {
|
||||
...Object.fromEntries(response.headers),
|
||||
'Data-Present': 'true',
|
||||
},
|
||||
})
|
||||
: response,
|
||||
);
|
||||
if (path === '/api/flights')
|
||||
return json({
|
||||
time: epoch / 1000,
|
||||
|
||||
@@ -8,6 +8,8 @@ import { parseSatcatCsv } from '../../src/layers/satellites/classes.js';
|
||||
import { createFakeEngine } from '../../src/globe/testing/fakeEngine.js';
|
||||
import { createTrafficDisplay } from '../../src/layers/traffic/render/display.js';
|
||||
import { createAnimation } from '../../src/layers/traffic/animation.js';
|
||||
import { createRecentImagerySource } from '../../src/layers/recentImagery/source.js';
|
||||
import { createThumbnailLoader } from '../../src/layers/recentImagery/thumbnails.js';
|
||||
|
||||
const origin = 'http://127.0.0.1:4309';
|
||||
test('every group has valid strict fixture routes', async () => {
|
||||
@@ -221,48 +223,81 @@ test('traffic uses deterministic road and flow tiles', async () => {
|
||||
assert.ok((await (await page.fetch(url)).arrayBuffer()).byteLength > 0);
|
||||
});
|
||||
|
||||
test('traffic dots finish their birth fade with the fixture clock', async (t) => {
|
||||
test('imagery fixtures admit a recent S30 scene through the real thumbnail loader', async (t) => {
|
||||
const page = await installedPage('imagery');
|
||||
const source = createRecentImagerySource({ fetchImpl: page.fetch });
|
||||
const box = { west: -97.77, east: -97.71, south: 30.25, north: 30.29 };
|
||||
const now = new Date(page.fixture.epoch);
|
||||
const { candidates } = await source.searchHls({ box, now });
|
||||
const candidate = candidates.find((row) => row.product === 'S30');
|
||||
assert.ok(candidate);
|
||||
assert.ok(now - Date.parse(candidate.day) < 3 * 86_400_000);
|
||||
const loader = createThumbnailLoader({
|
||||
requestThumbnail: source.getThumbnail,
|
||||
createObjectUrl: () => 'blob:fixture',
|
||||
revokeObjectUrl() {},
|
||||
});
|
||||
t.after(() => loader.destroy());
|
||||
const finished = new Promise((resolve) => {
|
||||
loader.subscribe((key) => {
|
||||
const state = loader.get(key);
|
||||
if (!state.loading && state.status !== 'unknown') resolve(state);
|
||||
});
|
||||
});
|
||||
loader.request(candidate, box);
|
||||
const state = await finished;
|
||||
assert.equal(state.status, 'present');
|
||||
assert.equal(state.objectUrl, 'blob:fixture');
|
||||
});
|
||||
|
||||
test('traffic fixture dots finish their birth fade and stay visible during comparison', async (t) => {
|
||||
const page = await installedPage('traffic');
|
||||
t.mock.method(Date, 'now', () => vm.runInContext('Date.now()', page.context));
|
||||
const engine = createFakeEngine();
|
||||
t.mock.method(Math, 'random', () =>
|
||||
vm.runInContext('Math.random()', page.context),
|
||||
);
|
||||
const engine = createFakeEngine({ altitude: 1800 });
|
||||
const display = createTrafficDisplay(engine);
|
||||
t.after(() => {
|
||||
display.destroy();
|
||||
engine.destroy();
|
||||
});
|
||||
const point = {
|
||||
position: { x: 6378137, y: 0, z: 0 },
|
||||
show: true,
|
||||
pixelSize: 6,
|
||||
color: { red: 1, green: 1, blue: 1, alpha: 1 },
|
||||
outlineWidth: 0,
|
||||
};
|
||||
const dot = {
|
||||
point,
|
||||
born: Date.now(),
|
||||
nominalSize: 6,
|
||||
direction: 1,
|
||||
segIdx: 0,
|
||||
numSegments: 1,
|
||||
t: 0.5,
|
||||
mps: 1,
|
||||
stoppedUntil: 0,
|
||||
segmentDist: [1000],
|
||||
waypoints: [point.position, { x: 6378137, y: 1000, z: 0 }],
|
||||
};
|
||||
const animation = createAnimation({
|
||||
state: {
|
||||
_enabled: true,
|
||||
_renderHeld: true,
|
||||
_engine: engine,
|
||||
_display: display,
|
||||
_dots: [dot],
|
||||
_dots: [],
|
||||
_scratchLerp: {},
|
||||
_nextDotId: 1,
|
||||
_speedScale: 1,
|
||||
_fadeScaleFar: 100000,
|
||||
_fadeTransFar: 100000,
|
||||
_bucketCounts: { sim: 0 },
|
||||
_motion: { added: 0, recycled: 0 },
|
||||
_lastAnimTime: Date.now(),
|
||||
_animFrame: 0,
|
||||
},
|
||||
services: {},
|
||||
parts: { rendering: {}, retention: { easeHeights() {} } },
|
||||
parts: {
|
||||
rendering: {},
|
||||
retention: { easeHeights() {} },
|
||||
style: { baseDotSize: () => 6, activeSizeDelta: () => 0 },
|
||||
},
|
||||
});
|
||||
animation.spawnDotsForRoad(
|
||||
{
|
||||
type: 'primary',
|
||||
segmentDist: [10000],
|
||||
waypoints: [
|
||||
{ x: 6378137, y: 0, z: 0 },
|
||||
{ x: 6378137, y: 10000, z: 0 },
|
||||
],
|
||||
},
|
||||
1800,
|
||||
10,
|
||||
);
|
||||
animation.animate();
|
||||
animation.drawDots();
|
||||
assert.equal(
|
||||
@@ -274,6 +309,15 @@ test('traffic dots finish their birth fade with the fixture clock', async (t) =>
|
||||
animation.drawDots();
|
||||
assert.equal(
|
||||
engine.movers.list().find(({ id }) => id === 'traffic-fill').count,
|
||||
1,
|
||||
10,
|
||||
);
|
||||
for (let i = 0; i < 300; i++) {
|
||||
page.advance(100);
|
||||
animation.animate();
|
||||
animation.drawDots();
|
||||
assert.equal(
|
||||
engine.movers.list().find(({ id }) => id === 'traffic-fill').count,
|
||||
10,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -42,7 +42,9 @@ export function compareLedgers(before, after, allow = []) {
|
||||
if (b.has(key)) return [];
|
||||
const exception = allow.find(
|
||||
(entry) =>
|
||||
entry.id === row.id && (!entry.family || entry.family === row.family),
|
||||
!entry.field &&
|
||||
entry.id === row.id &&
|
||||
(!entry.family || entry.family === row.family),
|
||||
);
|
||||
if (exception) {
|
||||
allowed.push({
|
||||
|
||||
@@ -818,8 +818,11 @@ test('real active monitor plane owns one protected host label and no native labe
|
||||
showProjection: false,
|
||||
});
|
||||
refreshCoverageStyles();
|
||||
assert.equal(runtime.planeEntity.show, false);
|
||||
assert.equal(primitive(runtime.planeEntity.id).show, false);
|
||||
assert.equal(runtime.planeEntity, null);
|
||||
assert.equal(
|
||||
engine.lines.list().some((line) => line.id.endsWith('-plane-outline')),
|
||||
false,
|
||||
);
|
||||
assert.deepEqual(calls.slice(-2), [
|
||||
['clear', 'cctv-projection'],
|
||||
['visible', 'cctv-projection', false],
|
||||
@@ -1210,7 +1213,7 @@ test('CCTV deselect publishes one null state and leaves the complete camera pose
|
||||
assert.equal(publications.at(-1).activeCameraId, null);
|
||||
assert.equal(publications.at(-1).activeCamera, null);
|
||||
assert.equal(publications.at(-1).enabled, true);
|
||||
assert.equal(runtime.planeEntity.show, false);
|
||||
assert.equal(runtime.planeEntity, null);
|
||||
assert.equal(record.probeClampRangeM, null);
|
||||
assert.equal(record.activationDone, false);
|
||||
assert.deepEqual(engine.camera.pose(), pose);
|
||||
|
||||
@@ -744,6 +744,32 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
||||
);
|
||||
}
|
||||
|
||||
function releaseInactiveGeometry(activeId, visibleIds) {
|
||||
const removed = new Set();
|
||||
for (const record of layerState._records) {
|
||||
if (record.camera.id === activeId) continue;
|
||||
if (!visibleIds.has(record.camera.id)) {
|
||||
for (const entity of record.coverageEntities || []) {
|
||||
parts.renderer.removeCoverageEntity(entity);
|
||||
removed.add(entity);
|
||||
}
|
||||
record.coverageEntities = [];
|
||||
}
|
||||
const runtime = record.projection;
|
||||
if (runtime?.planeEntity) {
|
||||
// Keep decoded media for reselection, but only the active plane draws.
|
||||
parts.renderer.removePlane(runtime);
|
||||
runtime.planeEntity = runtime.planeMaterial = null;
|
||||
runtime.planeVisible = false;
|
||||
runtime.needsPlane = true;
|
||||
}
|
||||
}
|
||||
if (removed.size)
|
||||
layerState._coverageEntities = layerState._coverageEntities.filter(
|
||||
(entity) => !removed.has(entity),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all coverage entities and projection runtimes from the scene.
|
||||
*/
|
||||
@@ -790,6 +816,7 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
||||
registerCoverageEntities,
|
||||
ensureActiveCoverageEntities,
|
||||
ensureVisibleCoverageEntities,
|
||||
releaseInactiveGeometry,
|
||||
destroyCoverageEntities,
|
||||
};
|
||||
}
|
||||
@@ -426,6 +426,7 @@ export function createRendering({
|
||||
}
|
||||
}
|
||||
|
||||
parts.geometry.releaseInactiveGeometry(activeId, coverageVisible);
|
||||
parts.renderer?.setMarkersVisible(!!layerState._enabled);
|
||||
parts.projection.pauseInactiveProjectionFeeds(activeId);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRendering } from './rendering.js';
|
||||
import { createGeometry } from './geometry.js';
|
||||
import {
|
||||
ACTIVE_CAMERA_COLOR,
|
||||
IDLE_CAMERA_COLOR,
|
||||
@@ -77,3 +78,102 @@ test('focus de-emphasis fades each icon from its own color', () => {
|
||||
);
|
||||
assert.equal(state._activeFocusStyleCount, 3);
|
||||
});
|
||||
|
||||
test('coverage refresh releases departed geometry and preserves media for reselection', () => {
|
||||
const records = ['opening', 'final', 'neighbor'].map((id) => ({
|
||||
camera: { id, headingConfidence: 'high' },
|
||||
frustumGeometry: {},
|
||||
frustumPositions: {},
|
||||
coverageEntities: [],
|
||||
}));
|
||||
const state = {
|
||||
_enabled: true,
|
||||
_coverageMode: 'on',
|
||||
_showProjection: true,
|
||||
_records: records,
|
||||
_coverageEntities: [],
|
||||
};
|
||||
let active = records[0];
|
||||
let visible = new Set(['opening']);
|
||||
const lines = new Set();
|
||||
const planes = new Set();
|
||||
const services = { ground: {}, focus: {} };
|
||||
const parts = {
|
||||
renderer: {
|
||||
ready: true,
|
||||
createCoverage(id) {
|
||||
const line = { id, show: false };
|
||||
lines.add(line);
|
||||
return [line];
|
||||
},
|
||||
removeCoverageEntity: (line) => lines.delete(line),
|
||||
styleLine: (line, { visible }) => {
|
||||
line.show = visible;
|
||||
},
|
||||
removePlane: (runtime) => planes.delete(runtime.planeEntity),
|
||||
setMarkersVisible() {},
|
||||
},
|
||||
selection: { getActiveRecord: () => active },
|
||||
model: { isBrowserOverlayCamera: () => false },
|
||||
projection: {
|
||||
ensureProjectionRuntime(record) {
|
||||
record.projection ??= { canvas: {} };
|
||||
const runtime = record.projection;
|
||||
if (!runtime.planeEntity) {
|
||||
runtime.planeEntity = {};
|
||||
runtime.needsPlane = false;
|
||||
planes.add(runtime.planeEntity);
|
||||
}
|
||||
},
|
||||
setPlaneVisible: (runtime, value) => {
|
||||
runtime.planeVisible = value;
|
||||
},
|
||||
pauseInactiveProjectionFeeds() {},
|
||||
},
|
||||
};
|
||||
parts.geometry = createGeometry({ state, services, parts });
|
||||
parts.geometry.buildCoverageVisibleSet = () => visible;
|
||||
const rendering = createRendering({ state, services, parts });
|
||||
rendering.refreshCoverageStyles();
|
||||
const openingLine = records[0].coverageEntities[0];
|
||||
const media = records[0].projection.canvas;
|
||||
assert.equal(
|
||||
openingLine.show,
|
||||
true,
|
||||
'startup without a restore draws immediately',
|
||||
);
|
||||
active = records[1];
|
||||
visible = new Set(['final', 'neighbor']);
|
||||
rendering.refreshCoverageStyles();
|
||||
assert.equal(lines.has(openingLine), false);
|
||||
assert.deepEqual(records[0].coverageEntities, []);
|
||||
assert.equal(records[0].projection.planeEntity, null);
|
||||
assert.equal(records[0].projection.needsPlane, true);
|
||||
assert.equal(records[0].projection.canvas, media);
|
||||
assert.deepEqual(
|
||||
[...lines].map((line) => line.id),
|
||||
['final', 'neighbor'],
|
||||
);
|
||||
assert.ok([...lines].every((line) => line.show));
|
||||
assert.deepEqual(state._coverageEntities, [...lines]);
|
||||
assert.equal(planes.size, 1);
|
||||
const finalLine = records[1].coverageEntities[0];
|
||||
rendering.refreshCoverageStyles();
|
||||
assert.equal(
|
||||
records[1].coverageEntities[0],
|
||||
finalLine,
|
||||
'stationary geometry is reused',
|
||||
);
|
||||
active = records[0];
|
||||
visible = new Set();
|
||||
rendering.refreshCoverageStyles();
|
||||
assert.equal(
|
||||
lines.size,
|
||||
1,
|
||||
'active projection keeps its coverage outside the visible cohort',
|
||||
);
|
||||
assert.equal(records[0].coverageEntities[0].show, true);
|
||||
assert.notEqual(records[0].coverageEntities[0], openingLine);
|
||||
assert.equal(records[0].projection.canvas, media);
|
||||
assert.equal(planes.size, 1);
|
||||
});
|
||||
Reference in new issue
Block a user