mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
fix(infrastructure): stop idle republishing and respect settled terrain
Skip unchanged overlay cohorts while detecting moved positions and lifecycle changes. Defer ground sampling until visible terrain settles, retain the loaded surface as a floor, and preserve roof heights and bounded retries. Keep regression and idle-render gates intact.
This commit is contained in:
1 parent
6dd00d1e76
commit
b5568de9fa
6 files changed
+124
-7
No files matched your search
@@ -13,6 +13,10 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
- Unchanged local infrastructure overlays no longer sustain idle rendering.
|
||||||
|
Ground samples wait for visible terrain to settle and cannot place a marker
|
||||||
|
below its loaded surface; roofs and valid below-sea-level heights are retained.
|
||||||
|
|
||||||
- Datacenter and dam marker stems use bounded, zoom-dependent active sets with
|
- Datacenter and dam marker stems use bounded, zoom-dependent active sets with
|
||||||
stable selection during camera motion. Close-up stems scale to the actual
|
stable selection during camera motion. Close-up stems scale to the actual
|
||||||
camera distance; source totals and submarine cables remain unchanged.
|
camera distance; source totals and submarine cables remain unchanged.
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ and 420 below 200 km. Selection favors existing label priority and nearby
|
|||||||
features, retains stable choices across small camera movements, and refreshes
|
features, retains stable choices across small camera movements, and refreshes
|
||||||
during continuous motion. Disable/re-enable resets selection cleanly.
|
during continuous motion. Disable/re-enable resets selection cleanly.
|
||||||
Close-up stem sizing uses camera-to-feature distance without a 5 km minimum.
|
Close-up stem sizing uses camera-to-feature distance without a 5 km minimum.
|
||||||
|
Unchanged overlay entries do not request another frame; moved positions,
|
||||||
|
changed membership and re-enabling still publish. Ground sampling waits for
|
||||||
|
visible globe tiles to settle and uses valid loaded terrain as a floor while
|
||||||
|
retaining rooftop and below-sea-level elevations. A hidden globe does not
|
||||||
|
constrain photoreal geometry, and retry work remains bounded.
|
||||||
The submarine-cable renderer is unchanged. These limits bound active stem work;
|
The submarine-cable renderer is unchanged. These limits bound active stem work;
|
||||||
they do not reduce the materialized entity count or establish an FPS gain.
|
they do not reduce the materialized entity count or establish an FPS gain.
|
||||||
|
|
||||||
|
|||||||
@@ -296,12 +296,27 @@ try {
|
|||||||
|
|
||||||
// ── parked-idle honesty: the LOD walk must not force continuous render ─
|
// ── parked-idle honesty: the LOD walk must not force continuous render ─
|
||||||
await new Promise((r) => setTimeout(r, 4_000));
|
await new Promise((r) => setTimeout(r, 4_000));
|
||||||
const idle = await page.evaluate(() => new Promise((resolve) => {
|
const idle = await page.evaluate(async () => {
|
||||||
const scene = window.__godsEyeView.viewer.scene;
|
const { getRenderGovernorDiagnostics } = await import('/src/renderGovernor.js');
|
||||||
let renders = 0;
|
return new Promise((resolve) => {
|
||||||
const remove = scene.postRender.addEventListener(() => { renders += 1; });
|
const scene = window.__godsEyeView.viewer.scene;
|
||||||
setTimeout(() => { remove(); resolve({ renders }); }, 5_000);
|
const startedAt = Date.now();
|
||||||
}));
|
const tilesLoadedBefore = scene.globe.tilesLoaded;
|
||||||
|
let renders = 0;
|
||||||
|
let framesWithPendingTiles = 0;
|
||||||
|
const remove = scene.postRender.addEventListener(() => {
|
||||||
|
renders += 1;
|
||||||
|
if (!scene.globe.tilesLoaded) framesWithPendingTiles += 1;
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
remove();
|
||||||
|
const governor = getRenderGovernorDiagnostics();
|
||||||
|
resolve({ renders, tilesLoadedBefore, tilesLoadedAfter: scene.globe.tilesLoaded,
|
||||||
|
framesWithPendingTiles, mode: governor.mode, holds: governor.holds,
|
||||||
|
requests: governor.recentRequests.filter(request => request.at >= startedAt) });
|
||||||
|
}, 5_000);
|
||||||
|
});
|
||||||
|
});
|
||||||
report(CONTROL
|
report(CONTROL
|
||||||
? 'parked idle, control (postRender fires / 5s)'
|
? 'parked idle, control (postRender fires / 5s)'
|
||||||
: 'parked idle with infra ON (postRender fires / 5s)', idle);
|
: 'parked idle with infra ON (postRender fires / 5s)', idle);
|
||||||
|
|||||||
@@ -225,6 +225,11 @@ export function createLocalInfrastructureOverlayPublisher({
|
|||||||
let visible = false;
|
let visible = false;
|
||||||
let published = false;
|
let published = false;
|
||||||
let destroyed = false;
|
let destroyed = false;
|
||||||
|
// Local entries have immutable metadata and a mutable Cartesian position.
|
||||||
|
// Snapshot coordinates: retaining only the entry reference would miss a
|
||||||
|
// stem tip moving in place. Republishing an unchanged cohort invalidates
|
||||||
|
// the host and can sustain a render loop when frames exceed the 450 ms walk.
|
||||||
|
let lastPublication = null;
|
||||||
const sourceOptions = {
|
const sourceOptions = {
|
||||||
cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT,
|
cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT,
|
||||||
collisionCapacity: LOCAL_OVERLAY_COLLISION_CAPACITY,
|
collisionCapacity: LOCAL_OVERLAY_COLLISION_CAPACITY,
|
||||||
@@ -239,7 +244,18 @@ export function createLocalInfrastructureOverlayPublisher({
|
|||||||
},
|
},
|
||||||
publish(entries) {
|
publish(entries) {
|
||||||
if (destroyed || !visible) return;
|
if (destroyed || !visible) return;
|
||||||
|
if (lastPublication && entries.length === lastPublication.length
|
||||||
|
&& entries.every((entry, index) => {
|
||||||
|
const previous = lastPublication[index];
|
||||||
|
return entry === previous.entry
|
||||||
|
&& entry.position?.x === previous.x
|
||||||
|
&& entry.position?.y === previous.y
|
||||||
|
&& entry.position?.z === previous.z;
|
||||||
|
})) return;
|
||||||
host.setEntries(sourceId, entries, sourceOptions);
|
host.setEntries(sourceId, entries, sourceOptions);
|
||||||
|
lastPublication = entries.map(entry => ({
|
||||||
|
entry, x: entry.position?.x, y: entry.position?.y, z: entry.position?.z,
|
||||||
|
}));
|
||||||
published = entries.length > 0;
|
published = entries.length > 0;
|
||||||
},
|
},
|
||||||
hide() {
|
hide() {
|
||||||
@@ -248,6 +264,7 @@ export function createLocalInfrastructureOverlayPublisher({
|
|||||||
if (visible) host.setVisible(sourceId, false);
|
if (visible) host.setVisible(sourceId, false);
|
||||||
visible = false;
|
visible = false;
|
||||||
published = false;
|
published = false;
|
||||||
|
lastPublication = null;
|
||||||
},
|
},
|
||||||
destroy() {
|
destroy() {
|
||||||
if (destroyed) return;
|
if (destroyed) return;
|
||||||
@@ -255,6 +272,7 @@ export function createLocalInfrastructureOverlayPublisher({
|
|||||||
if (visible) host.setVisible(sourceId, false);
|
if (visible) host.setVisible(sourceId, false);
|
||||||
visible = false;
|
visible = false;
|
||||||
published = false;
|
published = false;
|
||||||
|
lastPublication = null;
|
||||||
destroyed = true;
|
destroyed = true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -897,6 +915,8 @@ function sampleLocalGroundHeight(viewer, record, now) {
|
|||||||
if (record.groundSampled || !viewer.scene.sampleHeightSupported) return false;
|
if (record.groundSampled || !viewer.scene.sampleHeightSupported) return false;
|
||||||
if (now - record.lastGroundSampleMs < GROUND_SAMPLE_RETRY_MS) return false;
|
if (now - record.lastGroundSampleMs < GROUND_SAMPLE_RETRY_MS) return false;
|
||||||
record.lastGroundSampleMs = now;
|
record.lastGroundSampleMs = now;
|
||||||
|
const globe = viewer.scene.globe;
|
||||||
|
if (globe?.show && globe.tilesLoaded === false) return false;
|
||||||
let sampled;
|
let sampled;
|
||||||
try {
|
try {
|
||||||
sampled = viewer.scene.sampleHeight(record.carto, [record.entity]);
|
sampled = viewer.scene.sampleHeight(record.carto, [record.entity]);
|
||||||
@@ -904,6 +924,14 @@ function sampleLocalGroundHeight(viewer, record, now) {
|
|||||||
return false; // tiles not ready; retry on a later bounded update
|
return false; // tiles not ready; retry on a later bounded update
|
||||||
}
|
}
|
||||||
if (!Number.isFinite(sampled) || Math.abs(sampled) > GROUND_SAMPLE_MAX_ABS_HEIGHT_M) return false;
|
if (!Number.isFinite(sampled) || Math.abs(sampled) > GROUND_SAMPLE_MAX_ABS_HEIGHT_M) return false;
|
||||||
|
// A coarse scene-depth sample can lie below the terrain already loaded by
|
||||||
|
// the visible globe. Keep that terrain as a floor while allowing roofs and
|
||||||
|
// valid below-sea-level elevations. Photoreal scenes hide the globe, so its
|
||||||
|
// inactive terrain must not constrain their geometry.
|
||||||
|
const terrainHeight = globe?.show ? globe.getHeight?.(record.carto) : undefined;
|
||||||
|
if (Number.isFinite(terrainHeight) && Math.abs(terrainHeight) <= GROUND_SAMPLE_MAX_ABS_HEIGHT_M) {
|
||||||
|
sampled = Math.max(sampled, terrainHeight);
|
||||||
|
}
|
||||||
record.groundSampled = true;
|
record.groundSampled = true;
|
||||||
record.groundHeight = sampled;
|
record.groundHeight = sampled;
|
||||||
Cesium.Cartesian3.fromRadians(
|
Cesium.Cartesian3.fromRadians(
|
||||||
|
|||||||
@@ -314,6 +314,33 @@ test('local overlay publisher owns add/remove/visibility lifecycle and becomes i
|
|||||||
assert.deepEqual(calls[7], ['visible', 'local-datacenters', false]);
|
assert.deepEqual(calls[7], ['visible', 'local-datacenters', false]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('slow parked frames do not keep republishing the same local overlay cohort', async (t) => {
|
||||||
|
const env = await createRealLocalLayerHarness();
|
||||||
|
const clock = installFakeClock(t);
|
||||||
|
t.after(() => { env.layer.destroy(env.viewer); env.cleanup(); });
|
||||||
|
const publications = () => env.hostCalls.filter(([type]) => type === 'entries');
|
||||||
|
env.preRender.raise();
|
||||||
|
assert.equal(publications().length, 1);
|
||||||
|
assert.ok(publications()[0][2].length > 0, 'the initial cohort must be populated');
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
clock.advance(1_000); // Every slow frame passes the 450 ms source throttle.
|
||||||
|
env.preRender.raise();
|
||||||
|
}
|
||||||
|
assert.equal(publications().length, 1,
|
||||||
|
'unchanged publication invalidates the real host and requests another slow frame');
|
||||||
|
|
||||||
|
const camera = env.viewer.camera.positionWC;
|
||||||
|
env.viewer.camera.positionWC = Cesium.Cartesian3.add(camera,
|
||||||
|
new Cesium.Cartesian3(10_000, 0, 0), new Cesium.Cartesian3());
|
||||||
|
env.moveEnd.raise();
|
||||||
|
env.preRender.raise();
|
||||||
|
assert.equal(publications().length, 2, 'changed stem positions must still reach the host');
|
||||||
|
env.layer.disable(env.viewer);
|
||||||
|
await env.layer.enable(env.viewer);
|
||||||
|
env.preRender.raise();
|
||||||
|
assert.equal(publications().length, 3, 're-enable must republish after clearing the host');
|
||||||
|
});
|
||||||
|
|
||||||
test('real layer disable clears its published host entries and balances settle listeners', async () => {
|
test('real layer disable clears its published host entries and balances settle listeners', async () => {
|
||||||
const env = await createRealLocalLayerHarness();
|
const env = await createRealLocalLayerHarness();
|
||||||
env.preRender.raise();
|
env.preRender.raise();
|
||||||
@@ -683,6 +710,44 @@ function governorReasons() {
|
|||||||
return getRenderGovernorDiagnostics().recentRequests.map((entry) => entry.reason);
|
return getRenderGovernorDiagnostics().recentRequests.map((entry) => entry.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('ground sampling waits for visible globe tiles before caching a height', async (t) => {
|
||||||
|
const env = await createRealLocalLayerHarness({
|
||||||
|
sampleHeightSupported: true, sampleHeight: () => 117,
|
||||||
|
});
|
||||||
|
const clock = installFakeClock(t);
|
||||||
|
t.after(() => { env.layer.destroy(env.viewer); env.cleanup(); });
|
||||||
|
env.viewer.scene.globe = { show: true, tilesLoaded: false, getHeight: () => 117 };
|
||||||
|
setCameraAltitude(env, 20_000);
|
||||||
|
env.preRender.raise();
|
||||||
|
assert.equal(env.sampleCalls.count, 0, 'streaming terrain must not become a permanent coarse sample');
|
||||||
|
assert.ok(Math.abs(baseHeightM(env)) < 0.01);
|
||||||
|
env.viewer.scene.globe.tilesLoaded = true;
|
||||||
|
clock.advance(2_100);
|
||||||
|
env.preRender.raise();
|
||||||
|
assert.equal(env.sampleCalls.count, 1, 'the existing retry must sample the settled scene');
|
||||||
|
assert.ok(Math.abs(baseHeightM(env) - 117) < 0.01);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [terrainHeight, sampledHeight, expectedHeight, globeShown] of [
|
||||||
|
[117, -2238, 117, true],
|
||||||
|
[-400, -410, -400, true],
|
||||||
|
[117, 180, 180, true],
|
||||||
|
[117, -20, -20, false],
|
||||||
|
]) {
|
||||||
|
test(`ground sampling respects terrain ${terrainHeight} and geometry ${sampledHeight} with globe ${globeShown}`, async (t) => {
|
||||||
|
const env = await createRealLocalLayerHarness({
|
||||||
|
sampleHeightSupported: true, sampleHeight: () => sampledHeight,
|
||||||
|
});
|
||||||
|
installFakeClock(t);
|
||||||
|
t.after(() => { env.layer.destroy(env.viewer); env.cleanup(); });
|
||||||
|
env.viewer.scene.globe = { show: globeShown, getHeight: () => terrainHeight };
|
||||||
|
setCameraAltitude(env, 20_000);
|
||||||
|
env.preRender.raise();
|
||||||
|
assert.ok(Math.abs(baseHeightM(env) - expectedHeight) < 0.01,
|
||||||
|
`base ${baseHeightM(env)} must respect the visible terrain without flattening roofs or below-sea-level terrain`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
test('a failed ground sample schedules the retry frame the idle governor would never produce', async (t) => {
|
test('a failed ground sample schedules the retry frame the idle governor would never produce', async (t) => {
|
||||||
const env = await createRealLocalLayerHarness({ sampleHeightSupported: true });
|
const env = await createRealLocalLayerHarness({ sampleHeightSupported: true });
|
||||||
_resetRenderGovernorForTest();
|
_resetRenderGovernorForTest();
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ test('infraLodBudget band boundaries are inclusive at the lower edge', () => {
|
|||||||
assert.deepEqual(infraLodBudget(INFRA_LOD_REGIONAL_HEIGHT_M - 1), { activeLimit: INFRA_LOD_ACTIVE_MAX });
|
assert.deepEqual(infraLodBudget(INFRA_LOD_REGIONAL_HEIGHT_M - 1), { activeLimit: INFRA_LOD_ACTIVE_MAX });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('infraLodBudget treats non-finite / negative height as the global (cheapest) band', () => {
|
test('infraLodBudget uses the global band for non-finite height and clamps negative height to zero', () => {
|
||||||
assert.deepEqual(infraLodBudget(undefined), { activeLimit: INFRA_LOD_ACTIVE_MIN });
|
assert.deepEqual(infraLodBudget(undefined), { activeLimit: INFRA_LOD_ACTIVE_MIN });
|
||||||
assert.deepEqual(infraLodBudget(NaN), { activeLimit: INFRA_LOD_ACTIVE_MIN });
|
assert.deepEqual(infraLodBudget(NaN), { activeLimit: INFRA_LOD_ACTIVE_MIN });
|
||||||
assert.deepEqual(infraLodBudget(Number.POSITIVE_INFINITY), { activeLimit: INFRA_LOD_ACTIVE_MIN });
|
assert.deepEqual(infraLodBudget(Number.POSITIVE_INFINITY), { activeLimit: INFRA_LOD_ACTIVE_MIN });
|
||||||
|
|||||||
Reference in new issue
Block a user