40-quality-closeout.md 12 KB

40: 全程质量收口(内部换级 / 降级 / 性能 / 证据)

What to build: 把前三票的「方案通过」收成「生产可用」:确认矩形内部换级与各降级路径的真实表现,拿到性能与走查证据,回写偏差,并把可丢弃原型归档。

Blocked by: 39

Status: claimed Labels: ready-for-agent

契约来源

  • 规格:cockpit-primary-area-guardrails/spec.md、spec-fog.md。
  • 决策:cockpit-primary-area-guardrails/issues/07-fog-implementation-spec.md、08-coarse-to-fine-transition-solution.md(均已 resolved)。
  • 迷雾清单:cockpit-primary-area-guardrails/map.md 的「Not yet specified」两条。

验收

  • 矩形内部 LOD 换级:真机确认是否肉眼可辨;若可辨,在「不接管 Cesium 场景、不引第二渲染器」的边界内评估局部过渡或 shader 羽化;预烘焙中精度影像层仅作更高成本兜底。
  • 异常降级四条路径各有可复现证据且提示如实(不宣称已遮缝):① 无深度能力;② 自建雾运行失败;③ 目标瓦片失败;④ 主场景区未配置(关闭限位与雾)。
  • 管线视图:近处管线可读;若因透明深度错位减弱/暂关雾,记录实际处置与提示文案。
  • 性能:同机 / 同分辨率 / 同视角 / 相同加载条件下,取无原型基线与开启雾后的重复采样 P95 帧耗时,合计增幅 ≤15%;首次启用与进出切换的卡顿单独记录。读数口径写清(渲染事件间隔 ≠ GPU 时间)。
  • 走查证据:进入 → 锁定 → 拉远被夹 → 位于边界回弹 → 返回全球 → 闲置回引导;并覆盖浅俯角与多个旋转方向。
  • 规格同步:实现与 spec.md / spec-fog.md 的偏差逐条回写票面;改口径需维护者确认,不静默漂移。
  • 原型归档:可丢弃原型与 /cockpit 入口的 DEV 接线单独保存到 throwaway 分支,并在本票留分支指针;/cockpit 的 pages 入口在生产路径下不再挂原型。
  • 运维自检:docs/cockpit-deployment.md 增一节「限位与雾气是否生效」(或明确记为遗留,写明原因)。
  • 收口时区分三类结论:已验证(有证据)、已知代价(明确接受,例如降级时远景保护减弱)、遗留(列明下一步),不得把原型观感认可写成生产达标。

进度:本轮已修「回引导态」缺陷、定夺闲置时长、归档原型;验收 9 条里 4 条需真机(故不 close)

一、维护者新报缺陷(2026-09-30):闲置回引导态"看不出来"

原话:

退出到地球视图后,在重新回到引导状态时,摄像机角度没有回到引导状态,且地球也没有重新开始旋转, 除了中心出现「点击进入系统」外用户不一定能意识到进入引导状态了。

这不是新需求,是一次实现漂移 —— ADR 0009 第 27 行早已写死: 「全球态闲置 5 秒未操作 → 回到程序初始态:地球自转恢复 + 屏幕中央闪烁『点击进入系统』」。 所以它同时踩了两条既有契约:票 37 验收第 3 条、以及上面这句 ADR。

两个根因(都在代码里坐实,不是猜):

  1. 相机没回初始全景。 CockpitShell.vue 的 returnToIntro() 只做了两件事 —— introVisible.value = true 与 setGlobeSpin(...),没有把相机送回去。 于是画面停在用户最后停留的视角上,"回初始态"就只剩中央那行字。
  2. 自转起不来,而且失败被伪装成成功。 mapService 用了粘滞开关 instance.spinPaused: goHome() 与 returnGlobal 把它置 true,而 setGlobeSpin(>0) 命中它时 return true —— 代码里没有任何地方会把它清回 false。于是回引导态那次起转被静默吞掉, 且调用方(外壳挂载时的 600 ms 重试)以为已经成功、不再重试。

