36-primary-area-config.md 9.5 KB

36: 主场景区配置(map.primaryArea)

What to build: 驾驶舱配置新增主场景区矩形 map.primaryArea.bounds。部署写好即生效;未配置或顺序非法时,依赖它的区域限位与自建雾整体关闭,并在状态浮标如实提示原因 —— 即决策票 01 的「部署期准备候选值 + 运行时未配置则关闭」。

Blocked by: None

Status: resolved Labels: ready-for-agent

契约来源

  • 规格:cockpit-primary-area-guardrails/spec.md(决策票 03 的落地口径)。
  • 决策:cockpit-primary-area-guardrails/issues/01-primary-area-config-contract.md(已 resolved)。

验收

  • map.primaryArea.bounds 取 [west, south, east, north](度)四元数组,与 map.overlays[].bounds 同口径。
  • 运行时校验 west < east、south < north;不自动翻转甲方给的 [W, N, E, S] 顺序。
  • 未配置 → 关闭依赖主场景区的区域限位与自建雾;顺序非法 → 记入配置问题并同样走关闭路径,保留原因。
  • 生效 / 关闭都体现在既有配置状态浮标(或等价 data-* 读数)上,人工与走查都能读到,不只是注释。
  • 本部署显式值=甲方框定的沅陵县城范围;config.example.json 同步该字段形状。
  • 改配置不需要改前端源码即可生效,沿用现有配置分层(内置默认 → 构建期环境 → 运行时文件 → 宿主注入)。
  • 逻辑断言覆盖四类:合法 / 顺序写反 / 缺字段 / 数值个数不对。

进度:100%

已完成(2026-09-28)。 本票是配置与开关的落地:只回答「限位与自建雾能不能开、开在哪个矩形」, 不实现限位本身(票 37)也不实现雾(票 38)——那两张票消费本票的出口。

改动

  • 新增 domain/primaryArea.js:形状 / 顺序判定的唯一一处(配置归一化也调它,不写第二份), 另给三态 ready|missing|invalid、浮标措辞、关闭原因词表。纯函数、不 import cesium(与 domain/basemap.js、mapLayers.js 同模式)。
  • services/cockpitConfig.js:内置默认补 primaryArea: { bounds: null, configured: false } —— 故意不预置任何地理常量(决策票 01 Q3=A:兜底归部署期算,不写死源码); normalizeConfig 原样收下数组(不翻转)、把不可用原因记进 issues 并保留。 configured 用来区分「部署没写这个字段」(未配置)与「写了但是空的」(写错了)。
  • services/mapService.js:启动时按配置算一次计划(放在「引擎已由配置关闭」那条早退之前), 浮标加一句 主场景区:…,宿主加 data-map-primary-area / data-map-primary-area-reason, getLifecycleStats() 补三个字段,并给出票 37/38 的唯一入口 getPrimaryAreaPlan()(返回副本)。
  • 配置面:public/cockpit/config.json 写本部署显式值;config.example.json 同步字段形状与一条字段说明; docs/cockpit-deployment.md 新增 2.9 主场景区(顺序是硬要求、三种浮标读数的对照表、矩形怎么定)。
  • 验证面:新增 verify/t36-primary-area.mjs(逻辑闸门)+ verify/t36-primary-area-walkthrough.mjs(真机走查); verify/t05-adapter.mjs 的静态守卫面按仓库惯例纳入新 domain 文件(218→230 项)。

关键取舍

  1. 顺序非法走关闭,不做兜底翻转(决策票 01 Q2=A)。翻对了没人会发现配置抄错, 翻错了就是拿一段假范围去锁相机 —— 两条都让部署问题在运行期隐身。 关闭时保留原因,且未配置 / 形状错 / 顺序错三种原因措辞互不相同(排查时能直接对号)。
  2. 计划与引擎解耦:主场景区是配置事实,引擎失败或被关掉都不改变答案。 因此它算在引擎早退之前,且在 booting/failed/disabled 下浮标也照说这一句。
  3. 出口给副本,消费方改不动服务层那份计划 ——「谁开的、开在哪儿」只能有一个来源。

