spec.md 8.8 KB

主场景区的相机限位与进出状态机 spec

Status: ready-for-agent

Problem Statement

驾驶舱进入主场景区(沅陵县城框定范围)后,相机没有区域限位:用户可以把弹簧臂拉远到全球尺度,看到高精度精细数据以外的低精度区域与底图接缝("露馅")。同时,进入主场景区时相机从全球粗略底图飞到精细影像矩形,过渡突兀。本 spec 固化第 1 项(相机限位与进出状态机)的实现契约;雾与过渡分别由同 effort 的 07、08 另行固化为规格(待 resolved)。

决策来源:.scratch/cockpit-primary-area-guardrails/issues/03(已 resolved,维护者 Q1–Q5 + R2-1…R2-6 全部确认),决策记录在 docs/adr/0009-primary-area-camera-limits-and-entry-state.md。

Solution

进入主场景区后,相机被显式模式锁限定:最远视野 = 归位默认视角(range_max = 12,000 m),中心点被约束在 map.primaryArea.bounds 内(边界软回弹),相机不钻入地形;只有「返回全球」能解除限位,回到自由全球视角;全球态闲置 5 秒未操作回到程序初始态(星球自转 + 屏幕中央闪烁「点击进入系统」),不重置一级模式与筛选。与既有三键相机契约(ADR 0008)、地面/管线视图(取交集)合成,全部落在服务层。

User Stories

  1. 作为值班人员,我希望进入主场景区后滚轮不能把镜头拉远到超出归位默认视角,以便看不到高精度数据以外的区域而"露馅"。
  2. 作为值班人员,我希望点「进入系统」或「归位」后进入主场景区并自动锁定限位,以便无需手动设置即可获得保护。
  3. 作为值班人员,我希望「归位」把我带回沅陵县城默认视角(12 km 县域级),以便快速回到工作位置。
  4. 作为值班人员,我希望点击「返回全球」后限位解除、镜头阻尼飞到自由全球全景,以便查看整体态势或离开主场景区。
  5. 作为值班人员,我希望在自由全球视角闲置 5 秒后回到程序初始态(星球自转 + 「点击进入系统」闪烁),以便长时间离开后回来仍是熟悉入口。
  6. 作为值班人员,我希望回到引导页时不重置当前一级模式与行业/区域筛选,以便回来后续接之前的工作上下文。
  7. 作为值班人员,我希望在区内平移到边界时有软回弹手感(短暂越界后松手弹回),以便知道"到头了"而不是被硬生生挡住。
  8. 作为值班人员,我希望镜头贴近地面或设备时不会钻进地形(防入地),以便始终能看到目标。
  9. 作为值班人员,我希望在区内切换地面/管线视图时两组约束取交集、行为与现状一致,以便管线巡查不被区域限位破坏。
  10. 作为值班人员,我希望在区外顶栏按钮显示为「归位/进入」,以便一步回到主场景区。
  11. 作为维护者,我希望未配置 map.primaryArea 时区域限位与自建雾自动关闭并如实提示,以便不误导部署。
  12. 作为维护者,我希望限位的数值(range_max、软区、防入地 margin)可配置或可微调,以便按现场观感校准。
  13. 作为测试人员,我希望限位与状态机的纯逻辑可直接断言(脱离浏览器),以便快速回归。
  14. 作为验收人员,我希望通过既有走查脚本验证"进入→锁定→返回→闲置回引导"全过程,以便确认无跳变与露馅。

