t03-pole-probe.mjs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. /**
  2. * 极点探针:实测把相机拖到南北极点附近时,相机到底发生了什么。
  3. *
  4. * 只读诊断,不改产品代码:用 dev 把手读状态,用真实鼠标事件驱动,
  5. * 采样相机的位置/朝向/中心,判断异常是「中心翻到另一侧」「朝向乱转」还是「引擎报错」。
  6. *
  7. * 运行(需先起 `yarn dev`):node .scratch/cockpit-shell-panel-layout/verify/t03-pole-probe.mjs
  8. */
  9. import { join } from 'node:path'
  10. import { tmpdir } from 'node:os'
  11. const BASE = process.env.COCKPIT_BASE || 'http://localhost:8000'
  12. const CHROME = process.env.CHROME_PATH || 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
  13. const PUPPETEER = 'file:///C:/Users/ASUS/.dsh/profiles/desktop/node_modules/puppeteer-core/lib/puppeteer/puppeteer-core.js'
  14. const puppeteer = (await import(PUPPETEER)).default
  15. const browser = await puppeteer.launch({
  16. executablePath: CHROME,
  17. headless: true,
  18. userDataDir: join(tmpdir(), 'cockpit-t03-pole-profile'),
  19. defaultViewport: { width: 1680, height: 950 },
  20. args: ['--no-sandbox', '--disable-dev-shm-usage', '--no-first-run'],
  21. })
  22. const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
  23. try {
  24. const page = await browser.newPage()
  25. const errors = []
  26. page.on('pageerror', (e) => errors.push(String((e && e.message) || e)))
  27. await page.goto(`${BASE}/#/cockpit`, { waitUntil: 'networkidle2', timeout: 60000 })
  28. await page.waitForSelector('.cockpit-shell', { timeout: 20000 })
  29. await page.waitForFunction(() => !!window.__cockpitMap, { timeout: 30000 }).catch(() => {})
  30. await page
  31. .waitForFunction(
  32. () => document.querySelector('.cockpit-shell__map .map-canvas')?.dataset.mapEngine === 'ready',
  33. { timeout: 60000 },
  34. )
  35. .catch(() => {})
  36. await sleep(1000)
  37. // 把中心放到极点上(正上方俯视),这是最容易触发奇点的位形。
  38. const readState = () =>
  39. page.evaluate(() => {
  40. const s = window.__cockpitMap.getCameraState()
  41. const host = document.querySelector('.cockpit-shell__map .map-canvas')
  42. return {
  43. pivot: s.pivot,
  44. shownPivot: s.shownPivot,
  45. hpr: s.hpr,
  46. shownHpr: s.shownHpr,
  47. settled: s.settled,
  48. data: host ? host.dataset.camera : '',
  49. }
  50. })
  51. const canvasCenter = await page.evaluate(() => {
  52. const c = document.querySelector('.cockpit-shell__map .map-canvas')
  53. const b = c.getBoundingClientRect()
  54. return { x: b.x + b.width / 2, y: b.y + b.height / 2 }
  55. })
  56. console.log('== 初始 ==')
  57. console.log(JSON.stringify(await readState()))
  58. // 直接飞到北极上空附近(避免拖上千像素),再贴近观察。
  59. for (const [name, point] of [
  60. ['北极附近', { lon: 0, lat: 89.5, height: 0 }],
  61. ['南极附近', { lon: 0, lat: -89.5, height: 0 }],
  62. ]) {
  63. console.log(`\n== 飞到 ${name} ==`)
  64. await page.evaluate((p) => window.__cockpitMap.setCameraFocus(p), point)
  65. await sleep(1500)
  66. const at = await readState()
  67. console.log(JSON.stringify({ pivot: at.pivot, hpr: at.hpr, settled: at.settled }))
  68. // 在极点附近继续往极点方向拖(正是维护者遇到的操作)。
  69. await page.mouse.move(canvasCenter.x, canvasCenter.y)
  70. await page.mouse.down({ button: 'left' })
  71. for (let i = 1; i <= 12; i += 1) {
  72. await page.mouse.move(canvasCenter.x, canvasCenter.y - i * 40, { steps: 2 })
  73. await sleep(25)
  74. const s = await readState()
  75. console.log(
  76. ` 拖 ${i}: pivot=${s.pivot ? `${s.pivot.lon.toFixed(3)},${s.pivot.lat.toFixed(3)}` : 'null'}` +
  77. ` h=${s.hpr.heading.toFixed(1)} p=${s.hpr.pitch.toFixed(1)} r=${Math.round(s.hpr.range)}` +
  78. ` | shown=${s.shownPivot ? `${s.shownPivot.lon.toFixed(3)},${s.shownPivot.lat.toFixed(3)}` : 'null'}` +
  79. ` settled=${s.settled}`,
  80. )
  81. }
  82. await page.mouse.up({ button: 'left' })
  83. await sleep(1200)
  84. const after = await readState()
  85. console.log(` 松手后: pivot=${after.pivot.lon.toFixed(4)},${after.pivot.lat.toFixed(4)} r=${Math.round(after.hpr.range)}`)
  86. }
  87. console.log(`\n== 页面报错 ==\n${errors.length ? errors.slice(0, 5).join('\n') : '(无)'}`)
  88. } finally {
  89. await browser.close()
  90. }