What to build: 实现驾驶舱三维地图的显式相机控制:左键拖拽平移、右键绕中心旋转、滚轮调整弹簧臂距离,并替换 Cesium 默认交互。
Blocked by: 02, 27
依赖说明(2026-09-16 维护者确认):三键相机契约必须落在真实引擎上,故新增票 27(接入 Cesium 常驻 Viewer)作为本票前置。
Status: claimed Labels: ready-for-agent
ADR 0008 与决策地图票 Spring-arm orbit pivot:三键分工、旋转中心 = 选中对象地表点(未选中时 = 屏幕中心的地面拾取点)、右键按下锁定中心、取消选中不回退、只有左键平移才把中心交回地面拾取点、换中心时保持 heading 与 range。弹簧臂原语锁定为 camera.lookAtTransform(中心东北天局部系, HeadingPitchRange),不用 Tres OrbitControls、不用 vue-cesium 的鼠标绑定。
createViewer() 里逐项关闭(enableRotate/Zoom/Tilt/Look/Translate),左键平移/滚轮距离由本票接管;真机 A2/A4 待跑。domain/camera.js(纯数学)+ services/cameraControl.js(状态机与 DOM 绑定)+ services/cesiumEngine.js(Cesium 方言翻译)+ services/mapService.js(生命周期与对外 API);组件与外壳未改。src/modules/cockpit/domain/camera.js:三键的纯逻辑 —— orbit()(右键)、zoomByWheel()(滚轮)、metersPerPixel() + pivotOffsetMeters() + shiftPivot()(平移)、flightProgress() + lerpPivot()(换中心动画)、pitch 上下限与 range 上下限、formatCameraState()(状态串)。不 import cesium、不碰 DOM。src/modules/cockpit/services/cameraControl.js:状态机(中心、弹簧臂、拖拽态、锁定中心、飞行)+ attachCameraInput() 事件绑定(左/右键 pointer、滚轮 passive:false + preventDefault、右键菜单 preventDefault、setPointerCapture)。引擎以接口注入,所以能脱离浏览器断言。src/modules/cockpit/services/cesiumEngine.js:新增 createCameraEngine(viewer),把契约用到的 Cesium 方言包出来 —— applyView(lookAtTransform + HeadingPitchRange)、readView(反读当前相机相对中心的 HPR)、pickGroundAtCenter(屏幕中心椭球求交;地形归票 21)、projectPivot(中心点投回画布)、inputTarget/viewport/fovY/now/requestFrame。本文件仍是全仓唯一 import 'cesium' 的地方。src/modules/cockpit/services/mapService.js:引擎就绪后挂相机控制,卸载时先摘事件再销毁实例;对外只暴露 setCameraFocus(point|null) / clearCameraFocus() / getCameraState();宿主写 data-camera(状态串,走查与人工排查都读它);DEV 下挂 window.__cockpitMap 把手(演示与走查用,生产构建不含)。verify/t03-camera-contract.mjs:10 项逻辑断言(假引擎)。verify/t03-camera-walkthrough.mjs:9 项真机断言(真 Cesium + page.mouse 真实输入事件)。2·range·tan(fovY/2)/viewportH),与 pitch 无关,4K/窗口尺寸变化都不用改灵敏度。PAN_FOLLOWS_DRAG 一个常量即可,逻辑与走查都跟着它走。[-85, -5];ADR 里「管线视图允许平视或轻微仰视」的放宽([-85, 5])已在常量里备好,等视图预设那一票接(不属本票)。前置:yarn dev → 打开 /#/cockpit,引擎就绪后宿主上会有 data-camera="h=… p=… r=… pivot=… focus=ground drag=none"。
pivot 变化、h/p/r 不变,画面随之平移(不再旋转)。h 随横向像素、p 随纵向像素变化,r 与 pivot 不动;拖拽中松开前不会有任何拾取。r 变化,h/p/pivot 不动。__cockpitMap.setCameraFocus({ lon: 110.396, lat: 28.455 }) —— 中心平滑飞到该点,全程 h/r 不变;__cockpitMap.clearCameraFocus() 中心不回退。node .scratch/cockpit-shell-panel-layout/verify/t03-camera-contract.mjs → 10/10 通过(左键只平移并按像素比例移动中心、平移方向 = 相机移动方向、右键只旋转且 range/中心不动、pitch 夹限、拖拽中锁定中心且松手后才换中心、滚轮只改距离并夹限、中心三规则、换中心 10 帧单调且 h/r 全程不变、DOM 绑定与默认行为抑制、状态串)。t01-panel-catalog.mjs 通过(getLifecycleStats 新增 camera 字段,原字段语义未变)、t06-assets.mjs 15/15。真机走查 9 项还没跑 —— 需要 http://localhost:8000 上的 yarn dev(维护者自行起服务,项目规则下我不自动起)。服务起来后执行:
node .scratch/cockpit-shell-panel-layout/verify/t03-camera-walkthrough.mjs
把结果补进「验收证据」并把进度提到 100% 再 close。若真机暴露出与逻辑不一致的地方(例如 Cesium 事件被上游吞掉、中心投影偏移),据实回调进度并写明根因。