--- 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` 约束。