Implementation Decisions

  • range_max = home.range = 12,000 m:最远视野 = 归位默认视角(map.home pitch −55 / range 12000,home 预设不改)。取代 issues/02 的 range_max = 1651/(2·tan(fovY/2))(≈2,542 m)与"15% 垂直覆盖预算"锚点;isAtHome 的 2% 相对容差(240 m)足够识别"已归位"。
  • 中心点 ∈ primaryArea.bounds(软回弹):拖拽时 target.pivot 允许越出最多框短边的 5%(≈550 m,可微调);越界部分在松手时按既有阻尼弹回框内(非拖拽中持续回弹);shown.pivot 最终回到框内,越界只出现在拖拽过程。
  • 显式模式锁:areaState: 'locked' | 'global';点「进入系统」/「归位」进区并锁定;只有「返回全球」能解除;判定与相机位置无关,不随跨界自动切换(避免贴边抖动)。
  • 返回全球:解除限位 + 阻尼飞到程序初始态全景(heading 0 / pitch −60 / range 20,000,000,与 INITIAL_HPR 一致),之后完全自由。
  • 全球态闲置 5 秒:回到程序初始态(星球自转恢复 + 屏幕中央闪烁「点击进入系统」);不重置 mode/筛选;任何交互即重置计时。map.intro.enabled=false(走查自隔离)时按 global 自由视角启动、不弹引导页。
  • 与视图模式合成(取交集):区内管线视图 range ∈ [400, min(6000, range_max)] = [400, 6,000](因 range_max=12,000 > 6,000,与现状一致,无额外收紧);pitch 上下限沿用视图模式(区域不收紧 pitch)。
  • 防入地(继承 02):相机离地高度 h = range·sin|pitch|;每次输入(滚轮/轨道)时取相机正下方地形(globe.getHeight),若 h < 地形 + 10 m 则夹 range(不夹 pitch、不动 pivot);瓦片未加载时退回固定 10 m 绝对下限。输入时 range 同时受 [120, range_max]、管线视图 [400, 6000]、防入地下限三处夹取,最终生效 = 三者交集。
  • 状态归属:areaState 进 filterStore(全局视图条件,与 viewMode 同层,切一级模式不重置);mapService 负责状态转移(进入/返回/归位);cameraControl 按状态换 limits(新增 setRangeLimits 一类,与既有 setPitchLimits 对称);5 秒闲置计时器与引导页显隐归 CockpitShell(组件内或 uiStore)。
  • 顶栏按钮:区内显示「归位」+「返回全球」两个 chip(归位沿用现有);区外只显示一个「归位/进入」chip。
  • 三键分工与阻尼(ADR 0008)不变;限位只在输入时生效(现状 clamp 行为),无逐帧主动回拉。

Testing Decisions

  • 什么是好测试:只断言外部行为 —— 限位生效后的可达 range 区间、状态机转移(进入/返回/闲置回引导)、软回弹的最终落点、防入地夹取、isAtHome 识别;不断言内部变量名或组件结构。
  • 逻辑 seam(主):domain/camera.js 及同层新增纯函数(限位解析 / 状态机转移 / 软区夹取)—— 与既有 verify/t03-camera-contract.mjs(相机契约)、t21-view-modes.mjs(视图模式计划)同一模式:脱浏览器断言。
  • 组件 seam:CockpitShell 是唯一公共组件 seam —— 走查脚本断言顶栏 chip 的显隐/文案(data-*)、引导页显隐、dataset.camera 状态读数。
  • 集成 seam:mapService.getCameraState() + filterStore.areaState 为只读观测出口;走查断言"进入→锁定→滚轮拉远被夹→返回→闲置回引导"全过程,沿用 t30-intro-walkthrough.mjs 的做法。
  • 先例:verify/t03-camera-contract.mjs、t30-intro-walkthrough.mjs、t30-view-presets.mjs。按 AGENTS.md,改代码后不自动跑 build/dev,验证由维护者明确要求时执行。

Out of Scope

  • 视距雾气(曲线/配色/门控)—— 同 effort 07,areaState 与 range_max 是其输入契约。
  • 低精度→高精度过渡手段 —— 同 effort 08。
  • 镜头定位到监测点/设备的视角契约 —— 平行图 cockpit-point-locate-and-device-link 03(其 range 上限受 range_max = 12,000 约束)。
  • 重做进入引导动画本身(票 30 已交付并验收)。
  • 让 Three.js 接管场景(ADR 0004 / 0007 锁死;06 已裁定废止深度互通备选)。
  • 主场景区的配置字段形状与兜底(01 已锁)、数值口径(02 已锁,锚点被本 spec 取代)。
  • 改 mode / panelId / 面板语义与布局。

Further Notes

  • 联动风险(已确认归属,未裁决,留给平行图 03):主场景区只覆盖县城(13.4×11 km),而监测点散布全县(影像 105×122 km)。若区外监测点被选中/定位,中心点 ∈ primaryArea.bounds 的限位会挡住它 —— 该裁决归 cockpit-point-locate-and-device-link/issues/03(claimed);若裁定允许区外定位,则需在本契约开例外或收紧范围定义。
  • 边缘口径(2026-09-18 维护者确认):intro.enabled=false 时"全球态闲置 5 秒回引导页"不发生(无引导页可回,保持 global 自由视角)。
  • 软区与防入地 margin 均标注"可微调":实现与走查按观感校准,数值改动只涉及 domain 层常量,不改变契约形状。
  • 本 spec 与 docs/adr/0009-primary-area-camera-limits-and-entry-state.md 一致;实现票按 Q6=(a)+(c) 接在 .scratch/cockpit-shell-panel-layout/issues/ 30 之后(31+)。