22 KiB
STEP-100 Source → RenderSegment 局部 Revision / Invalidation / Readiness 与独立 Fallback — Codex 任务 v2
0. 任务性质
这是 STEP-099 之后的正式修复任务。
STEP-099 已经完成纯诊断并 CLOSED / PASS,已确认 V17 的核心问题:
- 动态 GeoJSON Source/Tile mutation 会推进 global retained content revision;
- 即使 R0 不依赖该 GeoJSON,R0 retained cache 仍跟着 invalid/build;
- GeoJSON 更新期间约 200 ms 的 transient
isLoaded=false会通过 globalRenderOrchestrator::isLoaded()把整个 retained path 拉成style-not-loaded; - 当前 renderer 还存在
useRetainedRangesall-or-none gate:一个 RETAINED Segment fallback 时,会使其他本来可安全 reuse 的 RETAINED Segment 一起退出 retained path; - 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.cpprender_orchestrator.*retained_underlay_cache.*retained_underlay_runtime_state.hpprender_segment.*- STEP-099 diagnostics 与 Android fixture;
- 当前 RenderSegment / retained cache / particle fast path tests。
不得凭历史印象修改。
2. 最终架构原则
长期 RenderSegment 模型继续保持通用模型:
Style Z-order
↓
R / C / R / C / R / ...
其中:
- R = RETAINED
- C = CONTINUOUS
本任务只用 R-C-R 做最小验证,不把 R-C-R 固化成最终架构。
必须保持:
- Style layer 顺序是唯一 Z-order 真相;
- Renderer 不理解 AIS / Route / Weather / GeoJSON 等业务名;
- Source → Segment 依赖由当前实际 Style / RenderLayer / Source 关系推导;
- 一个 Source 可依赖多个 Segment;
- source-less layer 不制造假 dependency;
- unknown/global dependency 必须 fail closed;
- 相邻 Segment、多个 C、多个 R 的通用模型不能被破坏;
- 本任务不实现 multi-continuous Fast Path。
3. 本任务必须实现的核心目标
3.1 Source/Tile mutation 局部化
假设:
R0 depends [staticSource]
C depends [particleField]
R1 depends [dynamicGeoJson]
当:
dynamicGeoJson mutation
正式 renderer 必须达到:
R0:
content/revision 不因该 source mutation 改变
cache 不 invalid
cache 不 rebuild
持续 reuse
R1:
revision/invalidation 正常变化
按需要 build/reuse/fallback
C:
正常 continuous render
禁止继续出现:
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 必须明确拆成两种用途:
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() 定义。
不得修改:
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。
必须区分:
- 已知 Source dependency;
- symbol/global prerequisite(glyph/image/placement 等);
- 真正 unknown dependency。
STEP-099 的 unknownDependencies 目前会把 symbol 相关条件标为 unknown。STEP-100 不得机械地把“存在 symbol”解释成永久 fallback;symbol 所需 glyph/image/placement 应作为明确的 global prerequisite/fail-closed 条件处理。
因此 T3 中一个:
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 路径存在类似:
useRetainedRanges = all retained segments supported
的 all-or-none 语义。
STEP-100 必须改为每个 RETAINED Segment 独立拥有:
- readiness/support decision;
- Build / Reuse / Fallback;
- content/source revision;
- invalidation reason。
如果:
R0 = Reuse
R1 = Fallback
则本帧必须允许:
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 后,必须能做到:
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 顺序。
推荐的逻辑行为:
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。
要求:
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-loadedfallback; - 能安全判断依赖的 retained Segment应继续 build/reuse。
T4 R-C-R(主验收)
必须保持清晰依赖:
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++ 测试源码,至少覆盖:
- source A mutation 只改变依赖 A 的 Segment revision;
- source A 被多个 Segment 共享时,对应多个 Segment 都变化;
- unrelated Segment revision 不变;
- source-less Segment 不制造 dependency;
- unknown/global dependency fail closed;
- unrelated source unloaded 不使安全 Segment readiness false;
- dependent source unloaded 使对应 Segment readiness false;
- R0 Reuse + R1 Fallback 的 mixed decision;
- 单 Segment build failure 不 composite stale cache;
- query diagnostics 不改变 revision/cache state;
- global non-source invalidation 仍影响所有 retained;
- source 删除后同 ID recreate 不误复用旧 Segment cache identity;
- dependent source fading 只影响对应 Segment;unrelated source fading 不影响安全 Segment;
- Cache OFF / invalid plan 行为不回归;
- 至少有一组能证明实际 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 本轮负责:
- 读取实际源码和任务书;
- 实现代码;
- 编写 Native 非 Jest 测试源码;
- 更新 Android Test App 验证入口;
- 执行轻量静态检查,如
git diff --check; - 生成中文完成报告。
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 负责:
- source/diff 审计;
- Native Mac 编译;
- Native focused + broad related C++ tests;
- Android x86_64 OpenGL validation APK 单次构建;
- Mac ADB 直连 FZG1-2;
- 自动跑 T1/T2/T3/T4;
- 解析 R0/R1 build/reuse/invalidation/revision/readiness;
- 确认硬验收标准 A~E;
- 若失败,回到 STEP-100 修复,不发布;
- 若全部 PASS,完成 STEP-100 Git push;
- 再按现有统一发行流程升级到:
maplibre-particle/rn/13.4.1-particle.18 - 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 发布。