From ed6b6114cf88f32ff821fb90b98987c56a5902fe Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Wed, 11 Nov 2026 02:07:36 +0400 Subject: [PATCH] Enable the Bhote Koshi event on Cesium while 3D tiles are shown Under photoreal tiles Cesium hides the globe, so its imagery layers never request tiles and the event's imagery install waited for a frame that never came: turning the story on with 3D tiles on never finished. The decoded images are installed hidden and shown afterwards by the same rule as the presentation mode. --- src/data/bhoteKoshiEvent.js | 11 ++++- src/data/bhoteKoshiEvent.test.mjs | 76 +++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/data/bhoteKoshiEvent.js b/src/data/bhoteKoshiEvent.js index 2cfc5485c..19a2640db 100644 --- a/src/data/bhoteKoshiEvent.js +++ b/src/data/bhoteKoshiEvent.js @@ -1179,12 +1179,21 @@ export function createBhoteKoshiEventLayer({ _imageryImages = null; throw new Error('Bhote Koshi imagery unavailable'); } + // These decoded images replace empty layers. Install them hidden so a + // tileset with no imagery requests cannot hold enable behind a drawn frame. + _native.setVisible(false); await _native.loadImages( event.imagery.rectangle, images, 'Vantor WorldView Open Data ยท CC BY-NC 4.0', ); - if (_enabled && current()) setSplit(_split); + if (_enabled && current()) { + _native.setVisible( + _presentation !== BHOTE_KOSHI_SCENE_PRESENTATION || + _sceneControls.imageryComparison === true, + ); + setSplit(_split); + } } async function resolveEvidenceAnchors(event, signal) { diff --git a/src/data/bhoteKoshiEvent.test.mjs b/src/data/bhoteKoshiEvent.test.mjs index b935a0444..4d9f59c68 100644 --- a/src/data/bhoteKoshiEvent.test.mjs +++ b/src/data/bhoteKoshiEvent.test.mjs @@ -36,6 +36,8 @@ import { SCENE_RECIPES } from '../scenes/recipes.js'; import { LayerLifecycle } from './lifecycle.js'; import { createBhoteKoshiEmbeddedMedia } from './bhoteKoshiEmbeddedMedia.js'; import { SceneDirector } from '../scenes/director.js'; +import { createCesiumImagery } from '../globe/cesium/imagery.js'; +import { createBhoteKoshiDisplay } from '../layers/bhoteKoshi/render/display.js'; const eventUrl = new URL('../../public/events/bhote-koshi-2026/event.json', import.meta.url); const eventModuleUrl = new URL('./bhoteKoshiEvent.js', import.meta.url); @@ -3258,6 +3260,80 @@ async function navaraEvidenceCard({ sample, signal }) { }; } +for (const presentation of ['standalone', 'scene-beat']) { + test(`Bhote Koshi enables ${presentation} without waiting for tileset imagery requests`, async () => { + const originalDocument = globalThis.document; + const originalWindow = globalThis.window; + installEventDom(); + globalThis.window = { setTimeout: () => 1, clearTimeout() {} }; + const event = JSON.parse(await readFile(eventUrl, 'utf8')); + event.evidenceSpine = []; + const engine = createFakeEngine({ canvas: eventViewer().scene.canvas }); + const viewer = { + imageryLayers: new Cesium.ImageryLayerCollection(), + scene: { + globe: { show: false }, + preRender: new Cesium.Event(), + preUpdate: new Cesium.Event(), + postRender: new Cesium.Event(), + }, + }; + const tileset = { + show: true, + imageryLayers: new Cesium.ImageryLayerCollection(), + isDestroyed: () => false, + }; + const imagery = createCesiumImagery(viewer, { + requestRender() {}, + tilesets: () => [tileset], + createCanvas: (width, height) => ({ + width, height, + getContext: () => ({ + createImageData: (w, h) => ({ data: new Uint8ClampedArray(w * h * 4) }), + putImageData() {}, + }), + }), + }); + engine.imagery = imagery.imagery; + let display; + const layer = buildEventLayer({ + eventLoader: async () => event, + imageLoader: async () => ({ width: 1, height: 1, data: new Uint8Array(4) }), + createNativeRenderer: (engine) => (display = createBhoteKoshiDisplay(engine)), + embeddedMediaFactory: () => ({ hide() {}, pause() {}, destroy() {} }), + overlayHost: { setEntries() {}, setVisible() {}, clearSource() {} }, + renderHost: { request() {}, hold() {}, release() {} }, + }); + let enabling; + try { + await layer.init(null, { engine }); + layer.setParams({ presentation, sceneSurface: 'evidence-beat', autoPlay: false }); + let enabled = false; + enabling = layer.enable(null).then(() => { enabled = true; }); + await new Promise(setImmediate); + assert.equal(enabled, true, 'decoded images must not wait for an undrawable stage'); + const images = engine.imagery.list(); + assert.equal(images.length, 2); + assert.equal(tileset.imageryLayers.length, 2, 'no staged layers remain'); + for (const image of images) + assert.equal(image.shown, presentation === 'standalone'); + await layer.detachRenderer(); + let attached = false; + enabling = layer.attachRenderer({ engine }).then(() => { attached = true; }); + await new Promise(setImmediate); + assert.equal(attached, true, 'reattachment does not wait for imagery requests either'); + } finally { + display?.setVisible(false); + await enabling; + await layer.destroy(); + imagery.destroy(); + engine.destroy(); + globalThis.document = originalDocument; + globalThis.window = originalWindow; + } + }); +} + test('Navara evidence anchors stand on the most detailed terrain', async () => { const originalDocument = globalThis.document; const originalWindow = globalThis.window;