t30-view-presets.mjs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. /**
  2. * T30 进入引导与视角预设 纯逻辑检查。
  3. *
  4. * 覆盖:`map.home` / `map.intro` 的归一化与默认值;预设视角解析;归位判定 `isAtHome()` 的容差与
  5. * 经度环绕;以及「配置里那组沅陵坐标」确实被解析成预期值(防止手抄坐标时写错)。
  6. *
  7. * 运行:node .scratch/cockpit-shell-panel-layout/verify/t30-view-presets.mjs
  8. */
  9. import assert from 'node:assert/strict'
  10. import { existsSync, readFileSync } from 'node:fs'
  11. import {
  12. HOME_DEFAULTS,
  13. INTRO_DEFAULTS,
  14. homeLabel,
  15. isAtHome,
  16. planHome,
  17. planIntro,
  18. } from '../../../src/modules/cockpit/domain/viewPresets.js'
  19. import { builtinConfig, normalizeConfig } from '../../../src/modules/cockpit/services/cockpitConfig.js'
  20. // ---------------------------------------------------------------- 1. 默认值
  21. const defaults = planHome({})
  22. assert.equal(defaults.lon, HOME_DEFAULTS.lon, '缺省经度应回落到默认(沅陵)')
  23. assert.equal(defaults.lat, HOME_DEFAULTS.lat, '缺省纬度应回落到默认')
  24. assert.ok(defaults.range > 1000, '默认距离应是县域级(>1km)')
  25. assert.ok(defaults.pitch < 0, '默认俯角应为负(从上往下看)')
  26. const introDefaults = planIntro({})
  27. assert.equal(introDefaults.enabled, true, '默认开启引导')
  28. assert.equal(introDefaults.title, INTRO_DEFAULTS.title, '默认文案')
  29. assert.ok(introDefaults.spinSeconds > 0, '默认开启自转')
  30. // ---------------------------------------------------------------- 2. 归一化
  31. const normalized = normalizeConfig({
  32. map: {
  33. home: { lon: 110.39028164, lat: 28.4436787, pitch: -55, range: 12000, label: '沅陵县城' },
  34. intro: { enabled: false, title: '进入', spinSeconds: 0 },
  35. },
  36. })
  37. assert.equal(normalized.config.map.home.lon, 110.39028164, '经度应透传(不丢精度)')
  38. assert.equal(normalized.config.map.home.lat, 28.4436787, '纬度应透传')
  39. assert.equal(normalized.config.map.home.label, '沅陵县城', '标签应透传')
  40. assert.equal(normalized.config.map.intro.enabled, false, '引导开关应透传')
  41. assert.equal(normalized.config.map.intro.spinSeconds, 0, '自转秒数应透传')
  42. const badTypes = normalizeConfig({ map: { home: { lon: '东经110', pitch: {} }, intro: { enabled: 'yes' } } })
  43. assert.equal(badTypes.config.map.home.lon, null, '非数字经度应为 null(回落默认)')
  44. assert.equal(badTypes.config.map.home.pitch, null, '非数字俯角应为 null')
  45. assert.equal(badTypes.config.map.intro.enabled, true, '非布尔开关应回落默认 true')
  46. assert.equal(badTypes.issues.length, 3, `三个错字段各记一条 issue,实际 ${JSON.stringify(badTypes.issues)}`)
  47. // ---------------------------------------------------------------- 3. 计划层
  48. const home = planHome({ home: { lon: 110.5, lat: 28.5, heading: 30, pitch: -40, range: 5000 } })
  49. assert.equal(home.lon, 110.5, '配置优先')
  50. assert.equal(home.heading, 30, 'heading 透传')
  51. assert.equal(home.range, 5000, 'range 透传')
  52. assert.equal(home.label, '目标区域', '没给 label 时有默认文案')
  53. const introOff = planIntro({ intro: { enabled: false } })
  54. assert.equal(introOff.enabled, false, '可关闭引导(走查自隔离用)')
  55. assert.equal(planIntro({ intro: { spinSeconds: -5 } }).spinSeconds, 0, '负数自转秒数应夹到 0')
  56. // label 文案:归位按钮的 tooltip 用它,必须带上坐标
  57. const label = homeLabel({ lon: 110.39028164, lat: 28.4436787, label: '沅陵县城' })
  58. assert.match(label, /沅陵县城/, '标签应含地名')
  59. assert.match(label, /110\.3903/, '标签应含经度(4 位小数)')
  60. assert.match(label, /28\.4437/, '标签应含纬度(4 位小数)')
  61. // ---------------------------------------------------------------- 4. 归位判定
  62. const homeRef = { lon: 110.39028164, lat: 28.4436787, heading: 0, pitch: -55, range: 12000 }
  63. const atHomeState = { pivot: { lon: 110.39028164, lat: 28.4436787, height: 0 }, hpr: { heading: 0, pitch: -55, range: 12000 } }
  64. assert.equal(isAtHome(atHomeState, homeRef), true, '同一视角应判为在家')
  65. assert.equal(
  66. isAtHome({ pivot: { lon: 110.4, lat: 28.44 }, hpr: { heading: 0, pitch: -55, range: 12000 } }, homeRef),
  67. false,
  68. '中心偏出容差(>1e-4°)不应判为在家',
  69. )
  70. assert.equal(
  71. isAtHome({ pivot: atHomeState.pivot, hpr: { heading: 0, pitch: -55, range: 30000 } }, homeRef),
  72. false,
  73. '距离差太多不应判为在家',
  74. )
  75. assert.equal(
  76. isAtHome({ pivot: atHomeState.pivot, hpr: { heading: 0, pitch: -20, range: 12000 } }, homeRef),
  77. false,
  78. '俯角差太多不应判为在家',
  79. )
  80. // 经度环绕:heading 0 与 360 是同一个朝向
  81. assert.equal(
  82. isAtHome({ pivot: atHomeState.pivot, hpr: { heading: 360, pitch: -55, range: 12000 } }, homeRef),
  83. true,
  84. 'heading 360 等价于 0(经度/朝向环绕)',
  85. )
  86. assert.equal(isAtHome(null, homeRef), false, '没有相机状态时不应判为在家')
  87. // ---------------------------------------------------------------- 5. 与真实配置对齐(本机)
  88. const configPath = new URL('../../../public/cockpit/config.json', import.meta.url)
  89. if (existsSync(configPath)) {
  90. const raw = JSON.parse(readFileSync(configPath, 'utf8'))
  91. const { config } = normalizeConfig(raw)
  92. const actual = planHome(config.map)
  93. assert.equal(actual.lon, 110.39028164, '配置里的经度应等于维护者给的沅陵坐标')
  94. assert.equal(actual.lat, 28.4436787, '配置里的纬度应等于维护者给的沅陵坐标')
  95. assert.ok(config.map.intro.enabled !== false, '本机配置应开启进入引导')
  96. console.log(` (已按本机配置核对:home=${actual.lon}, ${actual.lat} label=${actual.label} intro=${config.map.intro.enabled})`)
  97. }
  98. console.log(
  99. 'T30 检查通过:home/intro 默认值与归一化(含错字段 issue)、预设解析与 label 文案、归位判定(容差 + heading 环绕 + 无状态),并与本机配置里的沅陵坐标对齐核对。',
  100. )