0009-primary-area-camera-limits-and-entry-state.md 6.4 KB


status: accepted

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

驾驶舱进入主场景区后,相机施加区域限位,并用显式状态机控制进/出(2026-09-18 维护者裁定,来源 .scratch/cockpit-primary-area-guardrails/issues/03)。

主场景区

map.primaryArea.bounds([west, south, east, north] 度,配置显式给出)。未配置或顺序非法时,关闭依赖主场景区的限位与自建雾,并由引擎浮标如实提示(见 .scratch/cockpit-primary-area-guardrails/issues/01)。

限位数值

  • 中心点:∈ primaryArea.bounds;拖拽到边界时允许软回弹 —— target.pivot 可越出最多框短边的 5%(≈550 m,可微调),松手时按既有阻尼弹回框内;shown.pivot 最终回到框内。
    • 例外(2026-09-28 维护者裁定,实现于 .scratch/cockpit-shell-panel-layout/issues/37):该约束只管手动三键(左键平移 / 右键轨道 / 滚轮)。点选/定位到框外对象是例外:中心照常飞到该对象。理由是监测点散布全县(影像 105×122 km)而主场景区只框县城(13.4×11 km),硬夹会把绝大多数点位挡在区外、点选后镜头停在框边而不是点位上。cockpit-primary-area-guardrails/spec.md 早已把这个碰撞记为「待平行图 03 裁决」;该图收口时只裁了 range/高度,故由票 37 提请裁定并落此句。手动拖拽从框外起手时不回弹(否则一按鼠标就会把刚定位到的点位拽回框边)。
    • 2026-10-12 维护者裁定:坐标准入改为严格主场景区(.scratch/cockpit-real-data-wayfinding/issues/34-coordinate-auto-admission.md,原编号 32 后因重号改 34)。此裁定只改准入,不删本例外:区外对象现在是 renderable: false,而 domain/mapLocate.js 对不可上图对象返回 BLOCKED、pointSelection.selectMonitoringPoint() 也先过 isRenderable(),因此本例外经产品路径已不可达 —— 不会再出现"点选一个框外对象、镜头飞出去"的场景。相机层的例外实现(cameraControl.js 的定位豁免与 outsideArea 读数)保留:它仍是相机层的正确保证(如程序化 applyViewPreset 到框外坐标时不瞬移),票 37 的用例继续有效。
    • 两处边界必须同源:准入用的矩形在后端是 CockpitCoordinateValidator 的常量,在限位/雾气侧是配置 map.primaryArea.bounds。当前两者逐位相同(110.328749941417, 28.3917230659234, 110.465779226663, 28.4913038992907)。改配置时必须同时改后端常量,否则会出现"后端允许上图、前端却算区外"或反向的静默分歧。
  • range:∈ [120, range_max],range_max = home.range = 12,000 m —— 最远视野 = 归位默认视角(map.home pitch −55 / range 12000,home 预设不改)。
  • pitch:不参与区域收紧(沿用视图模式的 PITCH_LIMITS)。
  • 防入地:相机绝对高度须高出其正下方地形 10 m(暂定,可微调);不足时夹 range(不夹 pitch、不动 pivot);瓦片未加载时退回固定净空 10 m。
    • 口径澄清(2026-09-28,票 37 实作时标注):原文写作 h = range·sin|pitch| ≥ 正下方地形 + 10 m,但 h 是相对 pivot 的高度、地形是绝对高程,相减在量纲上不通。字面直译会把沅陵约 111.6 m 的地形高程当成 h 的下限,于是 pitch −55° 时把 120 m 下限顶到 ≈148 m、pitch −5° 时顶到 ≈1.4 km,当场推翻票 30 已验收的「拉到距离下限 120 m 仍能看到地面」。实现按物理含义取「相机绝对高度 ≥ 其正下方地形 + 10 m」,即 range ≥ (terrain + margin − pivot.height)/sin|pitch|;pivot 就在地表时(票 30 起 pivot 高程=实测地形)退化为"至少留 10 m 净空"。

进出状态机

  • 显式模式锁:点「进入系统」/「归位」进入并锁定;只有「返回全球」能解除;判定与相机位置无关,不随跨界自动切换。
  • 进入路径同票 30:mapService.goHome() → cameraControl.applyViewPreset()。
  • 区外(global)完全自由;「返回全球」解除限位并阻尼飞到程序初始态全景(heading 0 / pitch −60 / range 20,000,000)。
  • 全球态闲置 10 秒未操作 → 回到程序初始态:镜头回初始态全景(0 / −60 / 2×10⁷)+ 地球自转恢复 + 屏幕中央闪烁「点击进入系统」;不重置一级模式/筛选。
    • 2026-09-30 维护者裁定两处(来源 .scratch/cockpit-shell-panel-layout/issues/40):闲置阈值由 5 秒改为 10 秒(现场手感);并把「镜头回初始全景」补进本条 —— 原先只写了自转与引导层,实现便只做了那两件,于是回引导态时画面停在用户最后停留的视角上(维护者实报「看不出回到了引导态」)。
  • map.intro.enabled=false(走查自隔离)时按 global 自由视角启动,不弹引导页。

与既有相机契约的合成

  • 视图模式(地面/管线)pitch/range 与区域限位取交集:区内管线视图 range ∈ [400, min(6000, range_max)] = [400, 6,000](因 range_max=12,000 > 6,000,与现状一致);pitch 上下限沿用视图模式。
  • 输入时 range 同时受 [120, range_max]、管线视图 [400, 6000]、防入地下限三处夹取,最终生效 = 三者交集。
  • 三键分工与阻尼(ADR 0008)不变;状态与限位落在服务层,不散落页面组件。

状态归属

areaState: 'locked' | 'global' 进 filterStore(全局视图条件,与 viewMode 同层,切一级模式不重置);mapService 负责状态转移;cameraControl 按状态换 limits(setRangeLimits 与既有 setPitchLimits 对称);10 秒闲置计时器与引导页显隐归 CockpitShell。

关联

  • 本 ADR 的 range_max = 12,000 m 取代 .scratch/cockpit-primary-area-guardrails/issues/02 的 range_max = 1651/(2·tan(fovY/2))(≈2,542 m)与"15% 垂直覆盖预算"锚点。
  • 雾的曲线、配色、门控见 .scratch/cockpit-primary-area-guardrails/issues/07(以 areaState 与 range_max 为输入)。
  • 低精度→高精度过渡见 .scratch/cockpit-primary-area-guardrails/issues/08。
  • 平行图镜头定位(.scratch/cockpit-point-locate-and-device-link/issues/03)的 range 上限受 range_max = 12,000 约束。