证据

  • 逻辑闸门 t36-primary-area.mjs 通过,覆盖验收点名的四类输入(合法 / 顺序写反 / 缺字段 / 个数不对, 其中个数类含 3 个、5 个、空数组、混字符串、null、NaN、非数组、对象共 8 种)。 另含:甲方原样的 [W,N,E,S] 必须判非法且不给出矩形;三条关闭路径原因两两不同; 本部署显式值与甲方原值只差纬度两值对调(逐位差 < 1e-9 度,仅截位);模板示例值自身合法; 以及静态守卫(判定唯一、无内置地理常量、无翻转、只读出口齐)。
  • DOM 宿主执行证据(脱浏览器,等价替代「人工读得到」):用最小 DOM 宿主真跑 mountMap, 读回宿主 dataset 与浮标文本 —— 未读到配置前不宣布结论;关引擎的部署浮标上照样读到 主场景区:配置无效(顺序应为 [西,南,东,北],不自动翻转) · 限位与雾气关闭。
  • 变异测试(守卫有牙):5 处变异全部被对应断言打红并已还原 —— ① 删顺序校验 → 零跨度用例红;② 改成自动翻转 → 下标对调守卫红; ③ 配置层不记 issue → 「顺序非法应记一条」红;④ 出口返回内部引用 → 「不得被调用方改写」红; ⑤ 去掉关引擎时的浮标那句 → 第 ⑧ 条回归红。 另反向确认 t05 的 D3/D4 对新 domain 文件有牙(塞 fetch / 反向 import service 均打红)。
  • 全量回归:21 个零依赖逻辑闸门全绿(含 t05 扩面后 230/230)。t28-fill-container.mjs 是 puppeteer 脚本, 因 DSH profile 的 @puppeteer/browsers 是空目录(0 个文件)而 ERR_MODULE_NOT_FOUND, 与本票无关(改动前后同样失败;本票走查因此改用 DSH 自带浏览器,见下)。

真机走查:已运行,5/5 通过(维护者 2026-09-28 明确要求「先运行一次真机走查」)

运行方式与脚本原始设计不同,且证据更强:verify/t36-primary-area-walkthrough.mjs 的 puppeteer 路线在本机被 @puppeteer/browsers 包为空目录卡住(与本票无关,t28 等所有 puppeteer 脚本同样失败), 故改用 DSH 自带浏览器手工驱动同一条验收链(真实 Chrome、真实 yarn dev @ 127.0.0.1:8000、 真实页面 #/cockpit)。这比脚本的 evaluateOnNewDocument 注入多证明一件事: 配置是走真实运行时文件层(data-source=file)生效的,不是注入旁路。

# 用例 改 public/cockpit/config.json 实测读数 结果
A1 合法 本部署显式值 data-map-primary-area=ready;浮标 主场景区:13.4×11.1 km;出口 bounds 四元齐 ✅
A2 未配置 删除 map.primaryArea missing;原因 未配置;浮标 未配置 · 限位与雾气关闭;出口 bounds=null ✅
A3 顺序写反 甲方原样 [W,N,E,S] invalid;原因 顺序应为 [西,南,东,北],不自动翻转;出口 bounds=null(没被默默翻转) ✅
A4 个数不对 [110, 28, 111] invalid;原因 bounds 应为四个有限数字(与 A3 措辞不同,排查能对号) ✅
A5 关引擎 cesium.enabled=false 引擎 disabled、canvas=0,但 primaryArea=ready 且浮标照样读到 主场景区:13.4×11.1 km ✅
  • 每条都同时读了三处:宿主 data-map-primary-area(-reason)、__cockpitMap.getPrimaryAreaPlan()、浮标文本 —— 即「人工读得到」与「走查读得到」在同一时刻都被证实,不只是断言源码里存在。
  • 截图存证(人眼复核):t36-a1-ready.png(生效,浮标在中央顶部一行内可读)、 t36-a3-invalid.png(关闭,浮标写明顺序原因 + 「限位与雾气关闭」)。 顺带证实了「主场景区排在最前」这个决定的必要性:A3 关闭路径那句明显更长, 排在后面时截图里尾部已被 … 省略;提到最前之后,最关键的那句完整可见。
  • 原始 puppeteer 脚本保留(仍是维护者日后一键复跑的入口),但它在本机的依赖问题是既有环境缺陷, 不是本票引入,也未因此在票面把它记成"已跑过脚本"。A5 那条恰是脚本设计时就覆盖的用例,实测与脚本预期一致。
  • 走查后 public/cockpit/config.json 已逐字节还原(2291 bytes、无 BOM、intro.enabled=true、本部署 bounds), 与运行前备份 Compare 相等。

修掉的一个真缺陷(DOM 探针抓到)

浮标原来只在引擎 ready 时才拼业务读数,于是把引擎关掉的部署(map.cesium.enabled=false) 在主场景区这件事上完全失声 —— 而这恰恰是最需要看到「限位与雾气关了」的那种部署。 已改为:配置到手后,无论引擎处于何种状态,这一句都跟着说;引擎就绪时仍按原顺序拼在业务读数最前。 (顺手把主场景区提到 basemapText 之前:这根浮标是 nowrap + ellipsis,关闭路径那句更长,排后面会被尾省略。)

下一步

36 就位,37(进区锁定与相机区域限位)解除阻塞(38 仍等 37:它的门控要 areaState)。 37 与 38 都从 mapService.getPrimaryAreaPlan() 取「开不开、开在哪儿」,不要再自己解析一遍配置 ——判定只有 domain/primaryArea.js 一处,自己再判一次就会出现第二套口径。 40(全程质量收口)按 36→37→38→39→40 的顺序留到最后。