From 69fbf5b38d18a5a747cd843200605bc996a800ee Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Mon, 9 Nov 2026 13:03:13 +0400 Subject: [PATCH] Check hidden lines behind the Earth and open the picker panel by keyboard Cesium culls a line the globe hides whole and does not depth-test lines against the near side of a keyless globe, so the contract check runs the hidden line round the back of the Earth to a visible end. The wildlife switch scenario opens VISUAL PRESETS from the keyboard before tabbing to the engine picker and reports the page state when no switch follows. --- scripts/qa-wildlife-switch.mjs | 27 +++++++++++++++++++++-- src/globe/testing/contractSuite.mjs | 34 +++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/scripts/qa-wildlife-switch.mjs b/scripts/qa-wildlife-switch.mjs index 387b11231..8c1f8da5c 100644 --- a/scripts/qa-wildlife-switch.mjs +++ b/scripts/qa-wildlife-switch.mjs @@ -247,11 +247,21 @@ await strict.run({}, async (b) => { box: box ? [box.x, box.y, box.width, box.height] : null, }; }); + // The picker sits in VISUAL PRESETS, out of the Tab order while that + // panel is collapsed: open it from the keyboard too. + if ((await picker()).panel !== 'true') { + await p.focus('#control-panel-toggle'); + await p.keyboard.press('Enter'); + } await p .waitForFunction( () => { const el = document.querySelector('#globe-engine'); - return el && !el.disabled && el.getBoundingClientRect().height > 0; + return ( + el && + !el.disabled && + el.checkVisibility({ visibilityProperty: true }) + ); }, { timeout: 30000 }, ) @@ -293,7 +303,20 @@ await strict.run({}, async (b) => { const tabs = path.length; const from = await p.$eval('#globe-engine', (el) => el.value); await p.keyboard.type(kind[0]); - await waitForEngine(p, kind); + await waitForEngine(p, kind).catch(async (error) => { + const state = await p.evaluate(() => ({ + engine: window.__godsEyeView?.engine?.kind ?? null, + picker: document.querySelector('#globe-engine')?.value ?? null, + focus: document.activeElement?.id ?? null, + busy: document.querySelector('#globe-engine')?.disabled ?? null, + frame: Boolean(document.querySelector('.engine-switch-frame')), + loading: document.querySelector('#loading-screen')?.className ?? null, + })); + throw Error( + `no ${kind} globe after typing ${kind[0]}: ${JSON.stringify({ from, tabs: path.length, ...state })}`, + { cause: error }, + ); + }); assert.equal( await p.evaluate(() => performance.timeOrigin), origin, diff --git a/src/globe/testing/contractSuite.mjs b/src/globe/testing/contractSuite.mjs index 2c2774a44..69ba2c4d3 100644 --- a/src/globe/testing/contractSuite.mjs +++ b/src/globe/testing/contractSuite.mjs @@ -795,23 +795,39 @@ export function createContractSuite( const shownStrength = strength(at(20.5, 9)); line.show(false); await until(() => magentaAt(at(20.5, 9)) === 0, 'the line hidden'); - // 30 km under the ground: only the depth-fail copy shows, dimmed. - const deep = (lon) => at(lon, 11, -30_000); - hidden.setPositions([deep(19), deep(21)]); + // Round the back of the Earth along the great circle west of the + // view centre, out to a visible end past the western horizon. The + // far side, behind the Earth's centre, shows only by its dimmed + // depth-fail copy. Cesium culls a line the globe hides whole, and + // draws in full what lies under the horizon but before the centre. + const west = (degrees) => { + const t = (degrees * Math.PI) / 180; + const [lon0, lat0] = [(20 * Math.PI) / 180, (10 * Math.PI) / 180]; + const [x, y, z] = [ + Math.cos(lat0) * Math.cos(lon0) * Math.cos(t) + + Math.sin(lon0) * Math.sin(t), + Math.cos(lat0) * Math.sin(lon0) * Math.cos(t) - + Math.cos(lon0) * Math.sin(t), + Math.sin(lat0) * Math.cos(t), + ]; + return at( + (Math.atan2(y, x) * 180) / Math.PI, + (Math.asin(z) * 180) / Math.PI, + ); + }; + const deep = () => west(150); + hidden.setPositions([200, 150, 100, 50, 20].map(west)); await until( - () => magentaAt(deep(20)) > 10, + () => magentaAt(deep()) > 10, 'the hidden line drawn by its depth-fail copy', ); - const hiddenStrength = strength(deep(20)); + const hiddenStrength = strength(deep()); ensure( hiddenStrength < shownStrength, `the depth-fail copy (${hiddenStrength}) is not dimmer than the line (${shownStrength})`, ); hidden.destroy(); - await until( - () => magentaAt(deep(20)) === 0, - 'the destroyed line gone', - ); + await until(() => magentaAt(deep()) === 0, 'the destroyed line gone'); } finally { line.destroy(); hidden.destroy();