# STEP-100 Source → RenderSegment 局部 Revision / Invalidation / Readiness 与独立 Fallback — Codex 任务 v2 ## 0. 任务性质 这是 STEP-099 之后的正式修复任务。 STEP-099 已经完成纯诊断并 CLOSED / PASS,已确认 V17 的核心问题: 1. 动态 GeoJSON Source/Tile mutation 会推进 global retained content revision; 2. 即使 R0 不依赖该 GeoJSON,R0 retained cache 仍跟着 invalid/build; 3. GeoJSON 更新期间约 200 ms 的 transient `isLoaded=false` 会通过 global `RenderOrchestrator::isLoaded()` 把整个 retained path 拉成 `style-not-loaded`; 4. 当前 renderer 还存在 `useRetainedRanges` all-or-none gate:一个 RETAINED Segment fallback 时,会使其他本来可安全 reuse 的 RETAINED Segment 一起退出 retained path; 5. STEP-099 diagnostics 已能正确给 source mutation 计算 `affectedSegmentIndices`,但该依赖关系仍只是诊断,不参与正式 invalidation。 本任务目标是把 **Source/Tile 变化对 RETAINED Segment 的影响正式局部化**。 本任务不是 V18 发布任务。Codex 只负责实现与源码级说明;Android/iOS 构建、APK、ADB、设备测试、最终 Git push、V18 release 由 RDC/ChatGPT 在 Codex 完成后独立执行。 未经用户明确要求,本任务 **不加入、不运行 Jest**。 --- ## 1. 冻结基线 工作目录: `/Users/tei/dev/MapLibreParticleLab-step100` 分支: `step100-segment-local-source-invalidation` 开始基线必须重新核对,当前预期: - `origin/particle/main` - HEAD:`c4b481fd2c5b5da20d31f6ae54011f0552d7dbd0` - STEP-099 implementation:`8a5214d92dd2f8899db76d2194da42999fa0c87d` - V17 frozen tag:`maplibre-particle/rn/13.4.1-particle.17` - V17 frozen commit:`e8b451fe70054107773e82484c80e90df3889147` 禁止修改、移动、覆盖 V17 tag / release worktree。 STEP-100 是 V17 之后的新开发,最终只有 RDC 验收通过后才允许进入 V18 发布。 开始前必须读取: - 当前仓库实际 HEAD / status / diff; - 全局 Codex AGENTS; - `ARCHITECTURE.md`(若存在); - STEP-092 / 094 / 099 的任务、Step 记录、完成报告; - 当前: - `renderer_impl.cpp` - `render_orchestrator.*` - `retained_underlay_cache.*` - `retained_underlay_runtime_state.hpp` - `render_segment.*` - STEP-099 diagnostics 与 Android fixture; - 当前 RenderSegment / retained cache / particle fast path tests。 不得凭历史印象修改。 --- ## 2. 最终架构原则 长期 RenderSegment 模型继续保持通用模型: ```text Style Z-order ↓ R / C / R / C / R / ... ``` 其中: - R = RETAINED - C = CONTINUOUS 本任务只用 **R-C-R** 做最小验证,不把 R-C-R 固化成最终架构。 必须保持: 1. Style layer 顺序是唯一 Z-order 真相; 2. Renderer 不理解 AIS / Route / Weather / GeoJSON 等业务名; 3. Source → Segment 依赖由当前实际 Style / RenderLayer / Source 关系推导; 4. 一个 Source 可依赖多个 Segment; 5. source-less layer 不制造假 dependency; 6. unknown/global dependency 必须 fail closed; 7. 相邻 Segment、多个 C、多个 R 的通用模型不能被破坏; 8. 本任务不实现 multi-continuous Fast Path。 --- ## 3. 本任务必须实现的核心目标 ### 3.1 Source/Tile mutation 局部化 假设: ```text R0 depends [staticSource] C depends [particleField] R1 depends [dynamicGeoJson] ``` 当: ```text dynamicGeoJson mutation ``` 正式 renderer 必须达到: ```text R0: content/revision 不因该 source mutation 改变 cache 不 invalid cache 不 rebuild 持续 reuse R1: revision/invalidation 正常变化 按需要 build/reuse/fallback C: 正常 continuous render ``` 禁止继续出现: ```text dynamicGeoJson mutation ↓ global revision 变化 ↓ R0 + R1 一起 rebuild ``` ### 3.2 保留 global revision 兼容信号 STEP-100 不要求删除现有 global `retainedUnderlayContentRevision`。 为了保持: - diagnostics; - 现有 fast-path correctness; - 旧 API / compatibility; - conservative fallback; 允许并推荐继续维护 global revision。 但 **RETAINED Segment cache 的正式内容 revision / invalidation 不能再只依赖这个 global source/tile revision**。 当前 `retainedUnderlayRevisionTracker` 同时消费 global content revision 与 `renderTreeParameters.loaded`。STEP-100 必须明确拆成两种用途: ```text global retained revision / tracker -> compatibility / Fast Path / diagnostics segment-local retained content state -> full CPU renderer 的每个 RETAINED Segment Build / Reuse / Fallback ``` **Segment-local cache content state 禁止继续直接消费:** - global source/tile content revision; - global `renderTreeParameters.loaded`。 否则即使 source-scoped revision 已实现,R0 仍会被无关 GeoJSON transient unloaded 或 global revision 连带失效。 允许 global revision / global tracker继续按原逻辑变化;禁止为了让测试 PASS 而停止 global revision 增长或放宽 Fast Path。 ### 3.3 per-RETAINED-Segment readiness 不得修改任何 Source `isLoaded()` 定义。 不得修改: ```cpp renderTreeParameters->loaded = updateParameters->styleLoaded && isLoaded(); ``` 这个 global loaded 仍作为 MapLibre 现有全局兼容事实。 但 retained segment 的 cache Build/Reuse/Fallback 必须有自己的安全 readiness 判断。 最低语义: - Style 本身未 ready:fail closed; - image manager / sprite / glyph 等无法安全定位到单 Segment 的全局前置条件未 ready:fail closed; - Segment 已知依赖的 source 中任一缺失或 `isLoaded()==false`:仅该 Segment fail closed; - Segment 已知依赖的 source `hasFadingTiles()==true`:仅该 Segment视为 unstable; - **不被该 Segment 引用的 source unloaded 或 fading,不得让该 Segment cache fallback/rebuild**; - `retainedRangeIsStable(begin,end)` 的 source fading 检查必须按该 range 的真实 source dependency 缩小,不能继续遍历全部 `renderSources` 作为 full-CPU retained gate; - Fast Path 原有 global fading gate 保持不变; - source ID 无法解析、dependency 缺失或真正无法证明安全时 fail closed。 必须区分: 1. **已知 Source dependency**; 2. **symbol/global prerequisite**(glyph/image/placement 等); 3. **真正 unknown dependency**。 STEP-099 的 `unknownDependencies` 目前会把 symbol 相关条件标为 unknown。STEP-100 不得机械地把“存在 symbol”解释成永久 fallback;symbol 所需 glyph/image/placement 应作为明确的 global prerequisite/fail-closed 条件处理。 因此 T3 中一个: ```text no layer enabled=false isLoaded=false ``` 的 RasterArray source 可以继续让 global `RenderOrchestrator::isLoaded()==false`,但不得因此让与它无依赖关系的 R0/R1 cache 全部 `style-not-loaded`。 本 Step 的运行态硬验收范围是 **MapMode::Continuous**(粒子正式实时渲染模式)。当前非 Continuous / still 路径如果在 global unloaded 时提前返回,本 Step 不强行改变其兼容语义;完成报告必须明确该适用范围。 ### 3.4 RETAINED Segment 独立决策 当前 V17 路径存在类似: ```text useRetainedRanges = all retained segments supported ``` 的 all-or-none 语义。 STEP-100 必须改为每个 RETAINED Segment 独立拥有: - readiness/support decision; - Build / Reuse / Fallback; - content/source revision; - invalidation reason。 如果: ```text R0 = Reuse R1 = Fallback ``` 则本帧必须允许: ```text R0 composite cached texture C live render R1 live render fallback ``` 而不是退化为“整个 Style 全部 live render”。 如果某 retained cache 自身 build 失败,应优先只 fallback 该 Segment;只有无法证明局部安全的 context/global resource failure 才允许全 retained fail closed。 --- ## 4. Source-scoped revision 设计要求 Codex 必须基于当前代码选择最小、稳定设计,并在完成报告说明实际方案。 推荐方向但不强制具体类名: - 为 Source/Tile mutation 维护 source-scoped monotonic revision; - keyed by stable sourceId,但必须处理 source 删除后同 ID recreate,不能让新 source 错误继承可复用的旧 cache identity;可用 generation/epoch 或保留单调 tombstone 语义实现; - source diff、feature-state mutation、tile changed 等所有当前会进入 Source/Tile retained content invalidation 的正式路径都必须覆盖; - 能定位 sourceId 的 mutation 只推进对应 source revision;无法定位 sourceId 的 mutation继续 global fail closed; - Segment content revision 由: - 该 Segment 的 dependent source revisions; - 当前 Segment layer fingerprint / layer mutation; - 必须全局影响 retained 的 global revision 类别; - topology / camera / viewport 等既有安全条件 共同决定。 Source/Tile mutation 不应推进“所有 Segment 都消费的 retained cache revision”。 必须避免: - 用屏幕位置猜 dependency; - 在 renderer 中硬编码 source/layer ID; - 从 STEP-099 diagnostics JSON 反向驱动正式 renderer; - 建立 diagnostics 与 production 两套互相漂移的 dependency 真相。 如果需要提取 production-grade dependency helper,应让 diagnostics 和正式逻辑共享同一只读推导源。正式 renderer 不得从 diagnostics JSON 或 `RetainedUnderlayRuntimeState` 反向取 dependency。 Source-scoped revision 属正式 correctness state,不得沿用 STEP-099 diagnostics 的“best-effort 记录失败可忽略”语义。正式 revision/dependency state 若无法可靠更新,必须 conservative global invalidation/fail closed。 --- ## 5. Global mutation 仍可保守处理 本任务只要求 **Source/Tile mutation** 与明确 Source readiness 局部化。 以下无法安全局部化的事件,本轮允许继续 invalidate/fail closed 全 retained: - context reset; - style reload / topology change; - image mutation; - glyph mutation; - sprite/image manager 全局 readiness; - placement / symbol fade / glyph/image/sprite 全局状态; - camera / transform / viewport / resize / pixel ratio; - debug mode; - 3D / unsupported / legacy ordering; - 无法定位 dependency 的 mutation; - 真正 unknown/global dependency。 注意:**Source 自身的 loaded / fading 不属于这里的 global fail-closed 项**;若 source dependency 可定位,必须按 Segment 局部判断。Fast Path 的 global source/fading gate除外,本轮保持原状。 完成报告必须列出: - 已局部化的 mutation 类型; - 仍为 global 的 mutation 类型; - 为什么保守处理。 --- ## 6. Fast Path 本轮严格不改 STEP-099 已确认 Fast Path 仍有 global gates: - global update signature(sources/layers/images 等); - global content revision; - global fade; - pendingChanges; - placement/light/non-particle transition/repaint; - annotation mutation; - topology/boundary/revision/cache validity; - `singleContinuousSegment`。 STEP-100 **禁止修改这些 gate 的语义**。 允许: - Source mutation 后 Fast Path miss; - transient source readiness 时 Fast Path fail closed; - full CPU renderer 被调用。 但进入 full CPU renderer 后,必须能做到: ```text R0 retained cache reuse R1 rebuild/fallback ``` 这正是本任务验收目标。 Multi-continuous Fast Path 明确不在本任务。 --- ## 7. Render 顺序与混合 Build/Reuse/Fallback 实现 per-segment mixed decisions 时必须保持原 MapLibre 绘制语义: - Style Z-order; - global layerIndex; - opaque reverse traversal; - translucent forward traversal; - currentLayer; - depthRangeSize; - legacy item 不双绘; - debug overlay 不重复; - retained texture composite 不改变 segment 顺序。 推荐的逻辑行为: ```text for segment in Style order: RETAINED + Build: build cache composite cache RETAINED + Reuse: composite cache RETAINED + Fallback: live render only this range CONTINUOUS: live render this range ``` 实际代码可按现有 RenderPass 安全实现,不要求机械照搬伪代码。 必须特别检查: - 一个 R fallback 不得导致另一个 R live redraw; - resource prepare 在 Build 前失败时,必须先把该 Segment 本帧 decision 改为 Fallback,再进入主 buffer 绘制; - retained build 中途失败时,必须 invalidate/标记该 Segment失败并把该 Segment 本帧 decision 改为 Fallback,禁止 composite 旧纹理; - 如果失败意味着 context / shared GPU state 已不可信,允许全局 fail closed; - fallback range 的 legacy items 只绘一次,不得因 per-segment 主循环重复 render; - debug overlay 全帧只绘一次; - Segment 间 Opaque/Translucent 语义不得被破坏; - 相邻 R、多个 C、透明叠加与 legacy scene 不能因 mixed execution 出现漏绘/双绘。 推荐执行顺序:先完成各 Segment resource prepare 与必要离屏 Build;再 clear 主 buffer;最后按 Style Segment 顺序逐段选择 cached composite 或 live range render。 --- ## 8. Diagnostics 必须继续可证明局部性 保留 STEP-099 diagnostics,并 additive 增加足够字段,使 RDC 能验证正式行为。 每个 RETAINED Segment 至少应能观察: - dependentSourceIds; - segment readiness / support; - segment-local source/content revision 或等价可证明值; - Build / Reuse / Fallback 的**最终本帧 execution decision**; - build/reuse/fallback/invalidation count; - last invalidation/fallback reason。 Source mutation 继续输出: - sourceId; - sequence/reason/action; - affectedSegmentIndices。 要求: ```text step099-dynamic mutation affected=[R1] R0 revision unchanged R0 cache reuse++ R0 build unchanged R0 invalidation unchanged ``` Diagnostics 仍是 pull/event low-frequency,不允许逐帧高频 spam。 --- ## 9. Android Test App 复用 STEP-099,不另建独立 App。 至少保留并完善: ### T1 STATIC 验证静态 R-C-R: - loaded settle; - R0/R1 build 一次后稳定 reuse; - 不产生持续 rebuild。 ### T2 GEOJSON_1HZ 两个 GeoJSON source 交错,各约 1 Hz。 重点: - global loaded transient false window 可以继续存在; - 只引用某 source 的 retained Segment 才允许因该 source mutation 改变; - 无关 retained Segment build/invalidation 不得跟随增长。 ### T3 PERMANENT_UNREADY 保留一个: - no layer; - enabled=false; - isLoaded=false; - no field submission 的 RasterArray source。 预期: - global renderOrchestratorLoaded 仍可为 false; - 但无关 R0/R1 retained cache 不应因它持续 `style-not-loaded` fallback; - 能安全判断依赖的 retained Segment应继续 build/reuse。 ### T4 R-C-R(主验收) 必须保持清晰依赖: ```text R0 -> dedicated static source only C -> WeatherColor/Particle field R1 -> dedicated dynamic GeoJSON source ``` 运行动态 GeoJSON 约 1 Hz。 必须调整 fixture,避免 STEP-099 中 R1 再复用 R0 的 static source: - R0 static source 只出现在 R0; - R1 dynamic source 只出现在 R1; - 若 MapLibre 内建 annotation source/layer 自动进入 R1,必须在 diagnostics 中列明,并保证验收窗口内 annotation 不发生 mutation; - 不允许用业务 ID 特判 renderer。 RDC 验收区间必须支持“settled baseline → 60 s pure dynamic-source mutation window → final”对比。每个 aggregate sample 同时记录 mutation source/sequence、各 Segment local revision、readiness、最终 execution decision 与 cache counters。 --- ## 10. STEP-100 硬验收标准 ### A. R0 locality T4 settle 后记录 baseline。 随后至少 60 s dynamic GeoJSON: - R0 `buildDelta == 0` - R0 `invalidationDelta == 0` - R0 `reuseDelta > 0` - R0 segment-local revision/content state 不因 R1 dynamic source mutation 改变 - 即使某采样点 global `renderOrchestratorLoaded=false`,只要 R0 自身 prerequisites/dependencies ready,R0 final decision 仍可为 Reuse 如果为了安全出现明确 global event,应在日志中可解释并重新选择纯动态 mutation 区间;不能把 global event 混入后声称 PASS。 ### B. R1 reacts 同一时间窗: - R1 必须能观察到 source mutation; - R1 revision / build / invalidation / fallback 中至少有与动态 source 对应的实际变化; - `affectedSegmentIndices` 必须只包含真实依赖 Segment。 ### C. global revision compatibility - global `retainedUnderlayContentRevision` 允许并预期继续增长; - 不能通过停止 global revision 增长来制造 R0 PASS。 ### D. readiness locality T2 transient unloaded 或 T3 permanent unrelated unloaded 时: - global loaded 可 false; - R0 不得仅因无关 source 的 unloaded 而 invalid/rebuild/fallback; - R0 cache 必须继续可复用。 ### E. correctness - 无 crash / ANR; - 无 stale cache composite; - 无 Segment Z-order 错乱; - 无 fallback range 双绘; - Cache OFF 仍安全走 live renderer; - plan invalid 仍 fail closed。 任一硬门槛失败,STEP-100 不得宣布 PASS,不得进入 V18。 --- ## 11. Native 非 Jest 测试要求 Codex 必须新增或扩展现有 C++ 测试源码,至少覆盖: 1. source A mutation 只改变依赖 A 的 Segment revision; 2. source A 被多个 Segment 共享时,对应多个 Segment 都变化; 3. unrelated Segment revision 不变; 4. source-less Segment 不制造 dependency; 5. unknown/global dependency fail closed; 6. unrelated source unloaded 不使安全 Segment readiness false; 7. dependent source unloaded 使对应 Segment readiness false; 8. R0 Reuse + R1 Fallback 的 mixed decision; 9. 单 Segment build failure 不 composite stale cache; 10. query diagnostics 不改变 revision/cache state; 11. global non-source invalidation 仍影响所有 retained; 12. source 删除后同 ID recreate 不误复用旧 Segment cache identity; 13. dependent source fading 只影响对应 Segment;unrelated source fading 不影响安全 Segment; 14. Cache OFF / invalid plan 行为不回归; 15. 至少有一组能证明实际 mixed render 没有漏绘/双绘的 renderer-level draw-count、pixel 或现有等价测试入口;不能只用 decision unit test 代替。 不得修改测试断言去适应错误实现。 --- ## 12. 允许修改范围 允许: - RenderOrchestrator source mutation/readiness bookkeeping; - RenderSegment dependency production helper; - Renderer retained per-segment revision/readiness/decision; - RetainedUnderlayCache/RuntimeState 为局部状态增加必要字段; - additive diagnostics JSON/TS; - Android Validation Test App STEP-099 fixture/log; - 相关 Native C++ tests; - STEP-100 Step 记录/完成报告/开发记录。 禁止: - NavSea; - Integration; - V17 tag/release worktree; - Source `isLoaded()` 定义; - Particle field/provider/network; - Fast Path gate semantic; - `singleContinuousSegment`; - multi-continuous Fast Path; - 自动改变 Style Z-order; - provider/business-specific source/layer ID; - unrelated cleanup/refactor; - Jest。 --- ## 13. Codex 执行边界 Codex 本轮负责: 1. 读取实际源码和任务书; 2. 实现代码; 3. 编写 Native 非 Jest 测试源码; 4. 更新 Android Test App 验证入口; 5. 执行轻量静态检查,如 `git diff --check`; 6. 生成中文完成报告。 Codex **不得执行**: - Android Gradle / APK; - ADB / 安装 / 实机; - Xcode / iOS device; - V18 release; - tag; - push; - Jest。 本轮 Codex 完成报告固定: `docs/报告/STEP-100_SourceScoped_RenderSegment局部失效_Codex完成报告_v1.md` Step 记录固定: `docs/50_Step记录/STEP-100_SourceScoped_RenderSegment局部失效_v1.md` 完成报告必须写清: - 实际 Codex model / reasoning effort; - start HEAD; - 修改文件; - 生产设计; - source revision/readiness 规则; - mixed retained decision 规则; - Fast Path 明确未改; - 测试源码新增情况; - 实际执行 / NOT RUN; - 风险与 RDC 验收点; - 未 commit / 未 push(如果确实如此)。 Codex 不 commit、不 push。由 RDC 验证通过后统一 Git 收尾。 --- ## 14. RDC / ChatGPT 后续独立验收 Codex 返回后由 RDC 负责: 1. source/diff 审计; 2. Native Mac 编译; 3. Native focused + broad related C++ tests; 4. Android x86_64 OpenGL validation APK 单次构建; 5. Mac ADB 直连 FZG1-2; 6. 自动跑 T1/T2/T3/T4; 7. 解析 R0/R1 build/reuse/invalidation/revision/readiness; 8. 确认硬验收标准 A~E; 9. 若失败,回到 STEP-100 修复,不发布; 10. 若全部 PASS,完成 STEP-100 Git push; 11. 再按现有统一发行流程升级到: `maplibre-particle/rn/13.4.1-particle.18` 12. V18 build/release/public artifact 验证通过后才宣布 V18 发布完成。 --- ## 15. 停止条件 Codex 遇到以下任一情况必须停止并报告,不得扩大范围: - 要修改 Source `isLoaded()` 才能继续; - 需要放宽 Fast Path gate; - 必须改变 singleContinuous / multi-continuous; - production dependency 无法可靠从现有 Style/RenderLayer/Source 推导; - per-segment fallback 会破坏 RenderPass/Z-order 且无法在当前范围安全解决; - 发现需要 NavSea/Integration 配合; - 需要业务 layer/source ID; - 需要改变 V17 release。 --- ## 16. 本任务最终原则 STEP-100 的目标不是让“全局世界看起来 loaded”。 目标是: > **即使某个 GeoJSON Source 正在变化或短暂 unready,只要 R0 与它没有依赖关系,R0 retained cache 就必须继续安全 reuse,不能被迫一起刷新。** Fast Path 是否还能 miss,是后续独立问题。 只有这一点经过 Native + Android R-C-R 实测成立,才允许进入 V18 发布。