修法:

  • 相机侧:新增 mapService.resetToIntroView(),把相机送回 INITIAL_PANORAMA(0/−60/2×10⁷), 中心沿用当前位置(与「返回全球」同一条口径);returnToIntro() 改成 「显示引导层 + 回初始全景 + 恢复自转」三件事一起做。 已经在初始全景上就不排飞行 —— 否则自转要白等一个飞行时长才起得来。
  • 自转侧:删掉粘滞开关,把"意图"与"正在转的循环"分开 —— 意图 spinWanted(想要几秒一圈)、循环 instance.spin。 setGlobeSpin() 先记意图;"现在能不能转"按相机是否真的停稳(state.settled)判, 不能转就如实 return false(不再假装成功);等相机落定后由 resumeSpinIfWanted() 自动接上 (挂在 notifyCameraStateChange,飞行最后一帧必到)。 stopSpin() 负责放弃意图(进入系统/归位/返回全球都不该再自己转起来), startSpin() 只停循环、不抹掉意图。
  • 读数加 spinWanted:只报 spin 分不清「意图立着但相机还没落定」与「压根没想转」。

证据:

  • 新闸门 verify/t40-intro-return.mjs(静态守卫 + 一处纯行为断言):禁止 spinPaused 复活、 断言"记下意图之后不得 return true"、断言 stopSpin 放弃意图而 startSpin 只停循环、 断言落定后兑现意图、断言 returnToIntro 三件事齐备、断言 resetToIntroView 套用初始全景 且已在其中时不排飞行、断言阈值复用 SETTLE_EPSILON、断言读数带 spinWanted。
  • 定向变异 10/10 全部打红 —— 把两个原缺陷的形状各自复现一遍都抓得住: return true 假装成功、spinPaused 复活、startSpin 误用 stopSpin、stopSpin 不放弃意图、 落定后不兑现、回引导态不送相机、不套用初始全景、去掉"已在其中"短路、改用魔数、读数去掉 spinWanted。
  • 部署手册第 5 节补一行「闲置回引导」自检(含读哪两个读数);顺手修掉该表一处属性名写错 (data-fog-phase → data-map-fog-phase;实际写的是 host.dataset.mapFogPhase)。
  • 全量闸门 33 通过 / 1 失败,唯一失败 t04-config 与本票无关(并发会话的 cockpitApi.js 硬编码后端地址,见票 07 方向);t37 已随「契约改 10 秒」转绿。

二、诊断中发现的规格漂移(2026-09-30 已由维护者定夺:改为 10 秒并同步契约)

domain/areaLimits.js 的实现值原先是 10000;本轮并发写入期间一度被改成 2000,导致 t37 立即失败。维护者已经明确选择「我要 10 秒:把契约一起改」,因此我把实现恢复为 10000,并同步更新了五处权威: ADR 0009 / 票 37 验收 / 部署手册 / 常量注释 / 闸门 t37(含 walkthrough 的等待时长)。 现在实现与契约重新一致,t37 已转绿。

三、逐条验收状态(本票不能 close,原因在表里)

