diff --git a/docs/20_调查/RenderSegment多Retained段全局Placement门槛_粒子管理员审查说明_v1.md b/docs/20_调查/RenderSegment多Retained段全局Placement门槛_粒子管理员审查说明_v1.md new file mode 100644 index 000000000..4e0f3f520 --- /dev/null +++ b/docs/20_调查/RenderSegment多Retained段全局Placement门槛_粒子管理员审查说明_v1.md @@ -0,0 +1,436 @@ +# RenderSegment 多 Retained 段全局 Placement 门槛——粒子管理员审查说明 v1 + +日期:2026-10-07 +性质:调查结论 / 修改目标 / 管理员审查输入,不是直接实施任务书。 +目标读者:Particle Shared Core / Renderer 维护者。 + +## 1. 本文要解决的问题 + +NavSea 当前已经能通过公开 `RendererRuntime.setRenderSegmentPlan()` 配置任意连续的 `RETAINED / CONTINUOUS` Segment,且 `.19 / .20` 的 RenderSegment 实现已经具备 source-local revision / readiness / cache diagnostics 等基础。 + +但 NavSea 真实运行验证发现:当存在多个 Retained Segment 时,一个与某 Retained Segment 无直接关系的全局 placement/fade 状态,仍可能把所有 Retained Segment 同时打回 fallback,导致多 Segment 不能发挥设计目标。 + +本文希望 Particle 管理员审查并决定:怎样把剩余的 placement/global fallback 门槛收敛到正确粒度,同时保持 fail-closed、Style Z-order、跨平台语义和 C-001 边界不变。 + +## 2. 正式边界 + +跨项目 Contract 仍为 `C-001`。 + +本问题属于 Particle Renderer 内部实现优化,目标上不要求新增 NavSea 私有 Native 通道,也不要求 Particle 理解 AIS、Route、Weather、Alarm 等 NavSea 业务语义。 + +NavSea 仍只负责: +- 按 Style Z-order 选择 Segment 边界; +- 通过稳定公开 API 提交 RenderSegmentPlan; +- 读取 diagnostics; +- 决定天气等业务能力何时显示。 + +Particle 仍负责: +- Segment resolve / cache / invalidation / renderer execution; +- placement / transition / fading / source dependency 的正确性; +- GPU resource 和 stale-cache 安全; +- Android / iOS 共用的 Shared Core 语义。 + +本修改若不改变公共 API、生命周期、错误语义或双方职责,不需要升级 Contract。 + +## 3. 当前 Mac 基线需要管理员先确认 + +本次只读检查发现: + +- `/Users/tei/dev/MapLibreParticleLab` + - branch: `particle/main` + - HEAD: `750c14f52708d7dc93d8eb6ec2b21112185afd5c` +- `/Users/tei/dev/MapLibreParticleLab-release-v19` + - branch: `release/particle.19` + - HEAD: `6b48f905d2d17a8271b6ff883e682db7bc45dcd3` +- `/Users/tei/dev/MapLibreParticleLab-release-v20` + - branch: `release/particle.20` + - HEAD: `53a844e81849f254ba2d169c2d875dbd862cc7d5` +- `/Users/tei/dev/MapLibreParticleLab-step101` + - branch: `step101-render-orchestrator-teardown` + +NavSea 当前实际消费的是 `.19` 行为;本次检查确认 `release/particle.19` 与 `release/particle.20` 在本文相关 placement gate 代码上相同。 + +注意:当前 `particle/main` 与 `release/particle.19/.20` 的 RenderSegment 执行细节存在差异,因此管理员开始修改前必须先明确正式修复基线,不要把 main 与 release 分支默认视为完全一致。 + +## 4. NavSea 当前为什么需要多 Segment + +NavSea 当前为了避免旧的 retained fallback,采用保守的两段方案: + +```text +R0 [0,31) RETAINED +C0 [31,114) CONTINUOUS +``` + +C0 从 `rn-weather-color` 开始一直覆盖到 Style 尾部。 + +真实 diagnostics 已确认该 Continuous 尾段包含天气之后的大量运行时对象,包括 AIS / ownship / navigation / alarm 等 source/layer。 + +这能保护 R0 底图复用,但代价是:天气粒子需要持续 repaint 时,天气以上整个 Style 尾部都留在 Continuous 区域。 + +RenderSegment 的原始设计目标本来就是允许: + +```text +R0 → C0 → R1 → C1 → R2 ... +``` + +并严格保持 Style Z-order,不把业务语义写进 Renderer。 + +## 5. NavSea 真实多段实验 A + +第一次临时配置: + +```text +R0 [0,31) +C0 [31,79) +R1 [79,98) +C1 [98,114) +``` + +Native 接受该配置: + +```text +configured=true +valid=true +segmentCount=4 +retainedSegmentCount=2 +continuousSegmentCount=2 +coverageComplete=true +fallback=false +``` + +解析结果: + +- R0: `background` → `place-labels-land-ja` +- C0: `rn-weather-color` → `ownshipRangeLabelLayer` +- R1: `routeLineActiveShadowLayer` → `planSimBoatLabelLayer` +- C1: `crossCursorGotoLineLayer` → `chartDangerScanCenterLineLayer` + +R1 当时依赖: +- `navigationSource` +- `ownshipTrackSource` +- `planSimRouteSource` + +结果 R1 为 `source-fading` fallback,`reuse=0`。 + +这个结果本身还不能证明 placement 问题,因为 R1 确实包含活动 source。 + +## 6. NavSea 真实多段实验 B:把 R1 收窄到 idle PlanSim + +第二次把 R1 收窄为只有 PlanSim Route 的 10 个 layer: + +```text +R0 [0,31) +C0 [31,88) +R1 [88,98) +C1 [98,114) +``` + +R1: +- start: `planSimRouteShadowLayer` +- end: `planSimBoatLabelLayer` +- dependent source: 仅 `planSimRouteSource` +- 当时 PlanSim 为 idle。 + +5 秒早期诊断仍有 `source-fading`,所以继续等待到约 20 秒稳定态。 + +20 秒稳定态关键结果: + +```text +fadingSourceIds = [] +``` + +R0: +```text +sourceReady=true +sourceStable=true +cacheState=fallback +build=0 +reuse=0 +invalidation=306 +lastInvalidationReason=static-placement +finalDecision=fallback +``` + +R1: +```text +sourceReady=true +sourceStable=true +cacheState=fallback +build=1 +reuse=0 +invalidation=306 +lastInvalidationReason=static-placement +finalDecision=fallback +``` + +这说明稳定 source 已经不是阻塞因素;两个 Retained Segment 被同一个 `static-placement` 门槛同步打回 fallback。 + +## 7. release/particle.19 / .20 当前代码事实 + +### 7.1 per-segment source locality 已经存在 + +`.19 / .20` 在 `renderer_impl.cpp` 中会对每个 Retained Segment 单独计算: + +- `retainedSegmentSourceState(begin,end)` +- source-local revision +- source `ready / stable` +- `retainedSegmentIsStable(begin,end)` +- `segmentSupportsCache[]` +- 每个 cache 独立 `evaluate()` +- 每段独立 diagnostics / finalDecision + +`render_orchestrator.cpp` 中 `retainedSegmentSourceState()` 已通过 RenderSegment dependency 只检查该 Segment 相关 source 的 loaded / fading 状态。 + +这部分方向是正确的,不应回退到全局 source invalidation。 + +### 7.2 剩余 placement gate 仍是全局聚合 + +`.19 / .20` 的 `renderer_impl.cpp` 仍先对全部 Retained Segment 聚合: + +```cpp +bool retainedStaticUsesPlacement = false; +... +retainedStaticUsesPlacement = + retainedStaticUsesPlacement || + orchestrator.retainedRangeUsesPlacement(...); + +const bool retainedPlacementStable = + !retainedStaticUsesPlacement || + (!renderTreeParameters.placementChanged && + renderTreeParameters.symbolFadeChange == 0.0f); +``` + +然后每个 Segment 的 `segmentSupported` 又共同依赖同一个 `retainedPlacementStable`。 + +因此只要任一 Retained Segment 使用 placement,并且当前全局 frame 有 `placementChanged / symbolFadeChange`,所有 Retained Segment 都可能失去 cache eligibility。 + +### 7.3 full CPU mixed execution 已有基础,但仍有 globalLiveFallback + +`.19 / .20` 已有 `visitRenderSegmentExecution(...)`,理论上可以: + +```text +R0 reuse +C0 live +R1 fallback/live +C1 live +R2 reuse +``` + +但是进入该混合执行前仍有: + +```cpp +const bool globalLiveFallback = + ... + !retainedPlacementStable || + orchestrator.retainedGlobalLightTransition(); +``` + +`!retainedPlacementStable` 会直接导致: + +```text +drawableOpaquePass(0, layerCount) +drawableTranslucentPass(0, layerCount) +``` + +即整帧退回全 live,绕过 per-segment mixed execution。 + +这与 NavSea 实验中 R0/R1 同时出现 `static-placement` fallback 的现象一致。 + +## 8. 希望管理员审查的核心修改目标 + +目标不是简单“关闭 placement 检查”。 + +目标是: + +> 当 placement / symbol fade 只影响某些 Segment 时,不应让与其无关、且自身内容/依赖/相机均稳定的 Retained Segment 一起失效。 + +期望行为示例: + +```text +R0:不含 placement layer,source stable +C0:Weather continuous +R1:含 placement 或当前 placement unstable +C1:AIS / Alarm continuous +R2:不含 placement layer,source stable + +期望: +R0 = reuse +C0 = live +R1 = fallback/live +C1 = live +R2 = reuse +``` + +而不是: + +```text +R1 placement unstable +→ global retainedPlacementStable=false +→ R0/R1/R2 全部失效 +→ 整帧 full live +``` + +## 9. 建议管理员重点评估的实现方向 + +以下是审查建议,不是强制实现方案。 + +### 9.1 先把 placement eligibility 变成 per-retained-segment + +对于每个 Retained Segment 分别判断: + +- 该 Segment 是否实际 `usesPlacement`; +- 该 Segment 的 layer transition / needsRepaint; +- 该 Segment 的 source readiness / fading; +- placement transition 是否确实会改变该 Segment 的最终像素; +- 是否存在必须全局 fail-closed 的 placement 共享状态。 + +至少对于 **完全不含 placement layer 的 Retained Segment**,应评估是否有理由继续受全局 `placementChanged / symbolFadeChange` 阻塞。 + +### 9.2 审查 `globalLiveFallback` 中的 `!retainedPlacementStable` + +建议确认是否可以: + +- 从 globalLiveFallback 移除纯 placement 条件; +- 让 placement 结果进入每段 `segmentSupported / decision`; +- 真正使用现有 `visitRenderSegmentExecution()` 做 mixed retained/live。 + +真正全局的条件仍可保持全局,例如: +- 3D; +- Debug 模式; +- MapMode 不支持; +- sprite/image/global resource 未就绪; +- global light transition(若确实会改变所有段); +- context/resource 失效; +- 无法证明安全的全局状态。 + +### 9.3 不要破坏已有 source-local 机制 + +`.19/.20` 已有 `retainedSegmentSourceState()` 与 dependency-scoped revision/readiness。 + +目标应是在此基础上继续收窄 placement/global gate,而不是重新把 source/tile mutation 退回“所有 Retained 一起 invalidate”。 + +### 9.4 placement 本身若无法简单 local 化,应明确分层 + +管理员需要判断 MapLibre placement 是否存在以下区别: + +1. Segment 完全不使用 placement:理论上是否可忽略全局 placementChanged? +2. Segment 使用 symbol placement,但其相关 source/layer 没变化:是否仍必须 invalidate? +3. cross-source collision / shared collision index 是否会让某 Segment 的符号像素受其他 Segment placement 影响? +4. `symbolFadeChange` 是全局时间量还是可以按 dependency/range 推导? +5. 是否需要一个 per-segment placement revision/fingerprint,而不是 bool? + +如果 2/3 无法安全局部化,也至少应优先解决第 1 类,让“无 placement Retained Segment”不被无关 placement 拖死。 + +## 10. 不能牺牲的正确性约束 + +任何优化都必须保持: + +- Style Z-order 是唯一顺序事实; +- Segment 不重新编号 Layer 的全局 ordinal; +- opaque/translucent/depth 语义不变; +- retained cache 不得显示 stale 像素; +- Camera / viewport / pixelRatio 变化仍正确 invalidation; +- Style reload / topology change 正确释放旧 cache; +- Android / iOS 使用同一 Shared Core 语义; +- 不把 AIS / Route / Weather 等业务名称写进 Renderer; +- 不要求 NavSea 手工 invalidate Native cache; +- 不新增“只为 NavSea 绕过 Renderer”的私有 API。 + +## 11. 建议新增/补强的 Core 测试 + +至少建议覆盖: + +### Case A:无 placement 的 R0 + 有 placement 的 R1 +- R0 stable; +- R1 发生 placement transition; +- 期望 R0 继续 reuse; +- R1 单独 fallback/live; +- 最终 Z-order 正确。 + +### Case B:两个 Retained 都稳定 +- R0/R1 均 build 一次; +- 后续 reuse 持续增长; +- Continuous 段持续 live。 + +### Case C:单一 source fading +- fading source 只属于 R1; +- R1 fallback; +- R0 不 invalidation。 + +### Case D:Continuous source 高频 mutation +- mutation 只属于 C1; +- R0/R1 retained revision 不变化; +- cache 可继续 reuse。 + +### Case E:真正全局状态 +- global light transition、3D、Debug、context/resource failure 等; +- 继续 fail-closed; +- 不为追求 reuse 放宽安全边界。 + +### Case F:topology 变化 +- R-C-R-C → R-C; +- 旧 retained texture/compositor 正确释放; +- 不 composite stale resource。 + +## 12. Diagnostics 验收要求 + +修复后 diagnostics 必须能证明“局部失败不扩散”。 + +NavSea 重新做 R-C-R-C 真机测试时,至少需要看到类似: + +```text +R0: + sourceReady=true + sourceStable=true + cacheState=reuse + reuse 持续增长 + +R1: + finalDecision=fallback + lastFallbackReason=static-placement + +C0/C1: + 正常 live +``` + +关键验收不是 Fast Path hit。 + +多 Continuous Segment 本来就可以继续走 full CPU mixed renderer;即使 `fastPathHit=0`,只要稳定 Retained 段能够独立 reuse,优化就是有效的。 + +## 13. 与 NavSea 最新 Trace 的关系 + +`Trace-20261007T224751.json` 是 UDP + 天气 UI 的 DevTools Trace。 + +该 Trace 中约 11–13 秒和 21–23 秒出现两组很高的 JS 峰值;调用栈显示峰值主要落在 React root work 与 `WeatherForecastStrip / WeatherTable`,其中 `WeatherTable` 两次重 render 分别约 0.91s / 1.07s,AIS decoder 并不是这两个峰值的主要执行栈。 + +代码同时显示 `WeatherForecastStrip` 会订阅 `smoothGPS`,并用经纬度 `toFixed(2)` 形成天气采样位置 key;但当前尚未完成对这两次具体 WeatherTable render 触发源的一一证明,因此本文不把“约 10 秒”直接归因于 GPS 跨格或任何单一 NavSea 机制。 + +这些 JS/UI 峰值属于 NavSea 侧独立调查项,不应作为 Particle RenderSegment 修改的直接证据。 + +Particle 本次应以 **RenderSegment diagnostics 的 R0/R1 `static-placement` 同步 fallback** 为主要证据。 + +## 14. 管理员需要给出的结论 + +请管理员审查后明确回答: + +1. `.19/.20` 当前全局 `retainedPlacementStable` 是否确实过度保守? +2. 对“不含 placement layer”的 Retained Segment,是否可以安全解除全局 placementChanged/symbolFadeChange 阻塞? +3. `globalLiveFallback` 是否应去掉 placement 条件,转为 per-segment decision? +4. 若 placement 必须部分全局,最小安全局部化边界是什么? +5. 正式修改应基于 `release/particle.20`、合并回 `particle/main`,还是先在独立工作树完成? +6. 是否需要新 ADR/RFC?初步判断:若公共 API/C-001 语义不变,只是 Renderer 内部优化,可不新增跨项目 Contract;若管理员认为会改变 RenderSegment 公共语义,再交回 Integration 决定。 + +## 15. 当前 NavSea 状态 + +NavSea 临时 R-C-R-C 实验已经撤回。 + +当前恢复为原有保守方案: + +```text +R0 [0,31) RETAINED +C0 [31,114) CONTINUOUS +``` + +因此管理员可以先只做 Native/Core 审查,不需要 NavSea 同步修改。 + +等 Particle 给出明确方案和可验证版本后,再由 NavSea 重新选择真实 Style 边界并做 R-C-R-C / R-C-R-C-R 真机验证。