Status: ready-for-agent
驾驶舱进入主场景区(沅陵县城框定范围)后,相机没有区域限位:用户可以把弹簧臂拉远到全球尺度,看到高精度精细数据以外的低精度区域与底图接缝("露馅")。同时,进入主场景区时相机从全球粗略底图飞到精细影像矩形,过渡突兀。本 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。
进入主场景区后,相机被显式模式锁限定:最远视野 = 归位默认视角(range_max = 12,000 m),中心点被约束在 map.primaryArea.bounds 内(边界软回弹),相机不钻入地形;只有「返回全球」能解除限位,回到自由全球视角;全球态闲置 5 秒未操作回到程序初始态(星球自转 + 屏幕中央闪烁「点击进入系统」),不重置一级模式与筛选。与既有三键相机契约(ADR 0008)、地面/管线视图(取交集)合成,全部落在服务层。
map.primaryArea 时区域限位与自建雾自动关闭并如实提示,以便不误导部署。range_max、软区、防入地 margin)可配置或可微调,以便按现场观感校准。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';点「进入系统」/「归位」进区并锁定;只有「返回全球」能解除;判定与相机位置无关,不随跨界自动切换(避免贴边抖动)。INITIAL_HPR 一致),之后完全自由。mode/筛选;任何交互即重置计时。map.intro.enabled=false(走查自隔离)时按 global 自由视角启动、不弹引导页。range ∈ [400, min(6000, range_max)] = [400, 6,000](因 range_max=12,000 > 6,000,与现状一致,无额外收紧);pitch 上下限沿用视图模式(区域不收紧 pitch)。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)。isAtHome 识别;不断言内部变量名或组件结构。domain/camera.js 及同层新增纯函数(限位解析 / 状态机转移 / 软区夹取)—— 与既有 verify/t03-camera-contract.mjs(相机契约)、t21-view-modes.mjs(视图模式计划)同一模式:脱浏览器断言。CockpitShell 是唯一公共组件 seam —— 走查脚本断言顶栏 chip 的显隐/文案(data-*)、引导页显隐、dataset.camera 状态读数。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,验证由维护者明确要求时执行。areaState 与 range_max 是其输入契约。cockpit-point-locate-and-device-link 03(其 range 上限受 range_max = 12,000 约束)。mode / panelId / 面板语义与布局。primaryArea.bounds 的限位会挡住它 —— 该裁决归 cockpit-point-locate-and-device-link/issues/03(claimed);若裁定允许区外定位,则需在本契约开例外或收紧范围定义。intro.enabled=false 时"全球态闲置 5 秒回引导页"不发生(无引导页可回,保持 global 自由视角)。docs/adr/0009-primary-area-camera-limits-and-entry-state.md 一致;实现票按 Q6=(a)+(c) 接在 .scratch/cockpit-shell-panel-layout/issues/ 30 之后(31+)。