# 验收项 状态
1 矩形内部 LOD 换级真机确认;可辨则在不接管场景/不引第二渲染器边界内评估 遗留 —— 观感判据,需真机(本会话工具清单里没有浏览器工具)
2 异常降级四条路径各有可复现证据且提示如实 部分 —— 逻辑面已覆盖(① 无深度能力 / ② 自建雾失败 → t38-fog;③ 目标瓦片失败 → t39-handover;④ 主场景区未配置 → t36/t37);真机逐条复现仍缺
3 管线视图:近处管线可读;若因透明深度错位减弱/暂关雾,记录处置与文案 遗留 —— 需真机
4 性能:无原型基线与开雾后 P95 帧耗时,合计增幅 ≤15%;首启与进出切换卡顿单独记录 遗留 —— 需真机同机/同分辨率/同视角采样。(票 38 曾测得开/关雾 P95 增幅 0%,但那是 CPU 侧口径、且不是本票要求的"无原型基线",不能顶替)
5 走查证据:进入 → 锁定 → 拉远被夹 → 边界回弹 → 返回全球 → 闲置回引导;覆盖浅俯角与多旋转方向 部分 —— 末段「闲置回引导」的功能缺陷本轮已修并有闸门+变异;链路其余段由 t37-area-limits-walkthrough 脚本覆盖。真机逐帧观感与"多方向"仍待维护者
6 规格同步:实现与 spec 的偏差逐条回写;改口径需确认 部分(已回写两处,均经维护者确认或记录) —— ① 票 39 的部署值 vs 代码默认(有意分离,已记录);② GLOBAL_IDLE_MS 5秒→10秒(维护者 2026-09-30 确认,契约五处已同步,见上)
7 原型归档到 throwaway 分支 + 留分支指针 + 生产路径不挂原型 已完成(2026-09-30,维护者确认"现在就做") —— 归档分支 throwaway/cockpit-prototypes @ 81932a0;生产树已删 src/modules/cockpit/prototypes/ 并摘除 /cockpit 入口接线(见下)
8 运维自检:手册增一节「限位与雾气是否生效」 已完成 —— 手册第 5 节「部署后自检」已含主场景区/进区限位/区内视距雾/雾的高度收口/雾不"一瞬间"散掉/粗→精交接/先收后撤全表;本轮补「闲置回引导」一行并修一处属性名错误
9 收口时区分已验证 / 已知代价 / 遗留 进行中 —— 见上表与下方"已知代价"

四、已知代价(明确接受,不当作达标)

  • 交接 requireTilesLoaded: false 牺牲的正是决策 08 设它要防的"先空白再跳方块"(票 39 记录)。
  • 退出时「相机已高于雾的全遮蔽端点而雾仍可见」的窗口 ≈ 387 ms(票 38/39 记录)。

五、验收第 7 条(原型归档)— 已完成(2026-09-30,维护者确认"现在就做")

归档用 git plumbing(临时 index)完成,没有动工作树里的并发在途改动:

  • 归档分支:throwaway/cockpit-prototypes @ 81932a0 (git log -1 该分支查看 commit:'归档:可丢弃原型(Transition/Fog/Anchor)与 /cockpit DEV 接线(票 40 收口)')
  • 归档内容(7 个原型文件 + 带 DEV 接线的 src/pages/cockpit/index.vue): anchor-prototype-effects.js、anchor-prototype-layout.js、AnchorPrototype.vue、 fog-prototype-stage.js、FogPrototype.vue、transition-prototype-scene.js、TransitionPrototype.vue
  • 生产路径:已删除 src/modules/cockpit/prototypes/;src/pages/cockpit/index.vue 已摘除 三组 defineAsyncComponent / show*Prototype 接线(现在只挂 CockpitShell,全仓再无 cockpit/prototypes 引用)。
  • 波及面已收:t39-handover.mjs 原先读 TransitionPrototype.vue 的两条断言改为 断言"生产树已无 prototypes/ 目录";服务层的 alpha 互斥开关(setHandoverPrototypeActive / applyHandoverAlpha 内 if (handoverPrototypeActive) return)保留 —— 一旦日后从归档分支还原原型, 互斥仍成立。cesiumEngine.js 两处提到 prototypes/fog-prototype-stage.js 的注释已标注"已归档分支"。
  • 还原方法:git checkout throwaway/cockpit-prototypes -- src/modules/cockpit/prototypes src/pages/cockpit/index.vue

六、下一步

  1. 真机四项(LOD 换级 / 管线视图 / 性能 P95 / 走查逐帧)需要可用的浏览器环境; 本轮本会话没有浏览器工具,只能给出逻辑与结构证据 —— 这四项是本票仍不能 close 的原因。
  2. 真机确认「闲置回引导」观感(镜头回 0/−60/2×10⁷ + 星球重新自转);按手册第 5 节「闲置回引导」自检行 读 __cockpitMap.getLifecycleStats() 的 spin/spinWanted 与 getCameraState() 即可。