Make the cold-start comparison wait for the same data and attribute it

Both pages now get the same deterministic SATCAT, NASA image and wildlife
data and advance animation clocks alike; the scenario waits until each
display has published and the registries stay unchanged for two seconds,
and late-created content is attributed to its registered owner. Known
allocation counters are normalised; every slot, count and visibility is
still compared.

Infrastructure drew on attach before the camera was restored, seeding its
level of detail at the incoming engine's default view; it now draws when
the view is restored.
This commit is contained in:
German Khokhlov committed 2026-11-10 21:36:38 +04:00
1 parent 555906da6b
commit 705b7a5314
8 files changed
+587 -44

No files matched your search

+39 -34
View File
@@ -11,6 +11,7 @@ import { drawLedger } from './qa/ledger.mjs';
import {
COLD_START_GROUPS,
coldStartUrl,
coldStartReady,
compareColdStart,
probeSnapshot,
sessionExclusion,
@@ -51,42 +52,26 @@ const evidence = (strict.extra.coldStart = {
async function settle(page, cold = false) {
await whenSettled(page, { timeoutMs: 30000 });
await page.evaluate(() => {
window.__coldStartStable = null;
});
await page.waitForFunction(
(layers, cold) => {
const { dataManager, engine } = window.__godsEyeView;
engine.render.request('qa-cold-start-readiness');
return layers.every((id) => {
const entry = dataManager.layers.get(id),
module = entry?.module;
if (!entry) return false;
if (cold && module.sessionState?.includes('enabled'))
return !entry.enabled;
if (
!entry.enabled ||
!entry.initialized ||
entry.sceneDetached ||
entry.refreshing
)
return false;
const stats = module.getStats?.() ?? {};
if (stats.loading || stats.refreshing) return false;
// These layers can be enabled with no search, route or selected subject.
if (
[
'directions',
'military-awareness',
'osm-places',
'sea-marks',
'ski-pistes',
].includes(id)
)
return true;
return stats.count > 0;
});
},
`(() => {
if (!(${coldStartReady})(${JSON.stringify(layers)}, ${cold})) {
window.__coldStartStable = null;
return false;
}
const { engine } = window.__godsEyeView;
const key = JSON.stringify(['movers', 'lines', 'ground', 'imagery', 'meshes']
.flatMap((family) => engine[family].list().map(({ id, kind, count, shown }) =>
[family, id, kind, count, shown])).sort());
const now = performance.now();
const last = window.__coldStartStable;
if (!last || last.key !== key) window.__coldStartStable = { key, at: now };
// Cover the 450 ms infrastructure walk and deferred feed/geometry publication.
return last?.key === key && now - last.at >= 2000;
})()`,
{ timeout: 30000, polling: 100 },
layers,
cold,
);
await whenSettled(page, { frames: 5, timeoutMs: 30000 });
}
@@ -328,6 +313,26 @@ await strict.run({}, async (browser) => {
sample.failureSnapshot = await probeSnapshot(page, layers).catch(
() => null,
);
sample.readiness = await page
?.evaluate(
(layers) =>
Object.fromEntries(
layers.map((id) => {
const module =
window.__godsEyeView.dataManager.layers.get(id)?.module;
return [
id,
{
stats: module?.getStats?.(),
loading: module?.getLoadingDiagnostics?.(),
lod: module?.getLodDiagnostics?.(),
},
];
}),
),
layers,
)
.catch(() => null);
evidence.differences.push({
layer: layers.join(', '),
direction,
+116 -1
View File
@@ -68,6 +68,71 @@ export function coldStartUrl(href, engine) {
return url.href;
}
/** Feed completion alone does not mean its deferred display has been drawn. */
export function coldStartReady(layers, cold = false) {
const { dataManager, engine } = window.__godsEyeView;
engine.render.request('qa-cold-start-readiness');
const movers = engine.movers.list();
return layers.every((id) => {
const entry = dataManager.layers.get(id),
module = entry?.module;
if (!entry) return false;
if (cold && module.sessionState?.includes('enabled')) return !entry.enabled;
if (
!entry.enabled ||
!entry.initialized ||
entry.sceneDetached ||
entry.refreshing
)
return false;
const stats = module.getStats?.() ?? {};
if (stats.loading || stats.refreshing) return false;
if (id === 'local-dams' || id === 'local-datacenters')
return (
module.getLodDiagnostics().computed && engine.surface.tilesLoaded()
);
if (id === 'wildlife-sightings')
return (
movers.some((row) => row.id === id && row.count === 4) &&
movers.some((row) => row.id === `${id}-density` && row.count === 3)
);
if (id === 'bhote-koshi-locator')
return movers.some(
(row) =>
row.id === `${id}-bhote-koshi-incident-halo` &&
row.count === 1 &&
row.shown,
);
if (id === 'traffic')
return (
stats.count > 0 &&
movers.some(
(row) =>
row.id === 'traffic-fill' && row.count === stats.count && row.shown,
)
);
if (['flights', 'military', 'transit'].includes(id))
return (
stats.count > 0 &&
movers.some(
(row) => row.id.startsWith(id + '-') && row.count > 0 && row.shown,
)
);
// These layers can be enabled with no search, route or selected subject.
if (
[
'directions',
'military-awareness',
'osm-places',
'sea-marks',
'ski-pistes',
].includes(id)
)
return true;
return stats.count > 0;
});
}
// Share addresses round the camera to 0.0001 degrees and whole metres/degrees.
// World positions must still match within a metre; projected positions allow
// two CSS pixels for that camera quantization. Counts and identities are exact.
@@ -168,19 +233,69 @@ export async function probeSnapshot(page, layers) {
return { ...probe, ledger: await drawLedger(page) };
}
// Display allocations are not content identities. Keep every slot and its
// generation, but number these allocator-local sequences within each family.
// Raw evidence retains the original IDs. A lost or extra slot still differs.
export function normalizeAllocations(snapshot) {
const patterns = [
['imagery', /^(weather-)(\d+)$/],
['imagery', /^(wind-field-)(\d+)$/],
['imagery', /^(bhote-koshi-imagery-)(\d+)(-(?:left|right))$/],
['lines', /^(satellites-orbit-)(\d+)$/],
['movers', /^(satellites-points-)(\d+)(-\d+)$/],
];
const ids = new Map();
for (const [family, pattern] of patterns) {
const rows = snapshot.ledger.filter(
(row) => row.family === family && pattern.test(row.id),
);
const sequence = [
...new Set(rows.map((row) => Number(row.id.match(pattern)[2]))),
].sort((a, b) => a - b);
for (const row of rows) {
const [, prefix, number, suffix = ''] = row.id.match(pattern);
ids.set(
`${family}:${row.id}`,
`${prefix}${sequence.indexOf(Number(number)) + 1}${suffix}`,
);
}
}
const rename = (row, family) => ({
...row,
id: ids.get(`${family}:${row.id}`) ?? row.id,
});
return {
...snapshot,
ledger: snapshot.ledger.map((row) => rename(row, row.family)),
movers: snapshot.movers.map((row) => rename(row, 'movers')),
};
}
/** Keep raw samples and declared exclusions beside the complete difference list. */
export function compareColdStart(
switched,
cold,
{ direction, owners = {}, allow = [], sessionState = true },
) {
const ownership = normalizeAllocations({
ledger: Object.entries(owners).map(([key, layer]) => {
const colon = key.indexOf(':');
return { family: key.slice(0, colon), id: key.slice(colon + 1), layer };
}),
movers: [],
});
switched = normalizeAllocations(switched);
cold = normalizeAllocations(cold);
const differences = [],
skipped = [];
const add = (layer, family, id, rows) =>
differences.push(
...rows.map((row) => ({ layer, direction, family, id, ...row })),
);
const layerOf = (family, id) => owners[`${family}:${id}`] ?? '[page]';
const layerOf = (family, id) =>
[...switched.ledger, ...cold.ledger, ...ownership.ledger].find(
(row) => row.family === family && row.id === id && row.layer,
)?.layer ?? '[page]';
const ledger = compareLedgers(switched.ledger, cold.ledger, allow);
for (const [side, rows] of [
['switch', ledger.onlyBefore],
+152
View File
@@ -11,6 +11,8 @@ import {
compareColdStart,
sessionExclusion,
probeSnapshot,
normalizeAllocations,
coldStartReady,
} from './coldStart.mjs';
const snapshot = () => ({
@@ -256,3 +258,153 @@ test('the production probe captures all requested fields and releases its frame
assert.equal(engine.frames.postRender.numberOfListeners, listeners);
engine.destroy();
});
test('display allocation counters do not become content identities or hide lost slots', () => {
const a = snapshot(),
b = snapshot();
for (const [family, left, right, layer] of [
['imagery', 'weather-4', 'weather-1', 'weather-radar'],
['imagery', 'wind-field-2', 'wind-field-1', 'wind'],
[
'imagery',
'bhote-koshi-imagery-2-left',
'bhote-koshi-imagery-1-left',
'bhote-koshi-2026',
],
[
'imagery',
'bhote-koshi-imagery-2-right',
'bhote-koshi-imagery-1-right',
'bhote-koshi-2026',
],
['lines', 'satellites-orbit-5', 'satellites-orbit-4', 'satellites'],
['movers', 'satellites-points-4-4', 'satellites-points-5-4', 'satellites'],
]) {
a.ledger.push({ family, id: left, layer, shown: true });
b.ledger.push({ family, id: right, layer, shown: true });
if (family === 'movers') {
a.movers.push({ id: left, count: 10, capacity: 8192 });
b.movers.push({ id: right, count: 10, capacity: 8192 });
}
}
assert.deepEqual(compareColdStart(a, b, options).differences, []);
assert.equal(
a.ledger[1].id,
'weather-4',
'raw samples retain allocation IDs',
);
b.ledger.find((row) => row.id === 'weather-1').shown = false;
b.ledger = b.ledger.filter((row) => row.id !== 'bhote-koshi-imagery-1-right');
b.movers.at(-1).count--;
const differences = compareColdStart(a, b, options).differences;
assert.deepEqual(differences.map(({ field }) => field).sort(), [
'count',
'present',
'shown',
]);
assert.ok(differences.every(({ layer }) => layer !== '[page]'));
a.ledger.push({
family: 'lines',
id: 'satellites-orbit-7',
layer: 'satellites',
shown: false,
});
assert.equal(
compareColdStart(a, b, options).differences.filter(
({ family }) => family === 'lines',
).length,
1,
);
assert.equal(
normalizeAllocations(a).ledger.filter(({ family }) => family === 'lines')
.length,
2,
);
});
test('asynchronously created CCTV lines are attributed even when absent at enable', async () => {
const engine = createFakeEngine();
engine.lines.createLine({
id: 'cctv-camera-ray-tl',
widthPx: 1,
color: [1, 1, 1, 1],
});
const context = vm.createContext({
window: {
__godsEyeView: {
engine,
dataManager: { layers: new Map([['cctv', { module: {} }]]) },
},
},
});
const page = {
evaluate: async (code) =>
vm.runInContext(typeof code === 'string' ? code : `(${code})()`, context),
};
const capture = await probeSnapshot(page, ['cctv']);
assert.equal(capture.ledger[0].layer, 'cctv');
engine.destroy();
});
test('readiness waits for fixture publications, LOD and the locator reveal', () => {
const engine = createFakeEngine();
const dataManager = { layers: new Map() };
const context = vm.createContext({
window: { __godsEyeView: { engine, dataManager } },
});
const ready = (id) =>
vm.runInContext(`(${coldStartReady})([${JSON.stringify(id)}])`, context);
const entry = (id, extra = {}) =>
dataManager.layers.set(id, {
enabled: true,
initialized: true,
module: { getStats: () => ({ count: 10 }), ...extra },
});
entry('local-dams', { getLodDiagnostics: () => ({ computed: false }) });
assert.equal(ready('local-dams'), false);
dataManager.layers.get('local-dams').module.getLodDiagnostics = () => ({
computed: true,
});
assert.equal(ready('local-dams'), true);
entry('wildlife-sightings');
const animals = engine.movers.createSet({
id: 'wildlife-sightings',
kind: 'point',
capacity: 4,
point: { sizePx: 4 },
});
animals.setCount(3);
assert.equal(ready('wildlife-sightings'), false);
animals.setCount(4);
const density = engine.movers.createSet({
id: 'wildlife-sightings-density',
kind: 'point',
capacity: 3,
point: { sizePx: 4 },
});
density.setCount(3);
assert.equal(ready('wildlife-sightings'), true);
entry('bhote-koshi-locator');
const halo = engine.movers.createSet({
id: 'bhote-koshi-locator-bhote-koshi-incident-halo',
kind: 'point',
capacity: 1,
point: { sizePx: 4 },
});
assert.equal(ready('bhote-koshi-locator'), false);
halo.setCount(1);
assert.equal(ready('bhote-koshi-locator'), true);
entry('transit');
const bus = engine.movers.createSet({
id: 'transit-1',
kind: 'point',
capacity: 1,
point: { sizePx: 4 },
});
bus.set(0, { show: false });
bus.setCount(1);
assert.equal(ready('transit'), false);
bus.set(0, { show: true });
assert.equal(ready('transit'), true);
engine.destroy();
});
+22 -6
View File
@@ -19,8 +19,8 @@ import {
} from '../../src/bench/satelliteFixture.js';
import { launchFixture } from '../../src/bench/launchFixture.js';
// These are the feeds used by the strict switching scenarios, with motion and
// timestamps held fixed so page startup duration cannot change the sample.
// These are the feeds used by the strict switching scenarios. Feed timestamps
// stay fixed; wall time must advance for visibility throttles and fade-in.
export async function coldStartFixtures(origin, group) {
const epoch =
group === 'space'
@@ -41,7 +41,8 @@ export async function coldStartFixtures(origin, group) {
const routes = [];
const fixtures = [];
const route = (pattern, respond) => routes.push({ pattern, respond });
if (group === 'wildlife') routes.push(...wildlifeRoutes(origin).routes);
if (group === 'wildlife')
routes.push(...wildlifeRoutes(origin, { now: () => epoch }).routes);
if (group === 'open') {
route('https://tiles.openseamap.org/*', image);
route('https://www.opensnowmap.org/*', image);
@@ -286,12 +287,15 @@ export async function coldStartFixtures(origin, group) {
function installFeeds({ group, epoch, groups, ghost, launches, firms }) {
const RealDate = Date;
const started = performance.now();
const now = () =>
group === 'space' ? epoch : epoch + performance.now() - started;
globalThis.Date = class extends RealDate {
constructor(...args) {
super(...(args.length ? args : [epoch]));
super(...(args.length ? args : [now()]));
}
static now() {
return epoch;
return Math.floor(now());
}
};
localStorage.setItem('gev:first-run-mission:v1', 'suppressed');
@@ -558,6 +562,18 @@ function installFeeds({ group, epoch, groups, ghost, launches, firms }) {
return Promise.resolve(
new Response(groups[path.split('/').pop()] ?? groups.stations),
);
if (path.startsWith('/api/celestrak-satcat/')) {
const tle = groups[path.split('/').pop()];
if (tle) {
const lines = tle.trim().split('\n');
const rows = [
'OBJECT_NAME,NORAD_CAT_ID,OBJECT_TYPE,OPS_STATUS_CODE,OWNER',
];
for (let i = 0; i < lines.length; i += 3)
rows.push(`${lines[i]},${Number(lines[i + 1].slice(2, 7))},PAY,+,US`);
return Promise.resolve(new Response(rows.join('\n')));
}
}
if (path === '/api/launches') return json(launches);
if (path === '/api/firms') return json(firms);
if (path === '/api/route') {
@@ -588,7 +604,7 @@ function installFeeds({ group, epoch, groups, ghost, launches, firms }) {
});
// Same-origin proxies must not silently turn this into a live-feed test.
// Delegate only routes answered by the fixtures above or the strict harness.
if (path.startsWith('/api/')) {
if (url.origin === location.origin && path.startsWith('/api/')) {
if (path === '/api/google/nearby-places') return json({ places: [] });
if (path === '/api/setup/status') return json({ ready: true });
if (/^\/api\/(firms|mapillary)\/status$/.test(path) && group !== 'street')
+183 -1
View File
@@ -3,6 +3,11 @@ import test from 'node:test';
import vm from 'node:vm';
import { coldStartFixtures } from './coldStartFixtures.mjs';
import { COLD_START_GROUPS } from './coldStart.mjs';
import { createSatelliteSource } from '../../src/layers/satellites/source.js';
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';
const origin = 'http://127.0.0.1:4309';
test('every group has valid strict fixture routes', async () => {
@@ -62,7 +67,8 @@ test('independent pages receive identical stationary aircraft and timestamps thr
URL,
Response,
EventTarget,
location: { href: origin },
performance: { now: () => 0 },
location: { href: origin, origin },
localStorage: { setItem() {} },
window: {
EventSource: class {},
@@ -95,3 +101,179 @@ test('independent pages receive identical stationary aircraft and timestamps thr
'/api/unexpected-feed',
);
});
async function installedPage(group) {
const fixture = await coldStartFixtures(origin, group);
const scripts = [];
await fixture.install({
evaluateOnNewDocument: async (fn, args) => scripts.push({ fn, args }),
});
let elapsed = 0;
const forwarded = [];
const context = vm.createContext({
URL,
Response,
EventTarget,
performance: { now: () => elapsed },
location: { href: origin, origin },
localStorage: { setItem() {} },
window: {
EventSource: class {},
async fetch(input) {
const url = new URL(input.url ?? input, origin).href;
forwarded.push(url);
const route = fixture.routes.find(({ pattern }) =>
new URLPattern(pattern).test(url),
);
assert.ok(route, url);
const response = await route.respond({ url });
return new Response(response.body, {
status: response.status ?? 200,
headers: { 'content-type': response.contentType },
});
},
},
});
for (const { fn, args } of scripts)
vm.runInContext(`(${fn})(${JSON.stringify(args)})`, context);
return {
fixture,
context,
forwarded,
advance: (ms) => {
elapsed += ms;
},
fetch: (url) =>
vm.runInContext(`window.fetch(${JSON.stringify(url)})`, context),
};
}
test('the page fetch guard delegates NASA snapshot APIs to their image fixture', async () => {
const page = await installedPage('imagery');
const response = await page.fetch(
'https://wvs.earthdata.nasa.gov/api/v1/snapshot?WIDTH=256',
);
assert.equal(response.headers.get('content-type'), 'image/png');
assert.ok((await response.arrayBuffer()).byteLength > 0);
assert.equal(page.context.window.__coldStartUnexpectedApi.length, 0);
await assert.rejects(page.fetch('/api/v1/snapshot'), /No cold-start fixture/);
});
test('every TLE group has matching deterministic SATCAT records on both pages', async () => {
const a = await installedPage('space'),
b = await installedPage('space');
for (const group of [
'stations',
'visual',
'gps-ops',
'glo-ops',
'galileo',
'geo',
'starlink',
'resource',
'weather',
'planet',
'sar',
'science',
]) {
const source = createSatelliteSource({ fetchImpl: a.fetch });
const catalog = await source.readSatcat(group);
assert.equal(catalog.ok, true);
assert.equal(
catalog.text,
await (await b.fetch(`/api/celestrak-satcat/${group}`)).text(),
);
const tle = (await source.readGroup(group)).text;
assert.equal(
parseSatcatCsv(catalog.text).size,
tle.trim().split('\n').length / 3,
);
}
assert.equal(a.context.window.__coldStartUnexpectedApi.length, 0);
});
test('wall time advances for fades and visibility while the orbit epoch stays fixed', async () => {
for (const group of ['aircraft', 'traffic', 'space']) {
const page = await installedPage(group);
const before = vm.runInContext('Date.now()', page.context);
page.advance(2000);
assert.equal(
vm.runInContext('Date.now()', page.context) - before,
group === 'space' ? 0 : 2000,
);
assert.equal(
vm.runInContext('+new Date()', page.context),
vm.runInContext('Date.now()', page.context),
);
}
});
test('traffic uses deterministic road and flow tiles', async () => {
const page = await installedPage('traffic');
assert.equal(
(await (await page.fetch('/api/tomtom/status')).json()).hasKey,
true,
);
for (const url of [
'https://tiles.openfreemap.org/qa-traffic/14/3743/6745.pbf',
'/api/tomtom/flow/12/935/1686.pbf',
])
assert.ok((await (await page.fetch(url)).arrayBuffer()).byteLength > 0);
});
test('traffic dots finish their birth fade with the fixture clock', async (t) => {
const page = await installedPage('traffic');
t.mock.method(Date, 'now', () => vm.runInContext('Date.now()', page.context));
const engine = createFakeEngine();
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],
_lastAnimTime: Date.now(),
_animFrame: 0,
},
services: {},
parts: { rendering: {}, retention: { easeHeights() {} } },
});
animation.animate();
animation.drawDots();
assert.equal(
engine.movers.list().find(({ id }) => id === 'traffic-fill').count,
0,
);
page.advance(200);
animation.animate();
animation.drawDots();
assert.equal(
engine.movers.list().find(({ id }) => id === 'traffic-fill').count,
1,
);
});
+14 -1
View File
@@ -1,12 +1,25 @@
/** The five public drawing registries, with no SDK objects or volatile fields. */
export function drawLedger(page) {
return page.evaluate(() => {
const { engine } = window.__godsEyeView;
const { engine, dataManager } = window.__godsEyeView;
const owners = [...(dataManager?.layers.keys() ?? [])].sort(
(a, b) => b.length - a.length,
);
const ownerOf = (id) =>
owners.find((owner) => id === owner || id.startsWith(owner + '-')) ??
(id.startsWith('wind-field-') ? 'wind' : undefined) ??
(id.startsWith('bhote-koshi-imagery-') ||
id.startsWith('bhote-koshi-flood-')
? 'bhote-koshi-2026'
: undefined);
return ['movers', 'lines', 'ground', 'imagery', 'meshes']
.flatMap((family) =>
engine[family].list().map((row) => ({
id: String(row.id),
family,
...(ownerOf(String(row.id))
? { layer: ownerOf(String(row.id)) }
: {}),
...(row.kind === undefined ? {} : { kind: row.kind }),
...(row.count === undefined ? {} : { count: row.count }),
shown: row.shown ?? true,
+12 -1
View File
@@ -78,6 +78,7 @@ export function createInfrastructureSessionLayer(
request = null,
abort = null,
display = null,
sceneContext = null,
enabled = false,
destroyed = false;
let updated = null,
@@ -110,6 +111,8 @@ export function createInfrastructureSessionLayer(
try {
const next = await read();
if (!enabled || destroyed) return;
if (sceneContext?.viewReady === false || sceneContext?.signal?.aborted)
return;
const active = display;
if (active) {
error = null;
@@ -138,8 +141,10 @@ export function createInfrastructureSessionLayer(
init(_viewer, options = {}) {
if (options.engine) return this.attachRenderer(options);
},
attachRenderer({ engine = null, signal = null } = {}) {
attachRenderer(options = {}) {
const { engine = null, signal = null } = options;
if (display) throw Error('Infrastructure display already attached');
sceneContext = options;
display = engine ? createDisplay(engine, config, services) : null;
// The dataset load belongs to the session and keeps running; only this
// scene's wait for it ends when the scene is released. A released
@@ -149,6 +154,10 @@ export function createInfrastructureSessionLayer(
if (!signal?.aborted) throw error;
});
},
resumeViewport() {
// LOD keeps incumbents, so its first walk must use the restored camera.
if (enabled && display) return show();
},
detachRenderer() {
if (!display) return;
const selected = services.getSelectedEntityContext?.();
@@ -158,6 +167,7 @@ export function createInfrastructureSessionLayer(
: null;
display.destroy();
display = null;
sceneContext = null;
},
enable() {
enabled = true;
@@ -196,6 +206,7 @@ export function createInfrastructureSessionLayer(
abort?.abort();
display?.destroy();
display = data = null;
sceneContext = null;
},
};
}
@@ -8,6 +8,55 @@ import {
selectEntityContext,
} from '../../data/contextStore.js';
import { layerFeedState } from '../../data/feedState.js';
import { createLayerSceneContext } from '../../data/layerSceneBinding.js';
test('reattached infrastructure first computes LOD at the restored camera', async (t) => {
t.mock.method(globalThis, 'fetch', async () => ({
ok: true,
text: async () =>
JSON.stringify({
type: 'Feature',
id: 'dam',
properties: {},
geometry: { type: 'Point', coordinates: [10, 20] },
}),
}));
const draws = [];
const layer = createInfrastructureSessionLayer(
{ id: 'local-dams', url: '/dams' },
{},
(engine) => ({
show: () => draws.push(engine.camera.height()),
hide() {},
destroy() {},
}),
);
const engine = createFakeEngine();
t.after(() => {
layer.destroy();
engine.destroy();
});
layer.init(null, { engine });
await layer.enable();
layer.detachRenderer();
draws.length = 0;
let restore;
const context = createLayerSceneContext({
engine,
viewRestored: new Promise((resolve) => {
restore = resolve;
}),
});
await layer.attachRenderer(context);
engine.fake.frame();
assert.deepEqual(draws, [], 'the incoming default view never seeds LOD');
engine.fake.setPose({ height: 20000 });
restore();
await context.viewRestored;
await layer.resumeViewport();
assert.equal(draws.length, 1);
assert.ok(Math.abs(draws[0] - 20000) < 1e-6);
});
test('a bundled file delivered between planets keeps multipart IDs and is read only once', async (t) => {
let deliver,