merge(upgrade): port particle extensions onto official native 13.6.1 (unverified)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@
|
|||||||
/platform/android/build
|
/platform/android/build
|
||||||
/platform/android/MapLibreAndroid/src/main/assets/sdk_versions/com.mapbox.mapboxsdk
|
/platform/android/MapLibreAndroid/src/main/assets/sdk_versions/com.mapbox.mapboxsdk
|
||||||
/platform/android/MapLibreAndroid/android-binaries-*.zip
|
/platform/android/MapLibreAndroid/android-binaries-*.zip
|
||||||
|
/artifacts/maplibre-particle/
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
|
||||||
local.properties
|
local.properties
|
||||||
|
|||||||
154
AGENTS.md
Normal file
154
AGENTS.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# MapLibreParticleLab — Codex / ChatGPT RDC 总规则
|
||||||
|
|
||||||
|
本文件是 Codex 与 ChatGPT Remote Desktop Commander(RDC)进入本仓库后的第一入口。
|
||||||
|
|
||||||
|
## 1. 开始工作前必须读取
|
||||||
|
|
||||||
|
任何修改、构建、Git、发布、同步或设备操作前,必须先读取:
|
||||||
|
|
||||||
|
1. 根目录 `AGENTS.md`(本文件);
|
||||||
|
2. 根目录 `PARTICLE_BASELINE.md`;
|
||||||
|
3. `docs/00_项目规范/`(包括官方基线来源);
|
||||||
|
4. `docs/00_项目总则/README.md`;
|
||||||
|
5. `docs/00_项目总则/` 中 README 标记的全部当前有效总则;
|
||||||
|
6. `docs/00_项目总则/当前基线与开发路线_v2.md`(或 README 指定的后续当前版本);
|
||||||
|
7. 当前 Step / 当前任务,以及与本任务直接相关的当前设计、测试或发布文档。
|
||||||
|
|
||||||
|
不得只依赖聊天记忆、旧窗口摘要或历史 Step 直接修改仓库。
|
||||||
|
|
||||||
|
## 2. RDC 启动规则
|
||||||
|
|
||||||
|
ChatGPT 使用 RDC 对本仓库进行实际操作时,必须把上面的阅读步骤作为固定启动流程。RDC 本身不会自动读取 `AGENTS.md`,因此由 ChatGPT 在第一次写文件、执行构建、Git、发布或设备命令前主动完成。
|
||||||
|
|
||||||
|
开始实际修改前至少确认:
|
||||||
|
|
||||||
|
- 当前机器/设备;
|
||||||
|
- 仓库绝对路径;
|
||||||
|
- 当前 branch;
|
||||||
|
- 当前 HEAD;
|
||||||
|
- `git status`;
|
||||||
|
- 与任务有关的当前版本号/发布坐标。
|
||||||
|
|
||||||
|
## 3. 冲突与事实优先级
|
||||||
|
|
||||||
|
若文档与代码/构建事实冲突,不得自行猜测或用旧规则覆盖当前实现。按以下顺序处理:
|
||||||
|
|
||||||
|
1. Git 当前代码与实际构建/运行结果 = 实现事实;
|
||||||
|
2. 当前基线与项目总则 = 设计与流程意图;
|
||||||
|
3. 当前 Step/任务 = 本轮工作合同;
|
||||||
|
4. 历史 Step/旧验收文档 = 历史证据,不自动代表当前流程。
|
||||||
|
|
||||||
|
发现冲突时先报告并限定修改范围;除非任务明确要求,不重写历史文档来伪装一致。
|
||||||
|
|
||||||
|
## 4. 项目固定边界
|
||||||
|
|
||||||
|
项目目标:在 MapLibre Native Core 中 clean-room 独立实现 Raster/Vector Field Particle,支持 Android OpenGL/Vulkan 与后续 iOS Metal。
|
||||||
|
|
||||||
|
固定边界:正式实现不使用 React Native/WebView/外部 Overlay;粒子模块高度隔离;官方原文件只做薄接入;Android/iOS 共用 C++ Core;WeatherColorLayer 与 RasterParticleLayer 分离;Wind=GLOBAL;Wave/Tide/Current=OCEAN_ONLY 且共用 Land/Ocean Mask;不得复制 Mapbox 新源码或 Windy APK 内代码。
|
||||||
|
|
||||||
|
Host/NavSea 负责 provider、HTTP、manifest、下载、缓存和业务调度;MapLibre Native 从宿主提交的 canonical/encoded field 开始负责校验、解码、Runtime Field Window 与 GPU 渲染。
|
||||||
|
|
||||||
|
## 5. Codex 与 RDC 的职责分工
|
||||||
|
|
||||||
|
### Codex 主要负责
|
||||||
|
|
||||||
|
- 大规模源码修改与跨文件重构;
|
||||||
|
- 新功能实现、接口调整、批量工程化修改;
|
||||||
|
- 按 Codex 任务书完成明确范围内的代码与文档变更;
|
||||||
|
- 需要较长连续编辑、依赖全仓上下文的实现工作。
|
||||||
|
|
||||||
|
Codex 的核心角色是“实现者”,不作为其自身修改结果的唯一最终验收者。
|
||||||
|
|
||||||
|
### RDC 主要负责
|
||||||
|
|
||||||
|
- 编译、链接、codegen、运行测试程序与测试命令;
|
||||||
|
- AAR/APK/npm tgz 等 artifact 的构建、检查、发布与下载;
|
||||||
|
- 公网发布后的回读、SHA-256/版本/文件内容验证;
|
||||||
|
- Win11、Linux、模拟器、ADB、Metro、安装、启动和运行态检查;
|
||||||
|
- 日志、进程、端口、文件系统、Git 状态与构建产物审计;
|
||||||
|
- 对 Codex 完成结果做独立代码/配置/文档/构建审计;
|
||||||
|
- 小范围、事实明确、风险可控的修正。
|
||||||
|
|
||||||
|
## 6. Codex → RDC 固定切换流程
|
||||||
|
|
||||||
|
默认工作流:
|
||||||
|
|
||||||
|
1. ChatGPT/用户先明确设计边界与任务目标;
|
||||||
|
2. 需要大规模实现时交给 Codex;
|
||||||
|
3. Codex 完成任务书范围后停止,不继续承担最终环境验收;
|
||||||
|
4. 切换到 RDC,重新读取 `AGENTS.md`、当前基线、Codex 交付文件和 Git diff;
|
||||||
|
5. RDC 做独立审计,并负责实际编译、测试、artifact、发布、下载、安装、Metro/ADB/模拟器和日志验证;
|
||||||
|
6. 若审计发现小型明确问题,可由 RDC 直接修正并重新验证;
|
||||||
|
7. 若发现架构性问题、跨模块大改或修改范围明显扩大,停止 RDC 扩改,重新生成/调整 Codex 任务再进入实现;
|
||||||
|
8. 最终以 RDC 实际环境验证结果 + Git 当前代码作为验收事实。
|
||||||
|
|
||||||
|
禁止默认让 Codex 同时承担“大规模实现 + 唯一审计 + 唯一最终环境验证”三种角色。
|
||||||
|
|
||||||
|
## 7. 记录与收尾
|
||||||
|
|
||||||
|
每一阶段必须有 `docs/50_Step记录/STEP-xxx_*.md`。完成报告固定写入 `docs/报告/STEP-xxx_<主题>_Codex完成报告_vN.md`(仅在任务要求 Codex 完成报告时)。
|
||||||
|
|
||||||
|
修改结束必须按当前构建规范验证、更新 Step、追加 `粒子开发记录.md`,并明确所有未完成/阻塞。Git 提交必须 scoped,禁止把既有无关 dirty/untracked 一并提交。
|
||||||
|
## 8. Codex 写代码,RDC 执行编译 / 测试
|
||||||
|
|
||||||
|
本项目固定采用以下闭环,除非用户在当前任务中明确另行授权:
|
||||||
|
|
||||||
|
1. **Codex 负责实现与修复。**
|
||||||
|
- 读取当前任务、设计、源码与错误信息;
|
||||||
|
- 修改代码、测试代码、构建脚本和任务要求的文档;
|
||||||
|
- 分析 RDC 返回的编译、链接、测试、codegen、打包失败;
|
||||||
|
- 根据真实错误继续修复代码。
|
||||||
|
|
||||||
|
2. **RDC / 普通 Terminal 负责实际编译与验证。**
|
||||||
|
- CMake / Ninja / Bazel 编译;
|
||||||
|
- C++ / OpenGL / Metal 定向测试;
|
||||||
|
- codegen、链接、打包、artifact 检查;
|
||||||
|
- 其他耗时构建或机械性验证。
|
||||||
|
Codex 不应自行启动、等待或轮询这些长时间命令。
|
||||||
|
|
||||||
|
3. **固定闭环:**
|
||||||
|
`Codex 写代码 → Codex 停止并给出精确验证命令 → RDC 执行 → RDC 返回结果 → 若失败,Codex 分析并修复 → RDC 再验证`。
|
||||||
|
|
||||||
|
4. **失败日志只回传必要信息。**
|
||||||
|
- RDC 保留完整 stdout/stderr;
|
||||||
|
- 成功时只返回 exit code、耗时和必要摘要;
|
||||||
|
- 失败时优先返回首个真实 error、相关 target、前后必要上下文和日志路径;
|
||||||
|
- 不把完整大型 build log 反复送入 Codex。
|
||||||
|
|
||||||
|
5. **Codex 禁止行为。**
|
||||||
|
- 未经用户明确授权,不得自行执行长时间编译、链接、codegen、打包或大型测试;
|
||||||
|
- 不得创建 background terminal 等待 build;
|
||||||
|
- 不得高频轮询构建状态;
|
||||||
|
- 不得为了等待编译而自行切换模型或启动第二个 Codex 实例;
|
||||||
|
- 不得把“编译未完成”当作理由持续占用高能力模型。
|
||||||
|
|
||||||
|
6. **RDC 发现问题后的处理。**
|
||||||
|
- RDC 可以直接处理范围明确、风险低、原因清楚的小问题,包括:
|
||||||
|
- 构建命令、CMake/Bazel 配置、链接配置、平台条件、测试条件等小型工程问题;
|
||||||
|
- 单文件或少量文件内、无需重新设计架构的小型源码修正;
|
||||||
|
- 明确的编译错误、声明/定义不一致、include/宏条件、简单 ABI/平台兼容问题;
|
||||||
|
- 不改变既定业务语义和总体架构的局部修复。
|
||||||
|
- RDC 修完后应立即重新编译/测试验证,不必为了这类小问题启动 Codex。
|
||||||
|
- 以下情况才交给 Codex:
|
||||||
|
- 大规模逻辑修改;
|
||||||
|
- 跨模块或跨层重构;
|
||||||
|
- renderer / runtime / API 设计需要重新判断;
|
||||||
|
- 需要新增较完整功能或大量代码;
|
||||||
|
- 用户明确指定“这是 Codex 代码任务”。
|
||||||
|
- 一旦 RDC 判断修复范围开始扩大、需要重新设计或无法通过少量明确修改解决,应停止继续扩改,把错误证据和现状交给 Codex。
|
||||||
|
|
||||||
|
7. **用户可见性。**
|
||||||
|
- 本项目未经用户明确授权,不得使用后台/headless Codex 执行任务;
|
||||||
|
- Codex 默认运行在用户可见的 Terminal/TUI 中;
|
||||||
|
- 当前 Mac 上如需启动或恢复 Codex,必须优先使用现有 GNU Screen 会话 `7445.codex`;
|
||||||
|
- 不得另起隐藏的 Codex 进程或新的不可见会话,除非用户明确授权;
|
||||||
|
- 用户应能够通过该 screen 会话直接看到 Codex 的输入、输出和当前状态。
|
||||||
|
|
||||||
|
8. **任务书冲突处理。**
|
||||||
|
- 若旧任务书写有“Codex 必须执行编译/测试”,但用户未在当前任务明确重新授权,则以本节为准:
|
||||||
|
Codex 只准备代码与验证命令,RDC 负责实际执行。
|
||||||
|
- 只有用户在当前任务中明确要求“由 Codex 执行某项编译/测试”时,Codex 才可执行该项。
|
||||||
|
|
||||||
|
核心原则:
|
||||||
|
|
||||||
|
> Codex 负责写和改;RDC 负责跑和验;RDC 报错,再交回 Codex 修。
|
||||||
@@ -180,6 +180,12 @@ list(APPEND SRC_FILES
|
|||||||
${PROJECT_SOURCE_DIR}/src/mln/gfx/uniform_buffer.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/gfx/uniform_buffer.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/gfx/vertex_attribute.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/gfx/vertex_attribute.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/render_target.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/render_target.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/particle_fast_path.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/retained_underlay_cache.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/retained_underlay_cache.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/retained_underlay_runtime_state.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/render_segment_plan.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/render_segment_json.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/change_request.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/change_request.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/layer_group.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layer_group.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/tile_layer_group.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/tile_layer_group.cpp
|
||||||
@@ -210,10 +216,32 @@ list(APPEND SRC_FILES
|
|||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/symbol_layer_tweaker.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/symbol_layer_tweaker.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/collision_layer_tweaker.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/collision_layer_tweaker.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/collision_layer_tweaker.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/collision_layer_tweaker.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/field/offline_fixture_particle_adapter.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/field/offline_fixture_particle_adapter.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/field/field_data_adapter.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/field/field_runtime.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/field/field_binding.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/field/field_projection_materialization.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/field/field_profile_validator.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/field/field_profile_contract_json.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/ingress/qbin_v2_decoder.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/ingress/qbin_v3_decoder.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/particle/field/local_particle_field_feed.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/shaders/shader_program_base.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/shaders/shader_program_base.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND INCLUDE_FILES
|
list(APPEND INCLUDE_FILES
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/field/field_data_adapter.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/field/field_profile_contract.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/field/field_profile_validator.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/field/field_profile_contract_json.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/field/field_runtime.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/field/field_binding.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/renderer_debug_counters.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/ingress/qbin_v2_decoder.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/ingress/qbin_v3_decoder.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/ingress/encoded_field_transport.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/particle/field/local_particle_field_feed.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/actor/actor_ref.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/actor/actor_ref.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/actor/actor.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/actor/actor.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/actor/aspiring_actor.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/actor/aspiring_actor.hpp
|
||||||
@@ -246,6 +274,9 @@ list(APPEND INCLUDE_FILES
|
|||||||
${PROJECT_SOURCE_DIR}/include/mln/gfx/types.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/gfx/types.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/gfx/vertex_buffer.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/gfx/vertex_buffer.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/layermanager/background_layer_factory.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/layermanager/background_layer_factory.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/layermanager/particle_validation_layer_factory.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/layermanager/raster_particle_layer_factory.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/layermanager/weather_color_layer_factory.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/layermanager/circle_layer_factory.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/layermanager/circle_layer_factory.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/layermanager/custom_layer_factory.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/layermanager/custom_layer_factory.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/layermanager/fill_extrusion_layer_factory.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/layermanager/fill_extrusion_layer_factory.hpp
|
||||||
@@ -280,6 +311,8 @@ list(APPEND INCLUDE_FILES
|
|||||||
${PROJECT_SOURCE_DIR}/include/mln/renderer/renderer_observer.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/renderer/renderer_observer.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/renderer/renderer_state.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/renderer/renderer_state.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/renderer/renderer.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/renderer/renderer.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/renderer/retained_underlay.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/renderer/render_segment.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/program_parameters.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/program_parameters.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/shader_source.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/shader_source.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/storage/database_file_source.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/storage/database_file_source.hpp
|
||||||
@@ -357,6 +390,10 @@ list(APPEND INCLUDE_FILES
|
|||||||
${PROJECT_SOURCE_DIR}/include/mln/style/layer_properties.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/style/layer_properties.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/style/layer.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/style/layer.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/style/layers/background_layer.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/style/layers/background_layer.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/style/layers/particle_validation_layer.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/style/layers/raster_particle_layer.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/style/layers/weather_color_layer.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/style/sources/raster_array_source.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/style/layers/circle_layer.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/style/layers/circle_layer.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/style/layers/custom_layer_host.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/style/layers/custom_layer_host.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/style/layers/custom_layer_init_parameters.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/style/layers/custom_layer_init_parameters.hpp
|
||||||
@@ -503,6 +540,9 @@ list(APPEND SRC_FILES
|
|||||||
${PROJECT_SOURCE_DIR}/src/mln/gfx/upload_pass.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/gfx/upload_pass.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/gfx/vertex_vector.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/gfx/vertex_vector.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/layermanager/background_layer_factory.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/layermanager/background_layer_factory.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/layermanager/particle_validation_layer_factory.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/layermanager/raster_particle_layer_factory.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/layermanager/weather_color_layer_factory.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/layermanager/circle_layer_factory.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/layermanager/circle_layer_factory.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/layermanager/fill_extrusion_layer_factory.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/layermanager/fill_extrusion_layer_factory.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/layermanager/fill_layer_factory.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/layermanager/fill_layer_factory.cpp
|
||||||
@@ -580,6 +620,11 @@ list(APPEND SRC_FILES
|
|||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/image_manager_observer.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/image_manager_observer.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_background_layer.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_background_layer.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_background_layer.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_background_layer.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_particle_validation_layer.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_particle_validation_layer.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_raster_particle_layer.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_weather_color_layer.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_weather_color_layer.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_circle_layer.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_circle_layer.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_circle_layer.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_circle_layer.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_fill_extrusion_layer.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/renderer/layers/render_fill_extrusion_layer.cpp
|
||||||
@@ -755,6 +800,13 @@ list(APPEND SRC_FILES
|
|||||||
${PROJECT_SOURCE_DIR}/src/mln/style/layer_impl.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/layer_impl.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/style/layer_observer.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/layer_observer.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/style/layers/background_layer.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/background_layer.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/particle_validation_layer.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/particle_validation_layer_impl.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/particle_render_layer_properties.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/raster_particle_layer.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/raster_particle_layer_impl.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/weather_color_layer.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/weather_color_layer_impl.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/style/layers/background_layer_impl.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/background_layer_impl.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/style/layers/background_layer_impl.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/background_layer_impl.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/style/layers/background_layer_properties.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/layers/background_layer_properties.cpp
|
||||||
@@ -835,6 +887,8 @@ list(APPEND SRC_FILES
|
|||||||
${PROJECT_SOURCE_DIR}/src/mln/style/sources/raster_dem_source.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/sources/raster_dem_source.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/style/sources/tile_source.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/sources/tile_source.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/style/sources/raster_source.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/sources/raster_source.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/style/sources/raster_array_source.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/style/sources/raster_array_source_impl.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/style/sources/tile_source_impl.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/sources/tile_source_impl.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/style/sources/tile_source_impl.hpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/sources/tile_source_impl.hpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/style/sources/vector_source.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/style/sources/vector_source.cpp
|
||||||
|
|||||||
17
PARTICLE_BASELINE.md
Normal file
17
PARTICLE_BASELINE.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# MapLibreParticleLab 基线记录
|
||||||
|
|
||||||
|
- 初始化日期:2026-08-27
|
||||||
|
- upstream:https://github.com/maplibre/maplibre-native.git
|
||||||
|
- origin:https://git.eyaku.jp/tei/MapLibreParticleLab
|
||||||
|
- Android:android-v13.4.1
|
||||||
|
- iOS:ios-v6.28.0
|
||||||
|
- 共同 core:18545ca10d296704b2cba675b182ff2c8da24588
|
||||||
|
- 分支:particle/main
|
||||||
|
- Ubuntu:Ubuntu 24.04.4 LTS
|
||||||
|
- 架构:x86_64
|
||||||
|
- Java:17
|
||||||
|
- Android API:34
|
||||||
|
- NDK:28.2.13676358
|
||||||
|
- 默认并行度:3
|
||||||
|
- upstream push:DISABLED
|
||||||
|
- git rerere:true
|
||||||
@@ -99,6 +99,8 @@ MLN_GENERATED_OPENGL_SHADER_HEADERS = [
|
|||||||
"include/mln/shaders/gl/fill_extrusion_pattern_instanced.hpp",
|
"include/mln/shaders/gl/fill_extrusion_pattern_instanced.hpp",
|
||||||
"include/mln/shaders/gl/heatmap.hpp",
|
"include/mln/shaders/gl/heatmap.hpp",
|
||||||
"include/mln/shaders/gl/heatmap_texture.hpp",
|
"include/mln/shaders/gl/heatmap_texture.hpp",
|
||||||
|
"include/mln/shaders/gl/particle_gpu_validation.hpp",
|
||||||
|
"include/mln/shaders/gl/retained_underlay_composite.hpp",
|
||||||
"include/mln/shaders/gl/hillshade_prepare.hpp",
|
"include/mln/shaders/gl/hillshade_prepare.hpp",
|
||||||
"include/mln/shaders/gl/hillshade.hpp",
|
"include/mln/shaders/gl/hillshade.hpp",
|
||||||
"include/mln/shaders/gl/color_relief.hpp",
|
"include/mln/shaders/gl/color_relief.hpp",
|
||||||
@@ -162,6 +164,9 @@ MLN_CORE_SOURCE = [
|
|||||||
"src/mln/gfx/upload_pass.hpp",
|
"src/mln/gfx/upload_pass.hpp",
|
||||||
"src/mln/gfx/vertex_vector.hpp",
|
"src/mln/gfx/vertex_vector.hpp",
|
||||||
"src/mln/layermanager/background_layer_factory.cpp",
|
"src/mln/layermanager/background_layer_factory.cpp",
|
||||||
|
"src/mln/layermanager/particle_validation_layer_factory.cpp",
|
||||||
|
"src/mln/layermanager/raster_particle_layer_factory.cpp",
|
||||||
|
"src/mln/layermanager/weather_color_layer_factory.cpp",
|
||||||
"src/mln/layermanager/circle_layer_factory.cpp",
|
"src/mln/layermanager/circle_layer_factory.cpp",
|
||||||
"src/mln/layermanager/custom_layer_factory.cpp",
|
"src/mln/layermanager/custom_layer_factory.cpp",
|
||||||
"src/mln/layermanager/fill_extrusion_layer_factory.cpp",
|
"src/mln/layermanager/fill_extrusion_layer_factory.cpp",
|
||||||
@@ -201,6 +206,17 @@ MLN_CORE_SOURCE = [
|
|||||||
"src/mln/map/zoom_history.hpp",
|
"src/mln/map/zoom_history.hpp",
|
||||||
"src/mln/math/log2.cpp",
|
"src/mln/math/log2.cpp",
|
||||||
"src/mln/platform/settings.cpp",
|
"src/mln/platform/settings.cpp",
|
||||||
|
"src/mln/particle/field/offline_fixture_particle_adapter.cpp",
|
||||||
|
"src/mln/particle/field/offline_fixture_particle_adapter.hpp",
|
||||||
|
"src/mln/particle/field/field_data_adapter.cpp",
|
||||||
|
"src/mln/particle/field/field_runtime.cpp",
|
||||||
|
"src/mln/particle/field/field_binding.cpp",
|
||||||
|
"src/mln/particle/field/field_projection_materialization.cpp",
|
||||||
|
"src/mln/particle/field/field_profile_validator.cpp",
|
||||||
|
"src/mln/particle/field/field_profile_contract_json.cpp",
|
||||||
|
"src/mln/particle/ingress/qbin_v2_decoder.cpp",
|
||||||
|
"src/mln/particle/ingress/qbin_v3_decoder.cpp",
|
||||||
|
"src/mln/particle/field/local_particle_field_feed.cpp",
|
||||||
"src/mln/renderer/backend_scope.cpp",
|
"src/mln/renderer/backend_scope.cpp",
|
||||||
"src/mln/renderer/bucket.hpp",
|
"src/mln/renderer/bucket.hpp",
|
||||||
"src/mln/renderer/bucket_parameters.cpp",
|
"src/mln/renderer/bucket_parameters.cpp",
|
||||||
@@ -233,6 +249,11 @@ MLN_CORE_SOURCE = [
|
|||||||
"src/mln/renderer/image_manager_observer.hpp",
|
"src/mln/renderer/image_manager_observer.hpp",
|
||||||
"src/mln/renderer/layers/render_background_layer.cpp",
|
"src/mln/renderer/layers/render_background_layer.cpp",
|
||||||
"src/mln/renderer/layers/render_background_layer.hpp",
|
"src/mln/renderer/layers/render_background_layer.hpp",
|
||||||
|
"src/mln/renderer/layers/render_particle_validation_layer.cpp",
|
||||||
|
"src/mln/renderer/layers/render_particle_validation_layer.hpp",
|
||||||
|
"src/mln/renderer/layers/render_raster_particle_layer.hpp",
|
||||||
|
"src/mln/renderer/layers/render_weather_color_layer.cpp",
|
||||||
|
"src/mln/renderer/layers/render_weather_color_layer.hpp",
|
||||||
"src/mln/renderer/layers/render_circle_layer.cpp",
|
"src/mln/renderer/layers/render_circle_layer.cpp",
|
||||||
"src/mln/renderer/layers/render_circle_layer.hpp",
|
"src/mln/renderer/layers/render_circle_layer.hpp",
|
||||||
"src/mln/renderer/layers/render_custom_layer.cpp",
|
"src/mln/renderer/layers/render_custom_layer.cpp",
|
||||||
@@ -259,6 +280,8 @@ MLN_CORE_SOURCE = [
|
|||||||
"src/mln/renderer/paint_parameters.hpp",
|
"src/mln/renderer/paint_parameters.hpp",
|
||||||
"src/mln/renderer/paint_property_binder.hpp",
|
"src/mln/renderer/paint_property_binder.hpp",
|
||||||
"src/mln/renderer/paint_property_statistics.hpp",
|
"src/mln/renderer/paint_property_statistics.hpp",
|
||||||
|
"src/mln/renderer/particle_viewport_reseed.hpp",
|
||||||
|
"src/mln/renderer/particle_fast_path.hpp",
|
||||||
"src/mln/renderer/pattern_atlas.cpp",
|
"src/mln/renderer/pattern_atlas.cpp",
|
||||||
"src/mln/renderer/pattern_atlas.hpp",
|
"src/mln/renderer/pattern_atlas.hpp",
|
||||||
"src/mln/renderer/possibly_evaluated_property_value.hpp",
|
"src/mln/renderer/possibly_evaluated_property_value.hpp",
|
||||||
@@ -270,6 +293,9 @@ MLN_CORE_SOURCE = [
|
|||||||
"src/mln/renderer/render_light.hpp",
|
"src/mln/renderer/render_light.hpp",
|
||||||
"src/mln/renderer/render_orchestrator.cpp",
|
"src/mln/renderer/render_orchestrator.cpp",
|
||||||
"src/mln/renderer/render_orchestrator.hpp",
|
"src/mln/renderer/render_orchestrator.hpp",
|
||||||
|
"src/mln/renderer/continuous_layer_range.hpp",
|
||||||
|
"src/mln/renderer/render_segment_plan.hpp",
|
||||||
|
"src/mln/renderer/render_segment_json.hpp",
|
||||||
"src/mln/renderer/render_pass.hpp",
|
"src/mln/renderer/render_pass.hpp",
|
||||||
"src/mln/renderer/render_source.cpp",
|
"src/mln/renderer/render_source.cpp",
|
||||||
"src/mln/renderer/render_source.hpp",
|
"src/mln/renderer/render_source.hpp",
|
||||||
@@ -283,6 +309,9 @@ MLN_CORE_SOURCE = [
|
|||||||
"src/mln/renderer/renderer_impl.cpp",
|
"src/mln/renderer/renderer_impl.cpp",
|
||||||
"src/mln/renderer/renderer_impl.hpp",
|
"src/mln/renderer/renderer_impl.hpp",
|
||||||
"src/mln/renderer/renderer_state.cpp",
|
"src/mln/renderer/renderer_state.cpp",
|
||||||
|
"src/mln/renderer/retained_underlay_cache.cpp",
|
||||||
|
"src/mln/renderer/retained_underlay_cache.hpp",
|
||||||
|
"src/mln/renderer/retained_underlay_runtime_state.hpp",
|
||||||
"src/mln/renderer/sources/render_custom_geometry_source.cpp",
|
"src/mln/renderer/sources/render_custom_geometry_source.cpp",
|
||||||
"src/mln/renderer/sources/render_custom_geometry_source.hpp",
|
"src/mln/renderer/sources/render_custom_geometry_source.hpp",
|
||||||
"src/mln/renderer/sources/render_custom_vector_source.cpp",
|
"src/mln/renderer/sources/render_custom_vector_source.cpp",
|
||||||
@@ -295,6 +324,7 @@ MLN_CORE_SOURCE = [
|
|||||||
"src/mln/renderer/sources/render_raster_dem_source.hpp",
|
"src/mln/renderer/sources/render_raster_dem_source.hpp",
|
||||||
"src/mln/renderer/sources/render_raster_source.cpp",
|
"src/mln/renderer/sources/render_raster_source.cpp",
|
||||||
"src/mln/renderer/sources/render_raster_source.hpp",
|
"src/mln/renderer/sources/render_raster_source.hpp",
|
||||||
|
"src/mln/renderer/sources/render_raster_array_source.hpp",
|
||||||
"src/mln/renderer/sources/render_tile_source.cpp",
|
"src/mln/renderer/sources/render_tile_source.cpp",
|
||||||
"src/mln/renderer/sources/render_tile_source.hpp",
|
"src/mln/renderer/sources/render_tile_source.hpp",
|
||||||
"src/mln/renderer/sources/render_vector_source.cpp",
|
"src/mln/renderer/sources/render_vector_source.cpp",
|
||||||
@@ -412,6 +442,13 @@ MLN_CORE_SOURCE = [
|
|||||||
"src/mln/style/layer_observer.hpp",
|
"src/mln/style/layer_observer.hpp",
|
||||||
"src/mln/style/layers/background_layer_impl.cpp",
|
"src/mln/style/layers/background_layer_impl.cpp",
|
||||||
"src/mln/style/layers/background_layer_impl.hpp",
|
"src/mln/style/layers/background_layer_impl.hpp",
|
||||||
|
"src/mln/style/layers/particle_validation_layer.cpp",
|
||||||
|
"src/mln/style/layers/particle_validation_layer_impl.hpp",
|
||||||
|
"src/mln/style/layers/particle_render_layer_properties.hpp",
|
||||||
|
"src/mln/style/layers/raster_particle_layer.cpp",
|
||||||
|
"src/mln/style/layers/raster_particle_layer_impl.hpp",
|
||||||
|
"src/mln/style/layers/weather_color_layer.cpp",
|
||||||
|
"src/mln/style/layers/weather_color_layer_impl.hpp",
|
||||||
"src/mln/style/layers/circle_layer_impl.cpp",
|
"src/mln/style/layers/circle_layer_impl.cpp",
|
||||||
"src/mln/style/layers/circle_layer_impl.hpp",
|
"src/mln/style/layers/circle_layer_impl.hpp",
|
||||||
"src/mln/style/layers/custom_layer.cpp",
|
"src/mln/style/layers/custom_layer.cpp",
|
||||||
@@ -466,6 +503,8 @@ MLN_CORE_SOURCE = [
|
|||||||
"src/mln/style/sources/raster_dem_source.cpp",
|
"src/mln/style/sources/raster_dem_source.cpp",
|
||||||
"src/mln/style/sources/tile_source.cpp",
|
"src/mln/style/sources/tile_source.cpp",
|
||||||
"src/mln/style/sources/raster_source.cpp",
|
"src/mln/style/sources/raster_source.cpp",
|
||||||
|
"src/mln/style/sources/raster_array_source.cpp",
|
||||||
|
"src/mln/style/sources/raster_array_source_impl.hpp",
|
||||||
"src/mln/style/sources/tile_source_impl.cpp",
|
"src/mln/style/sources/tile_source_impl.cpp",
|
||||||
"src/mln/style/sources/tile_source_impl.hpp",
|
"src/mln/style/sources/tile_source_impl.hpp",
|
||||||
"src/mln/style/sources/vector_source.cpp",
|
"src/mln/style/sources/vector_source.cpp",
|
||||||
@@ -636,6 +675,20 @@ MLN_CORE_SOURCE = [
|
|||||||
})
|
})
|
||||||
|
|
||||||
MLN_CORE_HEADERS = [
|
MLN_CORE_HEADERS = [
|
||||||
|
"include/mln/particle/field/field_data_adapter.hpp",
|
||||||
|
"include/mln/particle/field/field_profile_contract.hpp",
|
||||||
|
"include/mln/particle/field/field_profile_validator.hpp",
|
||||||
|
"include/mln/particle/field/field_profile_contract_json.hpp",
|
||||||
|
"include/mln/particle/field/field_projection_materialization.hpp",
|
||||||
|
"include/mln/particle/field/field_runtime.hpp",
|
||||||
|
"include/mln/particle/field/particle_gpu_math.hpp",
|
||||||
|
"include/mln/particle/field/field_binding.hpp",
|
||||||
|
"include/mln/particle/renderer_debug_counters.hpp",
|
||||||
|
"include/mln/renderer/layers/weather_color_texture_lifecycle.hpp",
|
||||||
|
"include/mln/particle/ingress/qbin_v2_decoder.hpp",
|
||||||
|
"include/mln/particle/ingress/qbin_v3_decoder.hpp",
|
||||||
|
"include/mln/particle/ingress/encoded_field_transport.hpp",
|
||||||
|
"include/mln/particle/field/local_particle_field_feed.hpp",
|
||||||
"include/mln/gfx/command_encoder.hpp",
|
"include/mln/gfx/command_encoder.hpp",
|
||||||
"include/mln/gfx/debug_group.hpp",
|
"include/mln/gfx/debug_group.hpp",
|
||||||
"include/mln/gfx/context.hpp",
|
"include/mln/gfx/context.hpp",
|
||||||
@@ -673,6 +726,9 @@ MLN_CORE_HEADERS = [
|
|||||||
"include/mln/i18n/collator.hpp",
|
"include/mln/i18n/collator.hpp",
|
||||||
"include/mln/i18n/number_format.hpp",
|
"include/mln/i18n/number_format.hpp",
|
||||||
"include/mln/layermanager/background_layer_factory.hpp",
|
"include/mln/layermanager/background_layer_factory.hpp",
|
||||||
|
"include/mln/layermanager/particle_validation_layer_factory.hpp",
|
||||||
|
"include/mln/layermanager/raster_particle_layer_factory.hpp",
|
||||||
|
"include/mln/layermanager/weather_color_layer_factory.hpp",
|
||||||
"include/mln/layermanager/circle_layer_factory.hpp",
|
"include/mln/layermanager/circle_layer_factory.hpp",
|
||||||
"include/mln/layermanager/custom_layer_factory.hpp",
|
"include/mln/layermanager/custom_layer_factory.hpp",
|
||||||
"include/mln/layermanager/fill_extrusion_layer_factory.hpp",
|
"include/mln/layermanager/fill_extrusion_layer_factory.hpp",
|
||||||
@@ -686,6 +742,11 @@ MLN_CORE_HEADERS = [
|
|||||||
"include/mln/layermanager/location_indicator_layer_factory.hpp",
|
"include/mln/layermanager/location_indicator_layer_factory.hpp",
|
||||||
"include/mln/layermanager/raster_layer_factory.hpp",
|
"include/mln/layermanager/raster_layer_factory.hpp",
|
||||||
"include/mln/layermanager/symbol_layer_factory.hpp",
|
"include/mln/layermanager/symbol_layer_factory.hpp",
|
||||||
|
"include/mln/style/layers/particle_validation_layer.hpp",
|
||||||
|
"include/mln/style/layers/raster_particle_layer.hpp",
|
||||||
|
"include/mln/style/layers/weather_color_layer.hpp",
|
||||||
|
"include/mln/style/layers/weather_color_stop.hpp",
|
||||||
|
"include/mln/style/sources/raster_array_source.hpp",
|
||||||
"include/mln/map/bound_options.hpp",
|
"include/mln/map/bound_options.hpp",
|
||||||
"include/mln/map/camera.hpp",
|
"include/mln/map/camera.hpp",
|
||||||
"include/mln/map/change.hpp",
|
"include/mln/map/change.hpp",
|
||||||
@@ -708,6 +769,8 @@ MLN_CORE_HEADERS = [
|
|||||||
"include/mln/renderer/renderer_frontend.hpp",
|
"include/mln/renderer/renderer_frontend.hpp",
|
||||||
"include/mln/renderer/renderer_observer.hpp",
|
"include/mln/renderer/renderer_observer.hpp",
|
||||||
"include/mln/renderer/renderer_state.hpp",
|
"include/mln/renderer/renderer_state.hpp",
|
||||||
|
"include/mln/renderer/retained_underlay.hpp",
|
||||||
|
"include/mln/renderer/render_segment.hpp",
|
||||||
"include/mln/shaders/program_parameters.hpp",
|
"include/mln/shaders/program_parameters.hpp",
|
||||||
"include/mln/storage/database_file_source.hpp",
|
"include/mln/storage/database_file_source.hpp",
|
||||||
"include/mln/storage/file_source.hpp",
|
"include/mln/storage/file_source.hpp",
|
||||||
@@ -1021,6 +1084,7 @@ MLN_DRAWABLES_HEADERS = [
|
|||||||
"include/mln/shaders/fill_extrusion_layer_ubo.hpp",
|
"include/mln/shaders/fill_extrusion_layer_ubo.hpp",
|
||||||
"include/mln/shaders/heatmap_layer_ubo.hpp",
|
"include/mln/shaders/heatmap_layer_ubo.hpp",
|
||||||
"include/mln/shaders/heatmap_texture_layer_ubo.hpp",
|
"include/mln/shaders/heatmap_texture_layer_ubo.hpp",
|
||||||
|
"include/mln/shaders/particle_gpu_validation_ubo.hpp",
|
||||||
"include/mln/shaders/hillshade_layer_ubo.hpp",
|
"include/mln/shaders/hillshade_layer_ubo.hpp",
|
||||||
"include/mln/shaders/color_relief_layer_ubo.hpp",
|
"include/mln/shaders/color_relief_layer_ubo.hpp",
|
||||||
"include/mln/shaders/hillshade_prepare_layer_ubo.hpp",
|
"include/mln/shaders/hillshade_prepare_layer_ubo.hpp",
|
||||||
@@ -1102,6 +1166,8 @@ MLN_DRAWABLES_MTL_SOURCE = [
|
|||||||
"src/mln/shaders/mtl/color_relief.cpp",
|
"src/mln/shaders/mtl/color_relief.cpp",
|
||||||
"src/mln/shaders/mtl/line.cpp",
|
"src/mln/shaders/mtl/line.cpp",
|
||||||
"src/mln/shaders/mtl/location_indicator.cpp",
|
"src/mln/shaders/mtl/location_indicator.cpp",
|
||||||
|
"src/mln/shaders/mtl/particle_gpu_validation.cpp",
|
||||||
|
"src/mln/shaders/mtl/retained_underlay_composite.cpp",
|
||||||
"src/mln/shaders/mtl/raster.cpp",
|
"src/mln/shaders/mtl/raster.cpp",
|
||||||
"src/mln/shaders/mtl/symbol.cpp",
|
"src/mln/shaders/mtl/symbol.cpp",
|
||||||
"src/mln/shaders/mtl/widevector.cpp",
|
"src/mln/shaders/mtl/widevector.cpp",
|
||||||
@@ -1145,6 +1211,8 @@ MLN_DRAWABLES_MTL_HEADERS = [
|
|||||||
"include/mln/shaders/mtl/color_relief.hpp",
|
"include/mln/shaders/mtl/color_relief.hpp",
|
||||||
"include/mln/shaders/mtl/line.hpp",
|
"include/mln/shaders/mtl/line.hpp",
|
||||||
"include/mln/shaders/mtl/location_indicator.hpp",
|
"include/mln/shaders/mtl/location_indicator.hpp",
|
||||||
|
"include/mln/shaders/mtl/particle_gpu_validation.hpp",
|
||||||
|
"include/mln/shaders/mtl/retained_underlay_composite.hpp",
|
||||||
"include/mln/shaders/mtl/raster.hpp",
|
"include/mln/shaders/mtl/raster.hpp",
|
||||||
"include/mln/shaders/mtl/shader_group.hpp",
|
"include/mln/shaders/mtl/shader_group.hpp",
|
||||||
"include/mln/shaders/mtl/shader_program.hpp",
|
"include/mln/shaders/mtl/shader_program.hpp",
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ list(APPEND
|
|||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/color_relief.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/color_relief.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/line.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/line.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/location_indicator.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/location_indicator.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/particle_gpu_validation.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/retained_underlay_composite.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/raster.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/raster.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/shader_group.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/shader_group.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/shader_program.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/shader_program.hpp
|
||||||
@@ -90,6 +92,8 @@ list(APPEND
|
|||||||
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/color_relief.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/color_relief.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/line.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/line.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/location_indicator.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/location_indicator.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/particle_gpu_validation.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/retained_underlay_composite.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/raster.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/raster.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/symbol.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/symbol.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/widevector.cpp
|
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/widevector.cpp
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ list(APPEND
|
|||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill_extrusion_pattern_instanced.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill_extrusion_pattern_instanced.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/heatmap.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/heatmap.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/heatmap_texture.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/heatmap_texture.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/particle_gpu_validation.hpp
|
||||||
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/retained_underlay_composite.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/hillshade_prepare.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/hillshade_prepare.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/hillshade.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/hillshade.hpp
|
||||||
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/color_relief.hpp
|
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/color_relief.hpp
|
||||||
|
|||||||
34
docs/00_项目总则/README.md
Normal file
34
docs/00_项目总则/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# MapLibreParticleLab 项目总则目录
|
||||||
|
|
||||||
|
## 定位
|
||||||
|
`docs/00_项目总则/` 是本项目长期规则层(Source of Truth)。保存经用户与 GPT 确认的长期设计决策、开发环境、系统构建规范、Codex 协作规则、Step/执行报告规范,以及当前正式基线与路线。普通 Step 默认禁止修改本目录。
|
||||||
|
|
||||||
|
## 当前有效文件
|
||||||
|
1. `粒子系统项目规则与设计决策_v1.md`
|
||||||
|
2. `开发环境与系统构建规范_v1.md`
|
||||||
|
3. `Step指令书与执行报告规范_v1.md`
|
||||||
|
4. `当前基线与开发路线_v2.md`(当前)
|
||||||
|
|
||||||
|
`当前基线与开发路线_v1.md` 保留为 2026-08-29 历史冻结,不再作为当前构建/发布依据。
|
||||||
|
|
||||||
|
## 阅读顺序
|
||||||
|
`AGENTS.md` → 本目录 README → 四份当前有效总则 → 当前 Step 指令书 → 上一 Step 执行报告 → 其他阶段文档。
|
||||||
|
|
||||||
|
## 维护职责
|
||||||
|
- GPT:参考“粒子系统”GPT 项目讨论,提出总则更新。
|
||||||
|
- 用户:确认或否决长期规则。
|
||||||
|
- Codex:普通 Step 只读;只有明确授权的总则更新任务才可修改。
|
||||||
|
|
||||||
|
## 冲突处理
|
||||||
|
若 Step 与总则冲突,Codex 停止冲突部分,在执行报告记录,不得自行改总则。
|
||||||
|
|
||||||
|
## 版本规则
|
||||||
|
同主题仅递增 `_v1`、`_v2`、`_v3`;不使用“最终版/最新版/final/new/修正版”。
|
||||||
|
|
||||||
|
## 大阶段目录
|
||||||
|
建议:`10_架构调查/`、`20_测试App/`、`30_粒子核心/`、`40_天气数据链/`、`50_渲染与视觉/`、`60_性能与后端/`、`70_NavSea集成/`、`80_iOS与Metal/`、`90_升级与维护/`。进入新大阶段前,GPT 先告诉用户目录名、目的、预计 Step 范围。
|
||||||
|
|
||||||
|
## 换窗口/换会话恢复
|
||||||
|
1. 读本目录全部当前有效总则;2. 读当前基线;3. 读上一 Step 执行报告;4. 检查 Git branch/commit/working tree/submodule;5. 再继续下一 Step。
|
||||||
|
|
||||||
|
分层:GPT 项目讨论=设计过程;项目总则=冻结规则;Step 指令书=工作合同;执行报告=实际事实;Git=最终代码事实。
|
||||||
55
docs/00_项目总则/Step指令书与执行报告规范_v1.md
Normal file
55
docs/00_项目总则/Step指令书与执行报告规范_v1.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Step 指令书与执行报告规范 v1
|
||||||
|
|
||||||
|
## 1. 命名
|
||||||
|
指令书:`step-xxxx-目的内容.md`。
|
||||||
|
报告:`step-xxxx-目的内容-执行报告.md`。
|
||||||
|
统一使用“执行报告”。
|
||||||
|
|
||||||
|
## 2. 同目录
|
||||||
|
指令书与报告放同一个大阶段目的目录。
|
||||||
|
|
||||||
|
## 3. 指令书固定章节
|
||||||
|
1. Step 编号和目的
|
||||||
|
2. 当前已冻结基线
|
||||||
|
3. 本 Step 目标
|
||||||
|
4. 非目标/禁止事项
|
||||||
|
5. 允许修改范围
|
||||||
|
6. 禁止修改范围
|
||||||
|
7. 实施要求
|
||||||
|
8. 构建要求
|
||||||
|
9. 测试要求
|
||||||
|
10. 验收标准
|
||||||
|
11. Git 要求
|
||||||
|
12. 必做输出
|
||||||
|
13. 执行报告完整文件名
|
||||||
|
14. 执行报告固定内容
|
||||||
|
15. 阻塞与停止条件
|
||||||
|
|
||||||
|
## 4. Codex 前置必读
|
||||||
|
AGENTS.md → 本目录 README → 三份长期总则 → 当前基线与路线 → 本阶段已有文档 → 上一 Step 执行报告。
|
||||||
|
|
||||||
|
## 5. Jest 特殊规则
|
||||||
|
GPT 生成任何正式 Step 指令书之前必须先询问用户是否加入 Jest。未经明确确认不得写入。若加入,先确认测试力度、允许修复范围、是否可改生产代码、最大修复轮数、停止条件。
|
||||||
|
|
||||||
|
## 6. 执行报告固定结构
|
||||||
|
1. 执行结论:PASS/PARTIAL/BLOCKED/FAIL
|
||||||
|
2. Git 基线:branch/start commit/end commit/working tree/submodule
|
||||||
|
3. 实际修改文件
|
||||||
|
4. 新增文件
|
||||||
|
5. 实际实现内容
|
||||||
|
6. 与指令书差异(无也写“无”)
|
||||||
|
7. 构建记录:command/renderer/build type/JOBS/result/关键日志/耗时
|
||||||
|
8. 测试记录
|
||||||
|
9. 生成产物:文件名/路径/大小/SHA256/Renderer/Build Type/ABI
|
||||||
|
10. 已知问题
|
||||||
|
11. 未完成事项
|
||||||
|
12. 下一 Step 建议(只建议,不自行创建)
|
||||||
|
|
||||||
|
## 7. 报告必须反映事实
|
||||||
|
禁止未执行测试写 PASS、计划写成已完成、隐藏失败、用“应该正常”替代结果、把 Win11 人工验证写成 Linux Codex 已完成。若只完成 Linux APK build,必须明确“Win11 Android Simulator 人工验证待用户执行”。
|
||||||
|
|
||||||
|
## 8. 总则修改
|
||||||
|
普通 Step 禁止修改 `docs/00_项目总则/`。如认为需调整,只在报告提出建议。
|
||||||
|
|
||||||
|
## 9. GPT 写新 Step 的依据
|
||||||
|
必须参考:“粒子系统”GPT 项目既有设计、项目总则、当前基线、上一 Step 报告、当前 Git 事实、用户最新确认。
|
||||||
57
docs/00_项目总则/开发环境与系统构建规范_v1.md
Normal file
57
docs/00_项目总则/开发环境与系统构建规范_v1.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# 开发环境与系统构建规范 v1
|
||||||
|
|
||||||
|
## 1. 正式基线
|
||||||
|
源码:`/mnt/sda1/MapLibreParticleLab`;稳定分支:`particle/main`。Linux:Ubuntu 24.04 LTS、x86_64、4 CPU threads、约 8GB RAM、默认 `JOBS=3`。Win11:NavSea、Android 模拟器、Particle Test APK 运行、NavSea Android 手工编译。
|
||||||
|
|
||||||
|
## 2. Step 开始前
|
||||||
|
记录:`git branch --show-current`、`git rev-parse HEAD`、`git status --short`、`git submodule status`。
|
||||||
|
|
||||||
|
## 3. Git 远端
|
||||||
|
origin=`ssh://git@192.168.200.230:2222/tei/MapLibreParticleLab.git`;upstream=`https://github.com/maplibre/maplibre-native.git`,只读,push 禁用;保留 `git rerere`。
|
||||||
|
|
||||||
|
## 4. Step branch
|
||||||
|
建议 `step/0001-...`。流程:particle/main → Step branch → Codex → build/test/report → GPT/用户审查 → PASS → merge particle/main → push → freeze。
|
||||||
|
|
||||||
|
## 5. 构建磁盘
|
||||||
|
大型 build 必须使用 Linux 本地 SSD;禁止 NAS/SMB/NFS 作为 build root。NAS 只做备份、APK/AAR 中转、归档。
|
||||||
|
|
||||||
|
## 6. 环境入口
|
||||||
|
构建前 `source /root/.maplibre-particle-env`。Codex 不得自行升级 Node/Java/SDK/NDK/Gradle/AGP/CMake/MapLibre baseline,除非 Step 明确要求。
|
||||||
|
|
||||||
|
## 7. 已验证环境
|
||||||
|
Linux Native OpenGL PASS;mbgl-render PASS;mbgl-render-test-runner PASS;Android OpenGL Debug AAR PASS;Android Vulkan Debug AAR PASS;Android OpenGL Release AAR PASS。SDK=`/root/Android/Sdk`;NDK=`28.2.13676358`。
|
||||||
|
|
||||||
|
## 8. Development Build
|
||||||
|
默认 Debug + OpenGL + 增量 + Particle Test App。小改不强制 clean/Release/Vulkan/NavSea/全量回归。
|
||||||
|
|
||||||
|
## 9. Gate Build
|
||||||
|
关键 Step 才做 Release(按需)、明确 Git commit、完整 build、APK/AAR、Renderer、ABI、大小、SHA256、执行报告、必要时 Vulkan/NavSea。
|
||||||
|
|
||||||
|
## 10. OpenGL/Vulkan
|
||||||
|
OpenGL 是主开发 backend;Vulkan 在专门 Step 做正式回归。核心必须遵循 MapLibre gfx 抽象。
|
||||||
|
|
||||||
|
## 11. Test App 日常链
|
||||||
|
MapLibre Native → Particle Test APK → 复制到 Win11 → Android Simulator。NavSea 不参与日常粒子调试。
|
||||||
|
|
||||||
|
## 12. 产物
|
||||||
|
建议统一 `dist/android/`。示例:`particle-test-step-0005-opengl-debug.apk`、`particle-test-step-0009-opengl-release.apk`、`particle-test-step-0014-vulkan-debug.apk`、`maplibre-particle-step-0016-opengl-release.aar`。Gate 产物记录 SHA256。
|
||||||
|
|
||||||
|
## 13. Linux/Win11 边界
|
||||||
|
Linux Codex 默认责任到 APK/AAR build PASS、产物存在、大小、SHA256、执行报告。用户负责复制到 Win11、安装模拟器、人工视觉/交互验证。
|
||||||
|
|
||||||
|
## 14. NavSea Android
|
||||||
|
继续由用户在 Win11 手工编译;Linux Codex 不代替用户编译 NavSea。
|
||||||
|
|
||||||
|
## 15. Gate A/B/C
|
||||||
|
Gate A:maplibre-react-native 11.3.6 + 官方 Native 13.2.0,PASS。
|
||||||
|
Gate B:11.3.6 + 官方 Native 13.4.1,PASS。
|
||||||
|
Gate C:11.3.6 + Linux 自编译 Native 13.4.1 OpenGL Release,PASS。
|
||||||
|
|
||||||
|
## 16. 当前 Release AAR
|
||||||
|
`MapLibreAndroid-opengl-release.aar`,约 156MB,SHA256=`5c67d2751fafe211a0df4b82deded09ab1c07cf6ca0f4a12a880283287f85e5c`,ABI:arm64-v8a/armeabi-v7a/x86/x86_64。
|
||||||
|
|
||||||
|
## 17. 构建失败
|
||||||
|
只修当前 Step 允许范围;若必须升级工具链/MapLibre、大改 gfx、修改 NavSea/冻结接口、删除测试或降低门槛,STOP 并报告 BLOCKED。
|
||||||
|
|
||||||
|
## 18. 禁止项
|
||||||
|
普通 Step 禁止自行升级 MapLibre/AGP/Gradle/NDK/Java/Node,禁止修改 NavSea/冻结接口、删除测试、降低门槛、用 CPU 粒子冒充正式 GPU 实现、修改 `docs/00_项目总则/`。
|
||||||
42
docs/00_项目总则/当前基线与开发路线_v1.md
Normal file
42
docs/00_项目总则/当前基线与开发路线_v1.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# 当前基线与开发路线 v1
|
||||||
|
|
||||||
|
> **历史冻结:2026-08-29。** 当前基线已由 `当前基线与开发路线_v2.md` 取代;本文仅保留当时事实,不用于当前 Android/RN 构建。
|
||||||
|
|
||||||
|
|
||||||
|
## 1. 冻结日期
|
||||||
|
2026-08-29
|
||||||
|
|
||||||
|
## 2. Git
|
||||||
|
正式分支:`particle/main`。STEP-000 冻结提交:`ea5b8f9d7fc53204a6d7fa088e9cf89be765b5ac`,说明 `chore: freeze MapLibreParticleLab step 0 baseline`。origin=`ssh://git@192.168.200.230:2222/tei/MapLibreParticleLab.git`。upstream=`https://github.com/maplibre/maplibre-native.git`。
|
||||||
|
|
||||||
|
## 3. MapLibre Native
|
||||||
|
正式粒子开发 baseline:Android 13.4.1。OpenGL 主开发;Vulkan/Metal 后续。
|
||||||
|
|
||||||
|
## 4. Linux 构建状态
|
||||||
|
Linux Native OpenGL PASS;mbgl-render PASS;mbgl-render-test-runner PASS;Android OpenGL Debug AAR PASS;Android Vulkan Debug AAR PASS;Android OpenGL Release AAR PASS。SDK=`/root/Android/Sdk`;NDK=`28.2.13676358`;JOBS=3。
|
||||||
|
|
||||||
|
## 5. Release AAR
|
||||||
|
`MapLibreAndroid-opengl-release.aar`,约 156MB,SHA256=`5c67d2751fafe211a0df4b82deded09ab1c07cf6ca0f4a12a880283287f85e5c`,ABI:arm64-v8a/armeabi-v7a/x86/x86_64。
|
||||||
|
|
||||||
|
## 6. NavSea
|
||||||
|
Win11;React Native 0.85.2;New Architecture=true;`@maplibre/maplibre-react-native@11.3.6`。
|
||||||
|
|
||||||
|
## 7. Gate
|
||||||
|
Gate A:11.3.6 + 官方 Native 13.2.0,PASS。
|
||||||
|
Gate B:11.3.6 + 官方 Native 13.4.1,PASS。
|
||||||
|
Gate C:11.3.6 + Linux 自编译 Native 13.4.1 OpenGL Release,PASS。
|
||||||
|
|
||||||
|
## 8. 固定分工
|
||||||
|
Linux:MapLibre Native/Particle/Test App/APK/AAR/Fixture/Native Debug/Git。Win11:Android Simulator/Particle Test APK/NavSea/NavSea Android 编译/产品 Gate。NavSea 不作为日常 Particle 宿主。
|
||||||
|
|
||||||
|
## 9. 下一大阶段
|
||||||
|
建议 `10_架构调查/`。正式建立前 GPT 先向用户说明该阶段目的与预计 Step 范围。
|
||||||
|
|
||||||
|
## 10. 下一 Step
|
||||||
|
`step-0001-MapLibre粒子接入点调查.md`。只读调查 Style Layer、Source/Tile/Bucket、RenderLayer/RenderPass、gfx、Shader、Texture/RenderTarget、OpenGL/Vulkan/Metal、Raster/Heatmap/Hillshade/CustomLayer 等;不实现正式粒子。
|
||||||
|
|
||||||
|
## 11. 近期路线
|
||||||
|
STEP-0001 接入点调查;0002 Android Particle Test App;0003 Fixture;0004 RasterParticleLayer 骨架;0005 最小 GPU Particle;0006 Camera/Projection;0007 Particle State 冻结;0008 Velocity/RasterArray/Tile;0009 Trail;0010 生命周期与视觉;0011 WeatherColorLayer;0012 Land/Ocean Mask;0013 OpenGL 性能;0014 Vulkan;0015 Test App 第一阶段冻结;0016 NavSea Particle Gate;0017 NavSea API/Adapter;0018 Android 产品回归;0019 iOS/Metal;0020 NavSea iOS;0021 upstream/长期维护。
|
||||||
|
|
||||||
|
## 12. 换窗口恢复
|
||||||
|
读项目总则 → 当前基线 → 上一 Step 报告 → 检查 Git → 继续下一 Step。
|
||||||
41
docs/00_项目总则/当前基线与开发路线_v2.md
Normal file
41
docs/00_项目总则/当前基线与开发路线_v2.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# 当前基线与开发路线 v2
|
||||||
|
|
||||||
|
## 1. 冻结日期
|
||||||
|
2026-09-14。当前正式分支为 `particle/main`,本文对应 QBIN2 V4 Android/RN Test Host 已完成真机/模拟器人工验证后的基线。
|
||||||
|
|
||||||
|
## 2. 当前 Git 基线
|
||||||
|
当前整理起点 HEAD:`de8d230b87345c2538c71318681119cdecb2762a`。正式远端为 `origin/particle/main`。历史 STEP 文档用于追溯当时事实,不再自动代表当前构建方式。
|
||||||
|
|
||||||
|
## 3. 当前 Android/RN 版本
|
||||||
|
- Native OpenGL AAR:`cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.3`。
|
||||||
|
- RN package:`@navsea/maplibre-particle-rn@13.4.1-particle.3`。
|
||||||
|
- MLRN:`@maplibre/maplibre-react-native@11.3.6`。
|
||||||
|
- React Native Test Host:RN `0.85.2`、New Architecture。
|
||||||
|
- Native/RN 版本必须同为 `.3`;不得再使用 `.2` 作为当前消费坐标。
|
||||||
|
|
||||||
|
## 4. 当前数据与渲染主链
|
||||||
|
正式新增链路为 QBIN V4:宿主下载/缓存 raw QBIN,本地文件通过 `FieldRuntime.submitEncodedField(...)` 进入 Android Bridge/JNI,再由 `RasterArraySource::submitEncodedField(...)` 与 `QbinV2Decoder::decode(...)` 解码。MapLibre 不负责 HTTP、provider、manifest、缓存或业务调度。
|
||||||
|
|
||||||
|
旧 V3 canonical `submitField(...)` 仍保留用于历史兼容和 Test Host 对照,不是 NavSea 新接入的推荐主链。
|
||||||
|
|
||||||
|
## 5. 当前 V4 公共控制面
|
||||||
|
NavSea/Consumer 新代码优先使用:`submitEncodedField`、`setEncodedColorBinding`、`setEncodedParticleBinding`、`setEncodedForecastTime`、`setEncodedForecastPlaybackState`、`setEncodedForecastPlaybackSpeed`、`clearEncodedField`。运行时继续由 Core 拥有 Field Window、Forecast Clock、T0/T1/mix 与 boundary reuse。
|
||||||
|
|
||||||
|
## 6. 当前发布来源
|
||||||
|
`https://pbf.jpcn.ccwu.cc/` 映射 Linux `/mnt/nas/pbf`。Native 正常发布到 `/mnt/nas/pbf/maven/`,RN tgz 正常发布到 `/mnt/nas/pbf/npm/`;Windows 11 正常验证必须从公网 npm/Maven 消费,不以 Windows Maven Local 或 LAN AAR 复制作为正式路径。
|
||||||
|
|
||||||
|
当前已验证 AAR SHA-256:`9ef03eb0d0c03457adc04847cfa69fb67d85191daecd64d3b76db2ad8cc688cd`。当前 `.3` RN tgz 在 2026-09-14 Kotlin 编译修正后重新发布;今后公开版本内容变化必须升版本,不再覆盖同版本。
|
||||||
|
|
||||||
|
## 7. 当前 Test Host
|
||||||
|
正式源码:`integration/react-native/MapLibreParticleRNTestHost/`。V4 Golden fixtures:Wind/Wave/Current/Rain 各 T0/T1,共 8 个 `.qbin`。Test Host 同时保留 V3 控制用于对照,但 V4 验收必须通过 `submitEncodedField`。
|
||||||
|
|
||||||
|
Windows 推荐工作目录必须是普通子目录,例如 `D:\React\MapLibreParticleRNTestHost-V4`,不要在盘符根目录执行 Expo prebuild。Metro 监听 `8091`,Debug App 访问设备 `localhost:8081`,固定使用 `adb -s 192.168.1.146 reverse tcp:8081 tcp:8091`。
|
||||||
|
|
||||||
|
## 8. 当前权威操作文档
|
||||||
|
Android AAR 公网发布、Win11 Test Host 构建、Metro/ADB 与 V4 验收以 `docs/50_Step记录/STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md` 为准。`tools/particle_android_pipeline/README.md` 已明确 v1 本地 handoff 工具只用于历史/离线兼容。
|
||||||
|
|
||||||
|
## 9. 历史文档边界
|
||||||
|
STEP-060~072 中的 `Maven Local`、`13.4.1-particle.1/.2`、旧 `D:\V308`、旧 reverse 组合等内容保留为历史验收证据,不得直接用于当前 `.3` 构建。需要当前命令时先读本文件和 STEP-080 v2。
|
||||||
|
|
||||||
|
## 10. 下一阶段
|
||||||
|
当前 Native/RN QBIN V4 Android 基线可用于 NavSea 最小正式接入。建议先做 Wind 单纵向切片:NavSea Weather/Cache 层准备本地 QBIN → V4 submit/binding/time → 地图显示/clear;通过后再扩展 Wave/Current/Rain。NavSea 不解析 QBIN,也不把网络 URL 交给 MapLibre。
|
||||||
55
docs/00_项目总则/粒子系统项目规则与设计决策_v1.md
Normal file
55
docs/00_项目总则/粒子系统项目规则与设计决策_v1.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# 粒子系统项目规则与设计决策 v1
|
||||||
|
|
||||||
|
## DEC-001 Linux 与 Win11 职责分离
|
||||||
|
Linux 是 MapLibre Native / Particle Renderer 的主要开发和构建环境,正式源码 `/mnt/sda1/MapLibreParticleLab`。Linux 负责 Native、Particle、Test App、Fixture、AAR/APK、OpenGL/Vulkan、Native 调试、性能与 Git。Win11 负责 NavSea、NavSea Android 编译、Android 模拟器、Particle Test APK 运行和产品 Gate。
|
||||||
|
|
||||||
|
## DEC-002 独立 Particle Test App
|
||||||
|
必须建立长期维护的独立测试 App。它不是 Demo,而是粒子 Renderer 的开发、调试、回归和性能平台。日常粒子开发不直接使用 NavSea。Android 第一阶段优先原生 Test App。
|
||||||
|
|
||||||
|
## DEC-003 NavSea 只用于关键产品 Gate
|
||||||
|
NavSea 是最终产品,不作为日常粒子调试宿主。只在自编译等价验证、关键 Particle Gate、真实 Layer Stack、Weather/Mask、产品级回归和最终集成时使用。
|
||||||
|
|
||||||
|
## DEC-004 OpenGL 为 Android 主开发 Backend
|
||||||
|
初期主开发 OpenGL;架构必须服从 MapLibre `gfx` 抽象,不能锁死 OpenGL。
|
||||||
|
|
||||||
|
## DEC-005 Vulkan / Metal 不是后补重写
|
||||||
|
Particle State、Velocity、Tile、生命周期、Style/RenderLayer、Coverage、Color Ramp、Mask 尽量共享;后端差异主要放在 Shader、Texture、RenderTarget、RenderPass、gfx backend。iOS/Metal 以后单独设计测试流程。
|
||||||
|
|
||||||
|
## DEC-006 WeatherColorLayer 与 RasterParticleLayer 分离
|
||||||
|
定量天气色场和粒子流动分为 `WeatherColorLayer` 与 `RasterParticleLayer`。
|
||||||
|
|
||||||
|
## DEC-007 天气色场不靠低透明度看底图
|
||||||
|
天气定量颜色保持准确;岸线、等深线、航标、航道、文字和导航对象绘制在天气层上方。
|
||||||
|
|
||||||
|
## DEC-008 Coverage
|
||||||
|
Wind=`GLOBAL`;Wave/Tide/Current/Sea Temperature 等=`OCEAN_ONLY`。
|
||||||
|
|
||||||
|
## DEC-009 统一 Land/Ocean Mask
|
||||||
|
所有 OCEAN_ONLY 字段共用统一 Mask;海岸线语义尽量与 NavSea/MapLibre 海图一致,不依赖天气模型 nodata。
|
||||||
|
|
||||||
|
## DEC-010 正式目标是 GPU Particle Engine
|
||||||
|
核心至少包括 Velocity Texture、Particle State Texture A/B、Ping-Pong、Update Pass、Draw Pass、Trail Render Target、Color Ramp。CPU 每粒子循环不能作为正式主实现。
|
||||||
|
|
||||||
|
## DEC-011 Camera 正确后再冻结 Particle State Model
|
||||||
|
先验证 Pan/Zoom/Rotate/Pitch/Resize/Camera Animation,再冻结状态格式。
|
||||||
|
|
||||||
|
## DEC-012 Fixture 优先
|
||||||
|
至少:uniform-east/north/west/south、vortex-clockwise/counterclockwise、shear、divergence、convergence、coastal-mask。真实天气服务器不是早期 Renderer 调试前置条件。
|
||||||
|
|
||||||
|
## DEC-013 thin-hook 原则
|
||||||
|
目标约 95% 粒子代码放新增文件/独立模块;MapLibre 官方现有文件只做必要 Layer/Source/RenderLayer/Shader/Build/Backend 薄接入,降低 upstream merge 冲突。
|
||||||
|
|
||||||
|
## DEC-014 普通 Step 禁止修改项目总则
|
||||||
|
需要改变长期规则时,只在执行报告提出建议。
|
||||||
|
|
||||||
|
## DEC-015 GPT“粒子系统”项目是设计背景来源
|
||||||
|
GPT 写设计和指令书时要参考该项目既有讨论;正式权威规则以 Git 仓库本目录为准。
|
||||||
|
|
||||||
|
## DEC-016 大阶段目录按目的建立
|
||||||
|
使用两位数字前缀;进入新阶段前 GPT 先告知用户目录目的和预计 Step 范围。
|
||||||
|
|
||||||
|
## DEC-017 Step 指令书与执行报告同目录
|
||||||
|
`step-xxxx-目的内容.md` 与 `step-xxxx-目的内容-执行报告.md` 放同一大阶段目的目录。
|
||||||
|
|
||||||
|
## DEC-018 报告由指令书预先定义
|
||||||
|
指令书必须事先写清报告完整路径、文件名、章节、Git 信息、构建信息、APK/AAR、大小、SHA256、PASS/PARTIAL/BLOCKED/FAIL。不得执行完后临时追加未定义报告。
|
||||||
3
docs/00_项目规范/00_文档目录与命名规范_v1.md
Normal file
3
docs/00_项目规范/00_文档目录与命名规范_v1.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 文档目录与命名规范 v1
|
||||||
|
|
||||||
|
正式目录:`00_项目规范/10_架构/20_调查/30_设计/40_任务/50_Step记录/60_测试/70_发布/报告`。正式说明文档文件名和正文使用中文;代码/API/命令保留英文。同主题只用 `_v1/_v2/_v3` 递增,不得用“最新版/修正版/final”制造近义文件。调查=`<主题>调查_vN.md`,设计=`<主题>设计_vN.md`,任务=`STEP-xxx_<主题>_Codex任务_vN.md`,Step=`STEP-xxx_<主题>_vN.md`,完成报告=`STEP-xxx_<主题>_Codex完成报告_vN.md`。完成报告只放 `docs/报告/`。
|
||||||
3
docs/00_项目规范/01_Codex协作规范_v1.md
Normal file
3
docs/00_项目规范/01_Codex协作规范_v1.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Codex 协作规范 v1
|
||||||
|
|
||||||
|
标准流程:只读调查 → 设计 → Step → 实现 → 构建/测试 → 完成报告 → Step 更新。没有调查结论不得凭猜测大改 Core。每个任务明确目标、必做项、允许/禁止范围、构建测试、必做输出、停止条件。无法完成必须明确阻塞,不伪造 PASS。默认禁止 force push、push upstream、改写官方 tag、删除历史 Step/报告、无授权扩大重构。
|
||||||
3
docs/00_项目规范/02_构建与验证规范_v1.md
Normal file
3
docs/00_项目规范/02_构建与验证规范_v1.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 构建与验证规范 v1
|
||||||
|
|
||||||
|
主环境 Ubuntu 24.04 x86_64,默认并行度 3。Linux 优先官方 CMake/Make 路线,至少保证 OpenGL build、mbgl-render、mbgl-render-test-runner。Android 官方路线:Java17、.nvmrc Node、npm install --ignore-scripts;`cd platform/android && RENDERER=opengl BUILDTYPE=Debug make apackage` 与 `RENDERER=vulkan BUILDTYPE=Debug make apackage`。涉及 renderer/backend 必须双 backend 验证。Linux 不得宣称 iOS/Metal PASS,必须 Mac 同 commit 验证。基础回归只用离线 fixture。GPU 阶段记录粒子数、FPS/frame time、backend 与 pan/zoom/rotate/pitch。
|
||||||
3
docs/00_项目规范/03_Step阶段记录规范_v1.md
Normal file
3
docs/00_项目规范/03_Step阶段记录规范_v1.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Step 阶段记录规范 v1
|
||||||
|
|
||||||
|
Step 连续编号 STEP-000/001/002,不跳号。状态只用:计划/进行中/阻塞/完成/冻结。每个 Step 至少有:状态、目标、基线、必做项勾选、允许修改、禁止修改、构建测试、必做输出、当前结果、阻塞、下一步。执行结束必须更新 Step、追加粒子开发记录、生成 docs/报告 完成报告;未完成项不得勾选。
|
||||||
3
docs/00_项目规范/04_Git与上游同步规范_v1.md
Normal file
3
docs/00_项目规范/04_Git与上游同步规范_v1.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Git 与上游同步规范 v1
|
||||||
|
|
||||||
|
upstream=MapLibre GitHub,仅 fetch,push=DISABLED;origin=用户 Gitea。日常分支 particle/main。提交小而单一。上游升级使用 upgrade/<release> 基于官方新 release 重放 Particle commits,完整回归后更新 PARTICLE_BASELINE.md;不得直接把 upstream/main 混进 particle/main。必须启用 git rerere。
|
||||||
3
docs/00_项目规范/05_粒子架构原则_v1.md
Normal file
3
docs/00_项目规范/05_粒子架构原则_v1.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 粒子架构原则 v1
|
||||||
|
|
||||||
|
目标链:RasterArraySource → RasterArrayTile/Velocity Field → WeatherColorLayer + RasterParticleLayer → RenderRasterParticleLayer → Particle State A/B + GPU Update/Draw/Trail → MapLibre gfx/backend → OpenGL/Vulkan/Metal。WeatherColor 表达定量颜色,Particle 表达方向/运动,默认不得靠降低 WeatherColor opacity 才看导航信息。Wind=GLOBAL;Wave/Tide/Current=OCEAN_ONLY 共用 Land/Ocean Mask。Particle State 格式/坐标系/生命周期/跨 tile 必须单独设计后冻结。
|
||||||
18
docs/00_项目规范/06_官方基线来源_v1.md
Normal file
18
docs/00_项目规范/06_官方基线来源_v1.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 官方基线来源 v1
|
||||||
|
|
||||||
|
本项目初始化版本依据 MapLibre 官方资料固定:
|
||||||
|
|
||||||
|
- MapLibre Native releases:`https://github.com/maplibre/maplibre-native/releases`
|
||||||
|
- Linux 开发文档:`https://maplibre.org/maplibre-native/docs/book/platforms/linux/`
|
||||||
|
- Render Tests:`https://maplibre.org/maplibre-native/docs/book/render-tests.html`
|
||||||
|
- Android release CI:MapLibre Native `.github/workflows/android-release.yml`
|
||||||
|
- Android `Versions.kt`:当前 core `18545ca` 的 `ndkVersion = 28.2.13676358`,`cmakeVersion = 3.24.0+`。
|
||||||
|
|
||||||
|
当前冻结:
|
||||||
|
|
||||||
|
- Android `android-v13.4.1` → core `18545ca`
|
||||||
|
- iOS `ios-v6.28.0` → core `18545ca`
|
||||||
|
- Android compileSdk = 34
|
||||||
|
- Java = 17
|
||||||
|
|
||||||
|
初始化脚本仍会运行时验证 Android/iOS tag 是否继续指向同一 core,避免只依赖本文档。
|
||||||
68
docs/00_项目规范/07_Android粒子Validation_APK构建规范_v1.md
Normal file
68
docs/00_项目规范/07_Android粒子Validation_APK构建规范_v1.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Android 粒子 Validation APK 构建规范 v1
|
||||||
|
|
||||||
|
## 目的
|
||||||
|
|
||||||
|
固定 `MapLibreAndroidTestApp` 粒子验证 APK 的制作流程,避免每次重新调查 Gradle 参数、Java/Android SDK 环境、native strip 与 APK 体积异常。
|
||||||
|
|
||||||
|
权威入口:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tools/particle_android_validation/build_validation_apk.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
除排错外,不再手工拼接 Validation APK 的 Gradle 命令。
|
||||||
|
|
||||||
|
## 固定构建契约
|
||||||
|
|
||||||
|
```text
|
||||||
|
App: MapLibreAndroidTestApp
|
||||||
|
Renderer: OpenGL
|
||||||
|
BuildType: Debug
|
||||||
|
ABI: x86_64
|
||||||
|
Native debug symbols: strip
|
||||||
|
Gradle console: plain
|
||||||
|
```
|
||||||
|
|
||||||
|
核心参数固定为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
:MapLibreAndroidTestApp:assembleOpenglDebug
|
||||||
|
-Pmaplibre.abis=x86_64
|
||||||
|
-Pmaplibre.validation.stripNativeDebugSymbols=true
|
||||||
|
--console=plain
|
||||||
|
```
|
||||||
|
|
||||||
|
## 必须执行的防错逻辑
|
||||||
|
|
||||||
|
1. 构建前删除目标 APK 文件,但不做全量 Gradle clean。
|
||||||
|
2. Java 必须为 17;Android SDK 必须可解析。
|
||||||
|
3. 构建失败时不得复用旧 APK。
|
||||||
|
4. 构建成功后必须校验 APK ZIP 完整性。
|
||||||
|
5. APK 内 native ABI 必须只有 `x86_64`。
|
||||||
|
6. 必须包含 `lib/x86_64/libmaplibre.so`。
|
||||||
|
7. 默认 `libmaplibre.so` 不得超过 80 MiB。
|
||||||
|
8. 默认 APK 不得超过 100 MiB。
|
||||||
|
9. 必须记录 SHA-256、commit、branch、dirty 状态和完整 Gradle 日志。
|
||||||
|
|
||||||
|
STEP-090/091 的正常 APK 参考体积约 57 MB。数百 MB 通常表示未 strip native debug symbols;约 95 MB 且 ZIP 逻辑内容仍约 60 MB,通常表示同一路径增量打包残留旧 ZIP 尾部数据。
|
||||||
|
|
||||||
|
## 标准产物目录
|
||||||
|
|
||||||
|
```text
|
||||||
|
artifacts/maplibre-particle/android-validation/
|
||||||
|
├── MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
|
||||||
|
├── build-info.txt
|
||||||
|
└── build.log
|
||||||
|
```
|
||||||
|
|
||||||
|
该目录为本地验证产物目录,不进入 Git。
|
||||||
|
|
||||||
|
## 边界
|
||||||
|
|
||||||
|
本 Pipeline 只负责构建和 APK 自验,不负责 ADB 安装、Activity 启动、设备交互或 STEP 业务场景验收。
|
||||||
|
|
||||||
|
Android 上机验证仍由后续独立步骤执行。构建成功不等于 STEP 场景已通过。
|
||||||
|
|
||||||
|
## 变更规则
|
||||||
|
|
||||||
|
若未来测试 App、ABI、Renderer、strip 策略或产物门槛发生正式变化,应先修改 Pipeline 脚本与本规范,再使用新流程;不得只在聊天或临时命令中形成新的事实基线。
|
||||||
773
docs/10_架构/20_粒子系统总体架构设计_v1.md
Normal file
773
docs/10_架构/20_粒子系统总体架构设计_v1.md
Normal file
@@ -0,0 +1,773 @@
|
|||||||
|
# MapLibreParticleLab 粒子系统总体架构设计 v1
|
||||||
|
|
||||||
|
设计日期:2026-08-29
|
||||||
|
|
||||||
|
基线分支:`particle/main`
|
||||||
|
|
||||||
|
基线 Core:`18545ca10d296704b2cba675b182ff2c8da24588`
|
||||||
|
|
||||||
|
性质:正式总体架构设计,不包含实现
|
||||||
|
|
||||||
|
## 1. 设计输入与适用范围
|
||||||
|
|
||||||
|
本设计以项目总则、项目规范、`PARTICLE_BASELINE.md`、`docs/20_调查/10_架构调查报告_v1.md` 和当前 MapLibre Native 源码为事实基础。当前仓库不是 React Native 工程,因此架构从 Native Core、Android/iOS 平台薄接入和 MapLibre gfx 开始,不定义不存在的 JS 每帧运行时。
|
||||||
|
|
||||||
|
现有 `docs/10_架构/MapLibre粒子总体架构_v1.md` 只给出了 Layer 顺序和 GPU 组件候选,与本设计没有冲突。本文件把这些候选收敛为正式模块、接口和生命周期;后续以本文件作为 `30_最小技术验证` 和实现任务的主要架构输入。
|
||||||
|
|
||||||
|
固定长期边界继续有效:clean-room 独立实现;正式实现不用 React Native/WebView/外部 Overlay;Android/iOS 共用 C++ Core;OpenGL 为 Android 主开发 backend;WeatherColorLayer 与 RasterParticleLayer 分离;Wind=`GLOBAL`;Wave/Tide/Current=`OCEAN_ONLY` 并共用 Land/Ocean Mask;第一阶段只用离线 fixture。
|
||||||
|
|
||||||
|
## 2. 核心决策摘要
|
||||||
|
|
||||||
|
1. **唯一正式主路线**:新增独立 Core `RasterParticleLayer`、`RenderRasterParticleLayer` 和隔离的 `mbgl::particle` 模块,进入 Style/RenderTree,通过 MapLibre gfx 执行 GPU simulation、trail 和最终合成。
|
||||||
|
2. `CustomLayerHost` 与 `CustomDrawableLayer` 不作为正式粒子引擎宿主;需要时只可用于一次性技术探针,不能进入公共 API、产品依赖或长期双轨。
|
||||||
|
3. 新增 `RasterArraySource` 作为 Field 数据边界;WeatherColorLayer 与 RasterParticleLayer 可引用同一 Field snapshot 和 Mask,但拥有不同 RenderLayer、GPU 状态和生命周期。
|
||||||
|
4. 每帧由 MapLibre render loop 驱动;活动粒子通过 Core render state 持续声明 `needsRepaint`,不建立第二套 UI/JS 动画循环。
|
||||||
|
5. Camera/Matrix 只从当前 render frame 的 `PaintParameters/TransformParameters/TransformState` 读取。
|
||||||
|
6. 第一版 GPU simulation 使用跨 backend 的 render-pass ping-pong,不以 compute 为前置;compute 仅作为后续 capability-gated 优化。
|
||||||
|
7. 第一阶段正式视觉范围包含粒子、颜色映射和屏幕空间 trail;最小验证可先无 trail 验证状态更新,但在第一阶段冻结前必须通过 trail 验证。
|
||||||
|
8. 第一阶段实现/日常验证范围是 Android OpenGL;Android Vulkan 在第一阶段冻结前做 gfx 兼容 gate;iOS OpenGL/Metal 保留相同 Core contract,在独立后续阶段验证。
|
||||||
|
9. 下一阶段选择 **B:进入 `30_最小技术验证`**,不直接进入正式实现。
|
||||||
|
|
||||||
|
## 3. 候选路线比较与选择
|
||||||
|
|
||||||
|
| 候选 | 优点 | 不作为正式主路线的原因 | 定位 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `CustomLayerHost/RenderCustomLayer` | 已有同帧 transform 和 backend callback;示例可运行 | Android 使用 raw host 指针且 API 标记 experimental;平台 context/encoder 不对称;公共生命周期容易平台化 | 只允许一次性验证,不进入正式 API |
|
||||||
|
| `CustomDrawableLayer` | 已使用 gfx DrawableBuilder,跨 backend 程度较高 | 当前接口偏向线、面、符号、几何 drawable;不天然表达 velocity、state ping-pong、trail target 和 source contract | 作为 gfx/Drawable 设计参考 |
|
||||||
|
| 独立 Core particle layer | 可进入 Style、RenderTree、layer order、repaint、context lifecycle;可隔离数据与 GPU 资源;平台只需薄 wrapper | 需要新增 Layer/Source/RenderLayer、shader 注册和少量 RenderTarget 调度薄接入 | **正式唯一主路线** |
|
||||||
|
| MapView Overlay/独立 renderer | 开发表面上独立 | 与地图 frame、matrix、depth/layer order、context 和生命周期不同步;违反固定规则 | 拒绝 |
|
||||||
|
|
||||||
|
正式路线的退出条件不是“以后再决定”:从第一个可保留实现开始,产品 API 和生产资源均必须归属独立 Core layer。若技术验证临时使用 CustomLayer,验证结束后仅保留测试结论;正式 `RasterParticleLayer` 可渲染第一张 fixture 且通过 lifecycle gate 时,任何 CustomLayer 粒子探针不得继续作为产品路径。
|
||||||
|
|
||||||
|
主路线风险是现有通用 RenderTarget/LayerGroup 是否足以表达多 offscreen pass、跨 backend 的资源状态转换,以及新增 Layer 对 `needsRepaint` 的最小接入是否能够保持 thin-hook。风险不通过平台分叉规避,而由第 26 节的独立 RenderLayer、State/Trail ping-pong 和 context recreation 验证决定;若验证失败,只允许调整粒子专用 gfx 抽象或必要薄 hook,不回退到 Overlay、JS loop 或两套平台引擎。
|
||||||
|
|
||||||
|
## 4. 粒子系统总体架构图
|
||||||
|
|
||||||
|
```text
|
||||||
|
Host Native API
|
||||||
|
├─ RasterArraySource ───────────────┐
|
||||||
|
├─ WeatherColorLayer ───────┐ │
|
||||||
|
└─ RasterParticleLayer ─────┼───────┘
|
||||||
|
▼
|
||||||
|
Style immutable snapshot
|
||||||
|
▼
|
||||||
|
RenderTree / RenderOrchestrator / layer order
|
||||||
|
┌────────────┴────────────┐
|
||||||
|
▼ ▼
|
||||||
|
RenderWeatherColorLayer RenderRasterParticleLayer
|
||||||
|
│
|
||||||
|
mbgl::particle Core
|
||||||
|
Field / Simulation / Mask / GPU / Controller
|
||||||
|
│
|
||||||
|
gfx Texture/Buffer/RenderTarget/Drawable/Shader
|
||||||
|
│
|
||||||
|
OpenGL / Vulkan / Metal backend → GPU
|
||||||
|
```
|
||||||
|
|
||||||
|
正式 Layer 顺序由宿主 Style 控制,推荐:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Base Map
|
||||||
|
→ WeatherColorLayer
|
||||||
|
→ RasterParticleLayer
|
||||||
|
→ Navigation Reference / coast / labels
|
||||||
|
→ AIS / Route / Ownship / Alarm
|
||||||
|
```
|
||||||
|
|
||||||
|
WeatherColor 保持定量色场;导航信息通过更高 Style Layer 显示,不以降低天气层透明度作为默认策略。
|
||||||
|
|
||||||
|
## 5. Core 模块边界与建议目录
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/mbgl/style/sources/
|
||||||
|
└─ raster_array_source.hpp 公共 Field Source API
|
||||||
|
include/mbgl/style/layers/
|
||||||
|
├─ weather_color_layer.hpp 定量色场 Layer API
|
||||||
|
└─ raster_particle_layer.hpp 粒子 Layer API
|
||||||
|
|
||||||
|
src/mbgl/particle/
|
||||||
|
├─ field/ Field descriptor/snapshot/validation
|
||||||
|
├─ data_adapter/ fixture 与未来数据适配
|
||||||
|
├─ simulation/ 状态语义、时间步、respawn
|
||||||
|
├─ render/ pass 描述、frame parameters
|
||||||
|
├─ gpu/ velocity/state/trail/mask resources
|
||||||
|
├─ mask/ Land/Ocean Mask snapshot/cache/contract
|
||||||
|
└─ controller/ 状态机、revision、repaint、错误
|
||||||
|
|
||||||
|
src/mbgl/style/sources/ RasterArraySource Impl
|
||||||
|
src/mbgl/style/layers/ 两个 Layer Impl/properties
|
||||||
|
src/mbgl/renderer/sources/ RenderRasterArraySource
|
||||||
|
src/mbgl/renderer/layers/
|
||||||
|
├─ render_weather_color_layer.*
|
||||||
|
└─ render_raster_particle_layer.*
|
||||||
|
src/mbgl/layermanager/ 两个 LayerFactory 的薄注册
|
||||||
|
shaders/particle/ update/draw/trail/composite shader contract
|
||||||
|
platform/android/ 类型安全 Java/Kotlin + JNI 薄 wrapper
|
||||||
|
platform/darwin/ ObjC/Swift Source/Layer peer 薄 wrapper
|
||||||
|
```
|
||||||
|
|
||||||
|
目标仍是约 95% 粒子代码位于新增文件。允许修改的官方既有文件只限 Layer/Source factory 注册、shader/build 清单、RenderTarget/repaint 所需的最小 hook;不得把算法写入 Map、RasterLayer、Camera、MapView 或 backend 主文件。
|
||||||
|
|
||||||
|
### 5.1 Particle Field
|
||||||
|
|
||||||
|
`ParticleFieldDescriptor` 描述:field id/type、revision、语义单位、bounds、CRS、grid registration、尺寸、U/V、NoData、时间片、display domain 和数据版本。`ParticleFieldSnapshot` 是 descriptor 与只读数据的不可变组合。
|
||||||
|
|
||||||
|
Field 数据进入 Core 后统一为:U 为正东、V 为正北、canonical simulation unit 为米/秒。原始数据单位转换属于 Data Adapter,不进入每帧 shader 的业务分支。
|
||||||
|
|
||||||
|
### 5.2 Particle Data Adapter
|
||||||
|
|
||||||
|
第一阶段 `OfflineFixtureParticleAdapter` 负责读取、校验、字节序转换并产生 immutable snapshot。未来真实天气协议必须通过新的 adapter 转成同一 snapshot,Renderer 不认识 URL、HTTP、GRIB、厂商字段名或 Server 协议。
|
||||||
|
|
||||||
|
### 5.3 Particle Simulation
|
||||||
|
|
||||||
|
Core 定义状态语义、固定/受限 timestep、field sample、NoData、mask、respawn 和 deterministic seed。正式每粒子更新在 GPU;CPU 只负责 fixture 解码、验证、测试参考值和资源描述,不允许每帧 CPU 粒子循环冒充正式实现。
|
||||||
|
|
||||||
|
### 5.4 Particle Render
|
||||||
|
|
||||||
|
负责 simulation pass、particle draw、trail fade/accumulation、最终 translucent composite、viewport 和当前 frame transform。它不拥有天气色场 RenderLayer。
|
||||||
|
|
||||||
|
### 5.5 Particle GPU Resources
|
||||||
|
|
||||||
|
负责 velocity texture、state A/B、trail A/B、mask texture、uniform、shader set、context generation、创建/释放/重建。公共逻辑只使用 gfx 抽象;backend-specific 格式映射、barrier/encoder 细节留在 gfx/backend。
|
||||||
|
|
||||||
|
### 5.6 Particle Mask
|
||||||
|
|
||||||
|
Mask 是可共享的 immutable snapshot 与 GPU resource,不属于某一个 RasterParticleLayer。Wave/Tide/Current 和未来 OCEAN_ONLY field 通过 mask id/revision 引用同一资源。
|
||||||
|
|
||||||
|
### 5.7 Particle Controller / Layer State
|
||||||
|
|
||||||
|
Controller 只管理 enable、source/mask revision、map/data/GPU readiness、状态机、repaint 和错误;UI/宿主不得持有或销毁 GPU texture。
|
||||||
|
|
||||||
|
## 6. 与 Style、RenderTree 和 gfx 的关系
|
||||||
|
|
||||||
|
- `RasterArraySource` 进入 Style Source 系统,负责提供 immutable Field snapshot,不直接绘制。
|
||||||
|
- `WeatherColorLayer` 和 `RasterParticleLayer` 分别引用 source id,进入 Style Layer、LayerFactory、RenderLayer 和 RenderTree。
|
||||||
|
- `RenderRasterParticleLayer` 最终合成属于 `Translucent` pass,按 Style layer index 与其他地图内容排序。
|
||||||
|
- simulation 与 trail 使用独立 offscreen render targets,在主 buffer 合成前完成;最终 composite drawable 仍在该 Layer 的 translucent 位置绘制。
|
||||||
|
- Layer 活动且可见时参与 `needsRepaint`;首次 enable、数据/选项变化和恢复时调用一次现有 invalidate/`triggerRepaint` 路径唤醒渲染。
|
||||||
|
- 每帧 transform 直接来自当前 `PaintParameters`,不缓存 UI 线程推测值。
|
||||||
|
|
||||||
|
### 6.1 复用的 gfx 能力
|
||||||
|
|
||||||
|
| 现有能力 | 用途 |
|
||||||
|
|---|---|
|
||||||
|
| `Texture2D` | velocity、state、mask、trail texture |
|
||||||
|
| `DynamicTexture` | 仅在技术验证证明适合局部 field 更新时使用;不是默认 state 资源 |
|
||||||
|
| Buffer/UniformBuffer | quad/point geometry、frame/field/options uniform |
|
||||||
|
| `RenderTarget` | simulation output、trail accumulation |
|
||||||
|
| `DrawableBuilder` | 最终 composite 与可由通用 drawable 表达的 draw geometry |
|
||||||
|
| `DrawableTweaker` | 同帧 transform/options uniform 更新 |
|
||||||
|
| Shader registry | 后端 shader 注册与查询 |
|
||||||
|
| UploadPass | field/mask/静态资源低频上传 |
|
||||||
|
| CommandEncoder | pass encoding/present,由 render thread 和 gfx 管理 |
|
||||||
|
|
||||||
|
新增粒子专用抽象仅描述语义:`ParticleVelocityResource`、`ParticleStateResources`、`ParticleTrailResources`、`ParticleMaskResource`、`ParticleShaderSet`、`ParticleFrameParameters`。这些类型不能暴露 GL/Vulkan/Metal handle。
|
||||||
|
|
||||||
|
## 7. WeatherColorLayer 与 RasterParticleLayer
|
||||||
|
|
||||||
|
| 边界 | WeatherColorLayer | RasterParticleLayer |
|
||||||
|
|---|---|---|
|
||||||
|
| 目的 | 定量连续色场 | 方向、运动、动态流线 |
|
||||||
|
| RenderLayer | 独立 `RenderWeatherColorLayer` | 独立 `RenderRasterParticleLayer` |
|
||||||
|
| GPU 状态 | color ramp/field sampling 资源 | state A/B、simulation、trail、particle draw |
|
||||||
|
| 动画 | 不承担粒子动画 | 由 MapLibre frame loop 驱动 |
|
||||||
|
| 可共享 | RasterArraySource snapshot、grid metadata、time slice、Mask snapshot/cache | 同左 |
|
||||||
|
| 不共享 | RenderLayer、粒子 state、trail target、动画状态 | 同左 |
|
||||||
|
|
||||||
|
二者不是同一 Layer 的模式开关。删除或禁用粒子 Layer 不影响 WeatherColor;更换天气颜色样式不重置 Particle State,除非 Field revision、Mask 或影响 simulation 的参数同时改变。
|
||||||
|
|
||||||
|
## 8. Field 与显示域模型
|
||||||
|
|
||||||
|
`FieldType` 第一版至少包含 `WIND`、`WAVE`、`TIDE`、`CURRENT`。`DisplayDomain` 只有 `GLOBAL` 和 `OCEAN_ONLY`。
|
||||||
|
|
||||||
|
规则由 Core validation 强制执行:
|
||||||
|
|
||||||
|
- WIND → `GLOBAL`,陆地与海洋均可显示,不依赖 Land/Ocean Mask。
|
||||||
|
- WAVE/TIDE/CURRENT → `OCEAN_ONLY`,必须有有效 Mask;mask 缺失时 fail closed,不在陆地或未知区继续显示。
|
||||||
|
- 未来新增 OCEAN_ONLY field 复用同一 Mask contract,不创建 field-specific mask engine。
|
||||||
|
|
||||||
|
Field snapshot 的 revision 单调变化。只改变 UI 可视属性不生成 Field revision;U/V、bounds、grid、time slice、NoData 或数据版本变化必须生成新 revision。
|
||||||
|
|
||||||
|
## 9. 第一阶段离线 fixture contract v1
|
||||||
|
|
||||||
|
### 9.1 目录与文件
|
||||||
|
|
||||||
|
每个 fixture 目录固定包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
particle-fixtures/<fixture-id>/
|
||||||
|
├─ metadata.json
|
||||||
|
├─ field.f32
|
||||||
|
└─ mask.u8 仅 OCEAN_ONLY/Mask 场景必需
|
||||||
|
```
|
||||||
|
|
||||||
|
`metadata.json` 最小结构:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema": "maplibre-particle-fixture/v1",
|
||||||
|
"fixtureId": "uniform-east",
|
||||||
|
"fieldType": "WIND",
|
||||||
|
"displayDomain": "GLOBAL",
|
||||||
|
"dataVersion": 1,
|
||||||
|
"timeIndex": 0,
|
||||||
|
"timestamp": null,
|
||||||
|
"crs": "EPSG:4326",
|
||||||
|
"bounds": { "west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0 },
|
||||||
|
"grid": {
|
||||||
|
"width": 64,
|
||||||
|
"height": 32,
|
||||||
|
"registration": "node",
|
||||||
|
"origin": "north-west",
|
||||||
|
"rowOrder": "north-to-south",
|
||||||
|
"columnOrder": "west-to-east"
|
||||||
|
},
|
||||||
|
"vector": {
|
||||||
|
"uPositive": "east",
|
||||||
|
"vPositive": "north",
|
||||||
|
"unit": "m/s",
|
||||||
|
"layout": "interleaved-uv",
|
||||||
|
"encoding": "float32-le",
|
||||||
|
"noData": -9999.0,
|
||||||
|
"file": "field.f32"
|
||||||
|
},
|
||||||
|
"mask": { "mode": "none" },
|
||||||
|
"integrity": {
|
||||||
|
"fieldByteLength": 16384,
|
||||||
|
"fieldSha256": "<sha256>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
实际 width/height 和 checksum 由 fixture 生成任务填写,上例数值只展示 contract,不冻结具体场景分辨率。
|
||||||
|
|
||||||
|
### 9.2 Binary 规则
|
||||||
|
|
||||||
|
- `field.f32`:little-endian IEEE-754 float32,row-major,按 `u0,v0,u1,v1...` 交错;文件长度必须等于 `width × height × 2 × 4`。
|
||||||
|
- 任一 cell 的 U 或 V 等于 `noData`,整个 cell 无效;不依赖 NaN payload 表达 NoData。
|
||||||
|
- adapter 校验有限值、长度、checksum、bounds、grid 和 domain 后,转换为 canonical immutable Field。
|
||||||
|
- `mask.u8`:row-major、与 metadata 指定 grid 对齐;`0=LAND`、`255=OCEAN`,`1..254` 为未来 coverage/shore antialias 保留,v1 validator 不接受未声明的中间值。
|
||||||
|
- Mask 文件长度必须等于 mask width × height,并记录 byte length/SHA256。
|
||||||
|
- v1 fixture 的 `bounds.west < bounds.east`,单文件不跨反经线;全球使用 `[-180,180]`。运行时 Field/Mask 模型仍支持以 unwrapped longitude interval 表达未来反经线数据。
|
||||||
|
- `timeIndex` 必须存在;有真实时间语义时同时填写 UTC ISO-8601 `timestamp`,确定性 synthetic fixture 可令其为 `null`。
|
||||||
|
|
||||||
|
### 9.3 Mask metadata
|
||||||
|
|
||||||
|
OCEAN_ONLY 场景把 `mask` 写为:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mode": "embedded",
|
||||||
|
"maskId": "coast-v1",
|
||||||
|
"dataVersion": 1,
|
||||||
|
"file": "mask.u8",
|
||||||
|
"encoding": "uint8-land-ocean",
|
||||||
|
"width": 64,
|
||||||
|
"height": 32,
|
||||||
|
"registration": "node",
|
||||||
|
"origin": "north-west",
|
||||||
|
"noDataPolicy": "land"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.4 预留 fixture 覆盖
|
||||||
|
|
||||||
|
| fixture | 合同目标 |
|
||||||
|
|---|---|
|
||||||
|
| `uniform-east` | WIND/GLOBAL,常量 +U,验证东西向和平移/wrap |
|
||||||
|
| `uniform-north` | WIND/GLOBAL,常量 +V,验证纬向和 projection |
|
||||||
|
| `vortex` | WIND/GLOBAL 或明确区域 bounds,验证旋转方向和采样连续性 |
|
||||||
|
| `shear` | WIND/GLOBAL 或区域 bounds,验证速度梯度和 timestep |
|
||||||
|
| `coastal-mask` | CURRENT/OCEAN_ONLY,必须含 mask,验证海岸裁剪和 fail-closed |
|
||||||
|
|
||||||
|
后续 fixture 可增加 west/south、双向 vortex、divergence/convergence,但不改变 v1 contract。
|
||||||
|
|
||||||
|
## 10. Offline Fixture 到 GPU 数据流
|
||||||
|
|
||||||
|
```text
|
||||||
|
Offline Fixture files
|
||||||
|
→ Worker: read / checksum / decode / validate
|
||||||
|
→ ParticleFieldSnapshot(shared immutable, revision N)
|
||||||
|
→ Map/Style thread: RasterArraySource Impl snapshot
|
||||||
|
→ RendererFrontend immutable UpdateParameters
|
||||||
|
→ Render thread: compare revision
|
||||||
|
→ UploadPass: velocity texture + optional mask texture
|
||||||
|
→ ParticleState A/B initialization
|
||||||
|
→ Simulation pass
|
||||||
|
→ Particle draw + Trail A/B
|
||||||
|
→ RasterParticleLayer translucent composite
|
||||||
|
→ MapLibre main render pass → GPU
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.1 低频步骤
|
||||||
|
|
||||||
|
fixture I/O、checksum、decode、unit normalization、Field validation、CPU snapshot 创建、velocity/mask upload、shader/resource 创建。仅在 source revision、mask revision、context generation、viewport target size或影响资源布局的选项变化时执行。
|
||||||
|
|
||||||
|
### 10.2 每帧步骤
|
||||||
|
|
||||||
|
读取当前 frame transform;计算受限 timestep;state A→B 或 B→A simulation;particle draw;trail fade/accumulate;final composite;更新 rendering stats;在仍活动时声明下一帧 repaint。
|
||||||
|
|
||||||
|
### 10.3 Camera 变化时
|
||||||
|
|
||||||
|
更新 transform、pixel ratio、viewport、visible field intersection、world-wrap selection 等 uniform。v1 不重建 velocity texture,不重新解析 Field,不重建 Particle State;viewport resize 只重建/清空 trail target。任何 pan/zoom/bearing/pitch/resize 的有效变化清空旧 screen-space trail,避免错误残影;后续可验证 trail reprojection,但不属于 v1。
|
||||||
|
|
||||||
|
## 11. 线程模型
|
||||||
|
|
||||||
|
```text
|
||||||
|
Map/UI thread
|
||||||
|
├─ 创建/删除 Source、Layer
|
||||||
|
├─ 修改业务 options、enable、source/mask reference
|
||||||
|
└─ 发布 immutable Impl/snapshot revision
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Worker thread
|
||||||
|
├─ fixture I/O、decode、validate、unit normalize
|
||||||
|
└─ 产出 shared_ptr<const ParticleFieldSnapshot>
|
||||||
|
│ ownership/immutable snapshot
|
||||||
|
▼
|
||||||
|
Render thread
|
||||||
|
├─ snapshot revision diff
|
||||||
|
├─ GPU resource create/upload/release/recreate
|
||||||
|
├─ 当前 frame Camera/Matrix
|
||||||
|
└─ encode simulation/trail/draw/composite
|
||||||
|
▼
|
||||||
|
GPU
|
||||||
|
└─ velocity sample、particle update、trail、rasterization
|
||||||
|
```
|
||||||
|
|
||||||
|
- Field 解码:Style 所属 worker/thread pool 或独立受控 worker;不得在 render frame 内 decode。
|
||||||
|
- CPU 插值:只用于 validator、测试参考或查询 API;正式每帧 field sampling 在 GPU。
|
||||||
|
- GPU resource 创建/释放:持有有效 `gfx::BackendScope` 的 render thread。
|
||||||
|
- 每帧 simulation:render thread 编码,GPU 执行。
|
||||||
|
- Camera Matrix:当前 `PaintParameters.transformParams/state`。
|
||||||
|
- Data 交接:`shared_ptr<const ParticleFieldSnapshot>`/MapLibre Immutable snapshot + monotonic revision;render thread 原子替换当前引用后再上传。
|
||||||
|
- 避免复制:decode 只产生一次 owning buffer;source/render snapshot 共享 const ownership;只在 GPU revision 变化时上传,不通过 Java/ObjC/JS 每帧传大数组。
|
||||||
|
- context loss:平台/backend 通知 Core;render thread 丢弃 GPU generation,CPU snapshot 保留;新 context 下 render thread 重建。
|
||||||
|
|
||||||
|
## 12. Camera / Matrix 同帧同步
|
||||||
|
|
||||||
|
```text
|
||||||
|
Map camera mutation
|
||||||
|
→ TransformState snapshot in UpdateParameters
|
||||||
|
→ RenderTree TransformParameters
|
||||||
|
→ PaintParameters for current frame
|
||||||
|
├─ state: center/zoom/bearing/pitch/size/frustum
|
||||||
|
├─ proj/aligned/near-clipped matrix
|
||||||
|
└─ pixel ratio/frame count/time
|
||||||
|
→ ParticleFrameParameters
|
||||||
|
→ simulation visible domain + particle draw/composite
|
||||||
|
```
|
||||||
|
|
||||||
|
规则:
|
||||||
|
|
||||||
|
- pan/zoom/bearing/pitch 全部使用当前 render frame snapshot;不接受 UI/JS 另行推算的 Camera。
|
||||||
|
- position state 存储与 Camera 无关的 Field-normalized/geographic 语义位置;绘制时转为 canonical Web Mercator/world coordinate,再使用当前 frame matrix。
|
||||||
|
- resize/pixel ratio 改变更新 viewport uniform,并重建 trail target;state 和 velocity texture保持。
|
||||||
|
- GLOBAL field 的 longitude 在 canonical interval 内 wrap;绘制时选择距离当前 map center 最近的 unwrapped world copy。
|
||||||
|
- 区域 Field 以 descriptor 的 unwrapped longitude interval 判断范围;v1 fixture 不跨反经线,运行时 contract 保留跨反经线能力。
|
||||||
|
- projection/unprojection 只复用 MapLibre Core utility/transform,不在平台层复制公式。
|
||||||
|
- v1 Camera 任意有效变化清空 screen-space trail;粒子位置继续保留并以新 transform 绘制。
|
||||||
|
|
||||||
|
## 13. 帧驱动与动画时钟
|
||||||
|
|
||||||
|
唯一 frame driver 是 MapLibre render loop。
|
||||||
|
|
||||||
|
1. enable 或资源 ready 时,Controller 通过现有 Core invalidate/`triggerRepaint` 路径唤醒第一帧。
|
||||||
|
2. `RENDERING` 且可见、Field/GPU ready 时,Particle Render 在当前 RenderTree 中声明 `needsRepaint=true`,使 MapLibre 请求下一帧。
|
||||||
|
3. 地图静止但粒子活动时,以上机制持续;不依赖 camera transition。
|
||||||
|
4. disable、Layer 不可见、App background、View 不可见、Field 不可用、ERROR 或 layer removal 时停止额外 repaint。
|
||||||
|
5. App foreground/context restore 后,只有资源重新 READY 且 layer enabled/visible 才恢复。
|
||||||
|
6. simulation time 使用 monotonic render time;采用固定逻辑步长加有界 accumulator,过长 pause 后不追赶所有丢失步,避免恢复瞬间爆炸。
|
||||||
|
|
||||||
|
平台 `requestRender`/CADisplayLink 只是现有 MapLibre frontend 的落地机制,不形成第二套粒子时钟。
|
||||||
|
|
||||||
|
## 14. GPU 粒子状态模型
|
||||||
|
|
||||||
|
### 14.1 冻结的逻辑状态
|
||||||
|
|
||||||
|
每个 particle 逻辑上至少包含:
|
||||||
|
|
||||||
|
- `position`:Field-normalized 2D position,可无损映射到 descriptor bounds;与屏幕 Camera 无关。
|
||||||
|
- `age`:秒或归一化寿命语义。
|
||||||
|
- `seed`:稳定随机/respawn 输入。
|
||||||
|
- `alive/valid`:可由 age/特殊值派生,不要求独立 channel。
|
||||||
|
|
||||||
|
Exact texture format、channel packing 和最终最大 particle count **不在本阶段冻结**;它们必须在 Camera correctness 和 backend capability 验证后选择。
|
||||||
|
|
||||||
|
### 14.2 Ping-pong 与 simulation
|
||||||
|
|
||||||
|
- `Particle State A/B` 同尺寸,当前帧只读 current、只写 next,帧末交换。
|
||||||
|
- texture texel count 至少覆盖 particle count;多余 texel 标记 inactive。
|
||||||
|
- velocity texture 保存 canonical U/V 与 validity;shader 按 descriptor 将物理速度转换为 Field-normalized displacement。
|
||||||
|
- timestep 使用受限固定步长;speed factor 是业务参数,不能改变原始 Field snapshot。
|
||||||
|
- Field bounds 外、NoData、age 到期或 OCEAN_ONLY mask 非 ocean 时执行 deterministic respawn。
|
||||||
|
- respawn domain 优先取 Field bounds 与当前可见 geographic bounds 的有效交集;交集为空时不绘制且不忙循环重建资源。
|
||||||
|
- viewport clipping 只影响 draw,不以离开屏幕立即销毁 state;region/domain 无效时才 respawn。
|
||||||
|
|
||||||
|
### 14.3 为什么第一阶段不用 compute 作为前置
|
||||||
|
|
||||||
|
当前目标同时覆盖 Android OpenGL/Vulkan 与后续 iOS Metal,而现有跨 backend 最稳定的共同语义是 render pass、texture、render target 和 shader registry。第一阶段以 full-screen/texture render-pass simulation 验证主架构,避免先冻结 Vulkan/Metal compute pipeline、workgroup 和 barrier contract。
|
||||||
|
|
||||||
|
后续 compute 只能作为 `ParticleSimulationBackend` 的 capability-gated 实现;逻辑 state、Field、Mask、Controller 和公共 API 不变。无 compute 时继续使用 render-pass simulation,不回退到正式 CPU 每粒子循环。
|
||||||
|
|
||||||
|
## 15. Trail 架构
|
||||||
|
|
||||||
|
第一阶段正式视觉范围包含 trail,但最小验证顺序允许先验证无 trail 粒子点/短线,再增加 trail gate。
|
||||||
|
|
||||||
|
```text
|
||||||
|
Trail Current texture
|
||||||
|
→ Fade pass → Trail Next
|
||||||
|
→ Particle draw blend into Trail Next
|
||||||
|
→ Composite Trail Next at RasterParticleLayer position
|
||||||
|
→ swap Current/Next
|
||||||
|
```
|
||||||
|
|
||||||
|
- 使用 screen-space Trail A/B,避免依赖所有 backend 对同一 attachment 同时读写的行为。
|
||||||
|
- accumulation/fade 与 particle draw 是独立 pass;最终 composite 使用 premultiplied alpha contract。
|
||||||
|
- resize/pixel ratio/render target scale 变化:重建并 clear A/B。
|
||||||
|
- pan/zoom/bearing/pitch/frustum 有效变化:v1 clear A/B,不错误复用旧屏幕轨迹。
|
||||||
|
- Field/mask revision、simulation reset、disable、style reload、context loss、App background、ERROR、layer removal:clear 或释放。
|
||||||
|
- opacity/trail fade 的纯样式变化可从下一帧生效;是否清空由 property 语义明确,改变 fade 不强制清空,改变坐标/particle size 可清空。
|
||||||
|
- context loss 后不恢复旧 trail 内容,只重建空 target。
|
||||||
|
|
||||||
|
## 16. Land/Ocean Mask 设计
|
||||||
|
|
||||||
|
### 16.1 数据与资源
|
||||||
|
|
||||||
|
Mask 数据属于独立 `ParticleMask` 模块。CPU 层是 `LandOceanMaskSnapshot`:mask id/revision、CRS、bounds、grid、registration、coverage policy 和 immutable byte data。GPU 层是按 `(maskId, revision, backend context generation)` 缓存的 texture,可被多个 OCEAN_ONLY Layer 共享。
|
||||||
|
|
||||||
|
第一阶段使用 fixture `mask.u8` GPU texture;CPU geometry 只允许作为未来 mask 生产/测试输入,不在每帧做 point-in-polygon。未来可扩展 tiled/pyramid mask,但保持同一 snapshot/sample contract。
|
||||||
|
|
||||||
|
### 16.2 坐标、分辨率和采样
|
||||||
|
|
||||||
|
- CRS 为 EPSG:4326,bounds/grid registration 与 Field contract 相同。
|
||||||
|
- fixture v1 mask 默认与对应 Field 同 bounds/width/height;架构允许独立分辨率,shader 使用各自 transform。
|
||||||
|
- binary 只接受 LAND/OCEAN;未知/NoData 对 OCEAN_ONLY 按 LAND 处理。
|
||||||
|
- v1 采用 nearest/明确阈值采样,避免线性过滤把海值渗入陆地。未来 coverage mask 可显式声明 coverage encoding 后再允许平滑。
|
||||||
|
- zoom 不触发 mask 重建;未来 pyramid 只在 LOD 变化时切换 resource revision。
|
||||||
|
- bearing/pitch 不改变 mask sample:particle 在 geographic/Field space 采样,再与地图使用同帧 transform 绘制。
|
||||||
|
- GLOBAL Wind 完全绕过 mask branch/resource requirement。
|
||||||
|
|
||||||
|
### 16.3 wrap、反经线与海岸 artifact
|
||||||
|
|
||||||
|
- 全球 mask longitude 周期 repeat;区域 mask 使用 unwrapped interval。
|
||||||
|
- 反经线数据拆分或使用 unwrapped bounds,不能用 `west > east` 的模糊单文件语义。
|
||||||
|
- 海岸 artifact 风险包括 mask 分辨率不足、Field 与 Mask registration 不同、nearest 锯齿、trail 残留和海图海岸语义差异。
|
||||||
|
- v1 通过严格 metadata validation、nearest sample、未知即陆地、Camera 变化清 trail 和 coastal fixture 控制;最终生产 Mask 来源/分辨率留待后续数据设计与 NavSea 语义 Gate。
|
||||||
|
|
||||||
|
### 16.4 更新、缓存和恢复
|
||||||
|
|
||||||
|
- 仅 mask id/revision/context generation 改变时重新 upload;Camera 和每帧 animation 不重建。
|
||||||
|
- CPU snapshot 可跨 context 保留;GPU texture 由 render thread 在新 context 重建。
|
||||||
|
- mask 创建失败时 OCEAN_ONLY layer 进入 ERROR/不绘制,不能降级为 GLOBAL。
|
||||||
|
|
||||||
|
## 17. Land/Ocean Mask 共用图
|
||||||
|
|
||||||
|
```text
|
||||||
|
LandOceanMaskSnapshot(maskId, revision)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Shared ParticleMaskCache
|
||||||
|
├─ CPU immutable bitmap
|
||||||
|
└─ GPU mask texture per context generation
|
||||||
|
│
|
||||||
|
┌──────┼─────────┬──────────────┐
|
||||||
|
▼ ▼ ▼ ▼
|
||||||
|
Wave Tide Current Future OCEAN_ONLY
|
||||||
|
|
||||||
|
Wind/GLOBAL ─────────────────────→ bypass Mask
|
||||||
|
```
|
||||||
|
|
||||||
|
## 18. 生命周期状态机
|
||||||
|
|
||||||
|
```text
|
||||||
|
DISABLED
|
||||||
|
└─ enable → WAITING_MAP
|
||||||
|
WAITING_MAP
|
||||||
|
├─ map/render context ready, no field → WAITING_FIELD
|
||||||
|
└─ disable/remove/destroy → DISABLED
|
||||||
|
WAITING_FIELD
|
||||||
|
├─ valid field snapshot → PREPARING
|
||||||
|
└─ invalid field → ERROR
|
||||||
|
PREPARING
|
||||||
|
├─ GPU resources ready → READY
|
||||||
|
├─ context lost → CONTEXT_LOST
|
||||||
|
└─ resource/shader failure → ERROR
|
||||||
|
READY
|
||||||
|
├─ enabled + visible + foreground → RENDERING
|
||||||
|
├─ background/invisible → PAUSED
|
||||||
|
└─ new field/mask revision → PREPARING
|
||||||
|
RENDERING
|
||||||
|
├─ background/invisible → PAUSED
|
||||||
|
├─ context lost → CONTEXT_LOST
|
||||||
|
├─ new resource revision → PREPARING
|
||||||
|
├─ disable/remove → DISABLED
|
||||||
|
└─ failure → ERROR
|
||||||
|
PAUSED
|
||||||
|
├─ foreground/visible, resources valid → READY → RENDERING
|
||||||
|
├─ context lost → CONTEXT_LOST
|
||||||
|
└─ disable/remove/destroy → DISABLED
|
||||||
|
CONTEXT_LOST
|
||||||
|
├─ new context + CPU snapshot valid → PREPARING
|
||||||
|
└─ disable/remove/destroy → DISABLED
|
||||||
|
ERROR
|
||||||
|
├─ valid new field/options/manual retry/context generation → PREPARING
|
||||||
|
└─ disable/remove/destroy → DISABLED
|
||||||
|
```
|
||||||
|
|
||||||
|
责任:
|
||||||
|
|
||||||
|
- Map/Style thread 管理 Layer/Source 存在性、enable 和 immutable revision。
|
||||||
|
- Worker 管理 decode job 的取消和 CPU snapshot。
|
||||||
|
- Render thread 管理所有 GPU resource 和 context generation。
|
||||||
|
- GPU 不拥有跨 Map 生命周期的资源责任。
|
||||||
|
- Style reload 删除旧 RenderLayer 时先停止 repaint,再释放 GPU;同 id 新 layer 按新实例从 WAITING_FIELD/PREPARING 开始。
|
||||||
|
- Map destroy/layer removal 是终止边界:取消 worker callback、断开 observer、释放 render resources,不自动复活。
|
||||||
|
|
||||||
|
## 19. 每帧渲染链图
|
||||||
|
|
||||||
|
```text
|
||||||
|
MapLibre requests frame
|
||||||
|
→ RenderTree prepared with current TransformState
|
||||||
|
→ RenderRasterParticleLayer checks state/revisions
|
||||||
|
→ Upload only if Field/Mask/resource revision changed
|
||||||
|
→ Simulation pass: State Current → State Next
|
||||||
|
→ Trail fade: Trail Current → Trail Next
|
||||||
|
→ Draw particles into Trail Next (Mask/domain applied)
|
||||||
|
→ Main translucent pass: composite Trail Next by layer index
|
||||||
|
→ present
|
||||||
|
→ active? needsRepaint=true : stop extra repaint
|
||||||
|
```
|
||||||
|
|
||||||
|
## 20. Android 薄接入
|
||||||
|
|
||||||
|
```text
|
||||||
|
Kotlin/Java Source + Layer API
|
||||||
|
→ typed JNI peer (no raw host pointer)
|
||||||
|
→ Core RasterArraySource / RasterParticleLayer
|
||||||
|
→ RenderRasterParticleLayer / mbgl::particle / gfx
|
||||||
|
→ Android OpenGL or Vulkan backend
|
||||||
|
→ existing MapRenderer requestRender + Surface lifecycle
|
||||||
|
```
|
||||||
|
|
||||||
|
- Java/Kotlin 只表达 source/layer/options/status;不接收 texture id、不执行每帧 simulation、不持有 GPU resources。
|
||||||
|
- 正式接口需要类型安全 JNI wrapper;禁止把 `CustomLayer(String,long)` 的 raw `long CustomLayerHost` 作为粒子 API。
|
||||||
|
- SurfaceView/TextureView 的 surface/context recreation 统一转为 Core context generation/lifecycle;不在两种 View 内分别写 particle engine。
|
||||||
|
- OpenGL/Vulkan 差异留在 gfx shader/resource implementation;Core logical resources 和 pass graph 相同。
|
||||||
|
- repaint 复用 `Map::triggerRepaint` → `AndroidRendererFrontend` → `MapRenderer.requestRender`,平台不增加粒子 timer。
|
||||||
|
- 第一阶段 Android OpenGL 完成日常开发;Vulkan 在冻结前验证 state/trail/mask/context-loss,不另写业务逻辑。
|
||||||
|
|
||||||
|
## 21. iOS 薄接入
|
||||||
|
|
||||||
|
```text
|
||||||
|
ObjC/Swift Source + Layer peer
|
||||||
|
→ Core RasterArraySource / RasterParticleLayer
|
||||||
|
→ RenderRasterParticleLayer / mbgl::particle / gfx
|
||||||
|
→ OpenGL or Metal backend
|
||||||
|
→ existing MLNMapView RendererFrontend / CADisplayLink / draw callback
|
||||||
|
```
|
||||||
|
|
||||||
|
- `MLNCustomStyleLayer`、`MLNCustomDrawableStyleLayer` 和 `MLNBackendResource` 是已确认能力与验证参考,不是正式粒子引擎所有者。
|
||||||
|
- 正式 iOS API 应是 Core Source/Layer 的 ObjC peer;ObjC/Swift 不保存 particle state、不编写第二套 simulation。
|
||||||
|
- CADisplayLink 继续驱动 MapLibre view;粒子通过 Core `needsRepaint` 使 MapLibre 保持活动,不添加独立 display link。
|
||||||
|
- Metal/OpenGL resource 与 encoder 只在 gfx/backend 内可见;平台 wrapper 不暴露给业务 API。
|
||||||
|
- iOS 最终 backend 和设备能力在后续专项验证;不能据当前源码宣称 Metal/OpenGL 已通过粒子 gate。
|
||||||
|
|
||||||
|
## 22. 最小公共 API 设计
|
||||||
|
|
||||||
|
以下为语义 API,不冻结具体 C++/Java/ObjC 方法签名:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RasterArraySource(id)
|
||||||
|
setField(ParticleFieldSnapshot)
|
||||||
|
clearField()
|
||||||
|
getRevision()/getDescriptor()
|
||||||
|
|
||||||
|
RasterParticleLayer(id, sourceId)
|
||||||
|
setEnabled(bool)
|
||||||
|
setDisplayDomain(DisplayDomain) 受 FieldType 固定规则校验
|
||||||
|
setParticleOptions(ParticleOptions)
|
||||||
|
setMaskId(optional maskId) OCEAN_ONLY 必需
|
||||||
|
getParticleOptions()
|
||||||
|
getStatus()/getLastError()
|
||||||
|
|
||||||
|
Style
|
||||||
|
add/remove RasterArraySource
|
||||||
|
add/remove RasterParticleLayer
|
||||||
|
observe ParticleStatus changes 低频状态,不是每帧 telemetry
|
||||||
|
```
|
||||||
|
|
||||||
|
数据更新通过 Source revision,而不是把 field 数组设为 Layer paint property。公共 API 禁止 GL texture id、VkImage、MTLTexture、command encoder、backend workgroup、raw native pointer。
|
||||||
|
|
||||||
|
Layer display domain 必须符合业务规则;例如尝试把 CURRENT 改为 GLOBAL 返回 validation error,不静默放宽。
|
||||||
|
|
||||||
|
## 23. 配置分层
|
||||||
|
|
||||||
|
### 23.1 稳定业务参数
|
||||||
|
|
||||||
|
- particle count(目标值,受 capability/memory clamp)
|
||||||
|
- speed factor
|
||||||
|
- lifetime
|
||||||
|
- opacity
|
||||||
|
- particle size/line width
|
||||||
|
- trail enabled
|
||||||
|
- trail fade
|
||||||
|
- color ramp/reference
|
||||||
|
- enabled/visibility
|
||||||
|
|
||||||
|
业务参数具有 backend-independent 范围、默认值和 validation;具体默认值在最小验证后冻结。
|
||||||
|
|
||||||
|
### 23.2 内部 backend 参数
|
||||||
|
|
||||||
|
- state/velocity/trail texture format
|
||||||
|
- buffer alignment、binding index
|
||||||
|
- render target channel type/scale
|
||||||
|
- shader variant/defines
|
||||||
|
- compute workgroup/barrier(未来)
|
||||||
|
- upload staging、sampler/filter 细节
|
||||||
|
|
||||||
|
这些由 capability profile 与 gfx implementation 决定,不能出现在产品业务 API 或 fixture contract。
|
||||||
|
|
||||||
|
## 24. 错误与降级策略
|
||||||
|
|
||||||
|
| 情况 | 行为 | 重试 |
|
||||||
|
|---|---|---|
|
||||||
|
| metadata/binary/checksum/Field 无效 | Source 拒绝新 revision;Layer 保留最后一个有效 snapshot或进入 WAITING_FIELD/ERROR | 仅新数据 revision |
|
||||||
|
| Field 与 viewport 无交集 | READY 但不绘制、不持续空忙 repaint | Camera/Field 改变时恢复 |
|
||||||
|
| OCEAN_ONLY Mask 缺失/无效 | fail closed,Layer ERROR;地图继续 | 有效 mask revision |
|
||||||
|
| GPU resource 创建失败 | 释放本次部分资源,Layer ERROR;其他 Layer 不受影响 | 一次受控低质量 profile 可选,否则人工/新配置 |
|
||||||
|
| backend 不支持必要 render target/format | Layer ERROR/UNSUPPORTED;不回退 CPU 正式粒子 | backend capability 变化或实现更新 |
|
||||||
|
| context loss | 停止 repaint,丢弃 GPU generation,保留 CPU snapshot,进入 CONTEXT_LOST | 新 context 自动 PREPARING |
|
||||||
|
| shader 创建失败 | 释放 particle shader/resource,Layer ERROR | 新 context/shader revision 时一次 |
|
||||||
|
| memory pressure | 先释放 trail、闲置 cache;允许按内部 profile 降低 trail scale/particle count并记录状态 | 有界一次;仍失败则 ERROR |
|
||||||
|
| Layer removal/Map destroy | 取消任务、停止 callback/repaint、释放 GPU/CPU ownership | 不自动重试 |
|
||||||
|
|
||||||
|
任何错误不得使 MapLibre Map、其他 Style Layer 或平台 View 崩溃。自动重试必须按 revision/context generation 去重,禁止每帧重试形成死循环。
|
||||||
|
|
||||||
|
## 25. 性能预算与后续测量
|
||||||
|
|
||||||
|
以下是设计目标,不是已验证结论。正式 PASS 必须绑定具体设备、分辨率、backend、build type、fixture 和 particle count。
|
||||||
|
|
||||||
|
### 25.1 第一阶段 Gate 指标
|
||||||
|
|
||||||
|
| 指标 | 设计目标/测量档位 |
|
||||||
|
|---|---|
|
||||||
|
| particle count | 16K correctness、32K target、64K stress;最终默认/最大值待验证 |
|
||||||
|
| FPS/frame time | 声明设备上 32K 的目标下限 30 FPS/p95 ≤33.3ms;不得写成已达到 |
|
||||||
|
| Particle CPU time | 32K target p95 ≤3ms/frame,不含 MapLibre 基础渲染 |
|
||||||
|
| Particle GPU time | 32K target p95 ≤12ms/frame,分别记录 simulation/trail/composite |
|
||||||
|
| Particle GPU memory | 1080p、32K target ≤64MiB,含 state/trail/velocity/mask,不含 base map |
|
||||||
|
| Field upload | 1024×1024 U/V target ≤200ms,且只在 revision 变化时发生 |
|
||||||
|
| Context recreation | target ≤1s 恢复可绘制;旧 trail 不要求恢复 |
|
||||||
|
| correctness | pan/zoom/bearing/pitch/resize/wrap/mask/context loss 无崩溃、无明显错位 |
|
||||||
|
|
||||||
|
若没有声明的 baseline 设备,只能报告测量值,不能判定性能 PASS。第一阶段必须提供 CPU/GPU time、资源字节数、upload/recreate time 的 instrumentation;60 FPS、最终最大粒子数、最佳格式和厂商优化属于后续优化。
|
||||||
|
|
||||||
|
## 26. 后续最小技术验证清单
|
||||||
|
|
||||||
|
| 优先级 | 验证 | 是否阻塞正式实现 | 可能改变什么 | 最小方式 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | 独立 Core RenderLayer 在 Android OpenGL 中进入正确 Style order、读取同帧 transform并持续/停止 repaint | 是 | 主入口与 frame driver | 仅一层、单 fixture、固定少量 state 的 RenderTree probe;不做完整视觉 |
|
||||||
|
| 2 | gfx render-pass State A/B + Trail A/B 在 Android OpenGL/Vulkan 的创建、采样、交换、barrier和 context recreation | 是 | render-pass simulation 是否可作为共同基线、资源抽象边界 | 最小 ping-pong pattern + trail target,Surface recreate 后重建;记录两 backend |
|
||||||
|
| 3 | Camera/wrap/Mask contract | 是 | position semantic、trail clear 和 Mask sampling | uniform-east/north + coastal-mask,覆盖 pan/zoom/bearing/pitch/resize/反经线邻近视图 |
|
||||||
|
| 4 | iOS Core layer 薄 peer 与 Metal/OpenGL 同帧 gfx 资源可达性 | 不阻塞 Android 第一阶段;阻塞 iOS 实现 | iOS wrapper/backend adapter 细节,不得改变 Core engine | Mac/iOS 同 commit 的最小 Source/Layer/render-target probe |
|
||||||
|
|
||||||
|
Android raw `CustomLayerHost` 不再是正式路线的阻塞项,不专门扩展产品 API;若独立 Layer probe 需要对照,可运行现有 CustomLayer 示例,但其结果只作诊断。
|
||||||
|
|
||||||
|
## 27. 明确不采用的架构
|
||||||
|
|
||||||
|
| 不采用方案 | 原因 |
|
||||||
|
|---|---|
|
||||||
|
| React Native/JS 每帧粒子 | 当前仓库无 RN runtime;高频大数组/Camera/frame 跨桥;无法与 render frame 严格同步 |
|
||||||
|
| MapView 外部 Overlay | 独立 surface/frame/matrix/layer order;Camera/pitch/context lifecycle 易错;违反固定边界 |
|
||||||
|
| 每种海洋场各自一套 Mask | 数据重复、海岸语义分叉、缓存和 artifact 不一致;违反统一 Mask 决策 |
|
||||||
|
| 直接改造 RasterLayer 承担粒子 | 普通 RasterLayer 是静态 raster semantics;会污染官方层并混合数据、simulation、trail |
|
||||||
|
| Android/iOS 各写一套引擎 | 状态、Field、Mask、生命周期和算法会分叉;违反共享 C++ Core |
|
||||||
|
| CustomLayer 作为长期产品主宿主 | Android raw host 与 iOS context API 不对称,生命周期/API 难以稳定;不利于 Source/Layer/RenderTree 正式集成 |
|
||||||
|
| 每帧重建 Field texture | 造成 upload、allocation、driver 和内存抖动;Field 应按 revision 缓存 |
|
||||||
|
| 每帧大数组跨 Java/ObjC/JS | 复制、序列化和 GC/ARC 压力;只允许低频 immutable snapshot/revision |
|
||||||
|
| backend-specific GPU API 暴露给业务 | 破坏跨平台、测试和升级边界;handle 只能在 gfx/backend 内部 |
|
||||||
|
| 正式 CPU 每粒子循环 | 不满足 GPU Particle Engine 目标;CPU 只做 decode/validation/reference test |
|
||||||
|
| compute-only 第一版 | 当前跨 backend capability 未验证,会过早冻结 workgroup/barrier;先用 render-pass simulation |
|
||||||
|
|
||||||
|
## 28. Camera / Matrix 同步图
|
||||||
|
|
||||||
|
```text
|
||||||
|
UI/Native camera API
|
||||||
|
→ mbgl::Map / TransformState
|
||||||
|
→ immutable UpdateParameters
|
||||||
|
→ RenderTree TransformParameters
|
||||||
|
→ current PaintParameters
|
||||||
|
└→ ParticleFrameParameters
|
||||||
|
├→ visible Field intersection / world copy
|
||||||
|
├→ GPU simulation uniforms
|
||||||
|
└→ draw/composite matrix
|
||||||
|
|
||||||
|
Independent UI/JS camera guess ─X→ Particle Renderer
|
||||||
|
```
|
||||||
|
|
||||||
|
## 29. Core 模块边界图
|
||||||
|
|
||||||
|
```text
|
||||||
|
Style API
|
||||||
|
├─ RasterArraySource ─→ ParticleFieldSnapshot
|
||||||
|
├─ WeatherColorLayer ─→ RenderWeatherColorLayer
|
||||||
|
└─ RasterParticleLayer
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
RenderRasterParticleLayer ← current PaintParameters
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
mbgl::particle
|
||||||
|
├─ DataAdapter → Field
|
||||||
|
├─ Controller → lifecycle/repaint/revision
|
||||||
|
├─ Simulation → logical state/timestep/respawn
|
||||||
|
├─ Mask → shared domain filter
|
||||||
|
├─ GPUResources → velocity/state/trail/uniform
|
||||||
|
└─ Render → offscreen passes/composite
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
gfx/backend only
|
||||||
|
```
|
||||||
|
|
||||||
|
## 30. 架构冻结项
|
||||||
|
|
||||||
|
以下内容从本设计起冻结,后续普通实现 Step 不得自行改变:
|
||||||
|
|
||||||
|
1. 主路线是独立 Core `RasterParticleLayer/RenderRasterParticleLayer`,进入 Style/RenderTree/gfx;CustomLayer/CustomDrawable 不是正式宿主。
|
||||||
|
2. Android/iOS 共用 C++ `mbgl::particle` Field、Simulation、Render、GPU resource contract、Mask、Controller 和状态机;平台只有类型 peer、backend lifecycle 与 capability 薄接入。
|
||||||
|
3. `RasterArraySource` 是 Field 数据边界;Field snapshot immutable、revisioned、canonical U-east/V-north/SI unit。
|
||||||
|
4. WeatherColorLayer 与 RasterParticleLayer 是不同 Layer/RenderLayer/GPU 状态/动画生命周期,只共享数据 snapshot、grid/time metadata 和 Mask。
|
||||||
|
5. Wind=`GLOBAL`;Wave/Tide/Current=`OCEAN_ONLY`;OCEAN_ONLY 统一共享 Land/Ocean Mask,缺失时 fail closed。
|
||||||
|
6. fixture contract v1 使用 `metadata.json + field.f32 + optional/required mask.u8`、float32 little-endian interleaved UV、uint8 mask、checksum 和明确 grid/bounds/NoData。
|
||||||
|
7. frame driver 是 MapLibre render loop;active layer 贡献 `needsRepaint`,没有独立 UI/JS 主时钟。
|
||||||
|
8. Camera/Matrix 来自同一 render frame 的 Paint/Transform parameters;position state 与 Camera 无关。
|
||||||
|
9. GPU simulation 的第一共同基线是 render-pass State A/B ping-pong;正式 CPU 每粒子循环和 compute-only 第一版均拒绝。
|
||||||
|
10. 第一阶段正式视觉包含粒子、颜色映射和 screen-space Trail A/B;Camera 变化清 trail,不做 v1 trail reprojection。
|
||||||
|
11. GPU resource 只由 render thread/gfx 创建;context loss 保留 CPU snapshot、丢弃 GPU generation并重建空 trail。
|
||||||
|
12. Android 第一阶段主验证 OpenGL,冻结前必须做 Vulkan gfx gate;iOS OpenGL/Metal 使用同一 Core contract,后续 Mac/iOS 专项验证。
|
||||||
|
13. 公共 API 不暴露 backend handle、raw host pointer或每帧大数组;数据更新通过 Source immutable revision。
|
||||||
|
14. 下一阶段只执行第 26 节四项最小技术验证,不重新讨论总体架构。
|
||||||
|
|
||||||
|
## 31. 本阶段不得冻结的内容
|
||||||
|
|
||||||
|
- 已达到的 FPS、最终最大/default particle count。
|
||||||
|
- 最佳 state/velocity/trail texture format 和 channel packing。
|
||||||
|
- Vulkan/Metal compute workgroup、barrier 和厂商优化。
|
||||||
|
- 最终生产 Mask 来源、分辨率与 NavSea 海岸语义。
|
||||||
|
- 真实天气 Server/GRIB/厂商数据格式。
|
||||||
|
- NavSea/RN 最终 Host API 和产品桥接。
|
||||||
|
- 最终生产设备能力矩阵、iOS 最终 backend。
|
||||||
|
- Camera 验证前的最终 Particle State 物理 packing。
|
||||||
|
|
||||||
|
这些内容只能由后续最小技术验证、数据设计和产品 Gate 冻结。
|
||||||
|
|
||||||
|
## 32. 下一阶段判定
|
||||||
|
|
||||||
|
选择:**B,进入 `30_最小技术验证`。**
|
||||||
|
|
||||||
|
原因:独立 Core layer、render-pass ping-pong/trail 在 OpenGL/Vulkan 的实际资源行为、context recreation、同帧 Camera/wrap/Mask correctness 和 iOS backend 薄接入仍缺运行时证据。这些未知项会影响实现细节或架构可行性,不能直接进入正式实现;但它们已被压缩为四项最小验证,不需要重新开展总体架构讨论。
|
||||||
|
|
||||||
|
## 33. 本次设计边界
|
||||||
|
|
||||||
|
本阶段未创建 Source、Layer、Renderer、Shader、GPU resource、CustomLayerHost、JNI/ObjC 类或 fixture 数据;未修改生产源码、构建配置、依赖、lock、Step 或项目总则;未运行会写工程状态的构建/测试。
|
||||||
3
docs/10_架构/MapLibre粒子总体架构_v1.md
Normal file
3
docs/10_架构/MapLibre粒子总体架构_v1.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# MapLibre 粒子总体架构 v1
|
||||||
|
|
||||||
|
Layer:Base Map → WeatherColorLayer → RasterParticleLayer → Navigation Reference → AIS/Route/Ownship/Alarm。GPU 核心候选:Velocity Texture、Particle State A/B、Update、Draw、Trail Render Target、Color Ramp。
|
||||||
391
docs/20_调查/10_架构调查报告_v1.md
Normal file
391
docs/20_调查/10_架构调查报告_v1.md
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
# MapLibreParticleLab 架构调查报告 v1
|
||||||
|
|
||||||
|
调查日期:2026-08-29
|
||||||
|
|
||||||
|
调查分支:`particle/main`
|
||||||
|
调查性质:只读架构调查,不是粒子方案设计
|
||||||
|
|
||||||
|
## 1. 调查环境
|
||||||
|
|
||||||
|
- 仓库:`/mnt/sda1/MapLibreParticleLab`,根目录包含 `PARTICLE_BASELINE.md`、`include/`、`src/`、`platform/android/`、`platform/ios/`、`shaders/`、`particle-fixtures/` 和 `docs/`,确认是 MapLibreParticleLab。
|
||||||
|
- 主机基线:Ubuntu 24.04 x86_64;项目基线记录的 Java 17、Android API 34、NDK 28.2.13676358、构建并行度 3 已读取自 `PARTICLE_BASELINE.md` 与 `docs/00_项目规范/06_官方基线来源_v1.md`。
|
||||||
|
- 本次只执行文件搜索、源码阅读、配置阅读和 Git 查询;未运行构建、测试、依赖安装或会生成产物的命令。
|
||||||
|
|
||||||
|
## 2. Git、基线和规则读取
|
||||||
|
|
||||||
|
- 分支:`particle/main`。
|
||||||
|
- HEAD:`127de2271480000fcc8f390d81cf446e98757800`,最近提交主题为 `docs: freeze particle project governance v1`,时间为 2026-08-29 11:13:49 +08:00。
|
||||||
|
- 远端:`origin` 为本地 Gitea SSH 地址;`upstream` 为 MapLibre Native GitHub,只启用 fetch,push 显式为 `DISABLED`。
|
||||||
|
- 工作区:调查开始时无已跟踪修改;有一个未跟踪的当前任务书 `docs/40_任务/10_架构调查指令书_v1.md`。因此“工作区干净”检查结果为:`未完成`,原因是任务书本身在当前工作区未跟踪,非本次生成。
|
||||||
|
- 当前基线:Android `android-v13.4.1`、iOS `ios-v6.28.0`、共用 Core `18545ca10d296704b2cba675b182ff2c8da24588`。实际版本文件为 `platform/android/VERSION` 和 `platform/ios/VERSION`。
|
||||||
|
- 已完整读取:根目录 `AGENTS.md`;`PARTICLE_BASELINE.md`;`docs/00_项目总则/` 下当前四份总则;`docs/00_项目规范/` 下当前六份规范及官方基线来源;当前 `STEP-000` 记录和报告;`docs/40_任务/README.md`;本任务书;调查、Step、报告 README;现有 `docs/10_架构/MapLibre粒子总体架构_v1.md`;`粒子开发记录.md`。
|
||||||
|
- 本次未修改 `docs/00_项目总则/`、`docs/00_项目规范/`、生产代码、实验源码、构建配置或依赖。
|
||||||
|
|
||||||
|
## 3. 当前项目整体架构
|
||||||
|
|
||||||
|
### 3.1 工程形态
|
||||||
|
|
||||||
|
当前仓库不是 React Native、Expo 或 Bare RN 产品工程。根 `package.json` 的名称是 `maplibre-native`,脚本只有样式规范复制;`tsconfig.json` 只包含 `.github/scripts/*.ts`、`scripts/**/*.mjs` 和声明文件,并设置 `noEmit`。仓库没有业务页面、React 组件、`react-native`/Expo 依赖、Metro 配置或 Babel 配置。
|
||||||
|
|
||||||
|
因此任务书中的“业务/页面 → RN Map 组件 → RN 封装”在当前仓库不存在,不能据此补画一条虚构调用链。这里的实际入口是各平台 MapLibre SDK 示例、测试应用或直接使用 Native C++ API 的宿主。
|
||||||
|
|
||||||
|
### 3.2 主要目录
|
||||||
|
|
||||||
|
```text
|
||||||
|
MapLibreParticleLab/
|
||||||
|
├── include/mbgl/ Core 对外 C++ API
|
||||||
|
├── src/mbgl/ Core 实现:Map、Style、Source、Render、gfx、线程
|
||||||
|
├── platform/android/ Android SDK、JNI、Java/Kotlin View、C++ backend、测试 App
|
||||||
|
├── platform/ios/ iOS SDK、MLNMapView、OpenGL/Metal backend、示例和测试
|
||||||
|
├── platform/darwin/ Darwin 公共绑定、CustomStyleLayer 和 CustomDrawableLayer
|
||||||
|
├── platform/node/ Node binding 与 Node 测试,不是 RN UI 宿主
|
||||||
|
├── shaders/ MapLibre 内置 GLSL/生成清单;无粒子 shader
|
||||||
|
├── particle-fixtures/ 第一阶段离线粒子场/Mask fixture 占位目录
|
||||||
|
├── render-test/ test/ 渲染测试、API 测试、fixture 和服务器测试
|
||||||
|
├── scripts/ 样式代码、设备农场和工程脚本
|
||||||
|
├── docs/ 总则、架构、任务、调查、Step、报告
|
||||||
|
├── vendor/ 第三方源码子模块
|
||||||
|
└── CMakeLists.txt、Makefile、bazel/、platform 构建文件
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 入口和边界
|
||||||
|
|
||||||
|
- Core 入口是 `mbgl::Map`(`include/mbgl/map/map.hpp`),通过构造时注入 `RendererFrontend` 和 `MapObserver`。
|
||||||
|
- Android 入口是 `MapView`(`platform/android/MapLibreAndroid/src/main/java/.../MapView.java`),内部创建 `MapRenderer`、`NativeMapView` 和 Java/C++ JNI peer。
|
||||||
|
- iOS 入口是 `MLNMapView : UIView`(`platform/ios/src/MLNMapView.h`),内部由 `MLNMapViewImpl` 连接 Core `Map` 和 OpenGL/Metal View。
|
||||||
|
- 没有项目自有 `assets/` 业务资源目录;`test/fixtures` 是 MapLibre 测试资源,`particle-fixtures` 目前仅包含 README 和五个 `.gitkeep`。
|
||||||
|
- 现有 `build-linux-opengl`、`build-particle` 等是工作区中的构建目录,不视为源码模块。
|
||||||
|
|
||||||
|
## 4. 依赖与版本
|
||||||
|
|
||||||
|
| 类别 | 当前可确认内容 | 证据/结论 |
|
||||||
|
|---|---|---|
|
||||||
|
| JavaScript 直接依赖 | `@maplibre/maplibre-gl-style-spec`、`simple-git`、`minimatch`、`npm-run-all`、GitHub Actions 等 | 根 `package.json`;不是地图 UI 依赖 |
|
||||||
|
| JavaScript 开发依赖 | TypeScript、EJS、Express、AWS SDK、MVT/style fixtures 等 | 用于脚本、测试、设备农场和代码生成 |
|
||||||
|
| RN/React/Expo | 未找到 `react`、`react-native`、Expo、RN MapLibre 包 | 当前仓库没有 RN 产品层 |
|
||||||
|
| Skia/Reanimated/WebGL JS | 未找到项目直接依赖 | 不能假定存在 |
|
||||||
|
| Android SDK | `platform/android/VERSION=13.4.1`;`compileSdk=34`、`minSdk=23`;OpenGL/Vulkan/WebGPU flavor;Java SDK 直接依赖 GeoJSON、gestures 等 | `platform/android/MapLibreAndroid/build.gradle.kts` |
|
||||||
|
| Android Core | `libmaplibre` 由本仓库 C++ Core 构建并通过 JNI/Prefab 接入,不是另一个 RN 包 | Android CMake、JNI 和 Prefab 配置 |
|
||||||
|
| iOS SDK | `platform/ios/VERSION=6.28.0`;Podspec 通过 `ios-v#{version}` release xcframework;iOS deployment target 12 | `platform/ios/MapLibre.podspec` |
|
||||||
|
| iOS backend | 当前源码含 OpenGL、Metal、WebGPU 条件路径;当前项目基线将 OpenGL 作为主线、Metal 作为后续验证方向 | `MLNMapView+Impl.mm` 和各 backend 文件 |
|
||||||
|
| lock 文件 | 根 `package-lock.json` lockfile v3、`pnpm-lock.yaml` lockfile v9;锁定 TypeScript 5.9.3 等脚本依赖 | 未安装/更新 |
|
||||||
|
|
||||||
|
需要区分:`package.json` 的版本 `5.4.0` 是 Native 仓库/脚本包版本;Android/iOS SDK 版本分别由各平台 `VERSION` 决定,不能把三者合并为一个 RN 版本。
|
||||||
|
|
||||||
|
## 5. MapLibre 实际接入链
|
||||||
|
|
||||||
|
当前无 RN 绑定,因此实际链路分为 Android SDK、iOS SDK 和直接 Core 三条:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android Activity/Test App
|
||||||
|
→ MapView(FrameLayout)
|
||||||
|
→ MapRenderer + NativeMapView(Java JNI peer)
|
||||||
|
→ AndroidRendererFrontend
|
||||||
|
→ mbgl::Map / mbgl::Style
|
||||||
|
→ RendererFrontend::update(UpdateParameters)
|
||||||
|
→ Android MapRenderer render thread
|
||||||
|
→ mbgl::Renderer
|
||||||
|
→ RenderOrchestrator / RenderTree / RenderLayer / gfx::Drawable
|
||||||
|
→ Android OpenGL 或 Vulkan RendererBackend
|
||||||
|
→ Surface/TextureView → GPU
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
iOS App/Example
|
||||||
|
→ MLNMapView(UIView)
|
||||||
|
→ MLNMapViewImpl(MapObserver adapter)
|
||||||
|
→ mbgl::Map + RendererFrontend
|
||||||
|
→ RenderOrchestrator / RenderTree / RenderLayer / gfx
|
||||||
|
→ MLNMapViewOpenGLImpl(GLKView) 或 MLNMapViewMetalImpl(MTKView)
|
||||||
|
→ EAGL/CAMetalLayer → GPU
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
直接 Core 宿主
|
||||||
|
→ mbgl::Map(RendererFrontend, MapObserver)
|
||||||
|
→ Map::Impl::onUpdate()
|
||||||
|
→ Immutable Style/Source/Layer snapshot
|
||||||
|
→ Renderer::render()
|
||||||
|
```
|
||||||
|
|
||||||
|
当前能够确认的对象:MapView、Camera、Style、RasterSource、ImageSource、Vector/GeoJSON/CustomGeometrySource、Layer、MapObserver 事件和 CustomLayer。当前仓库没有 RN `ShapeSource` 组件层,也没有 RN `onPress` API;Android/iOS Native SDK 的点击、相机和样式 API 需分别通过各自 SDK 查询,不能移植成当前 RN 事实。
|
||||||
|
|
||||||
|
## 6. MapLibre 生命周期
|
||||||
|
|
||||||
|
### 6.1 Core 生命周期
|
||||||
|
|
||||||
|
`Map::Impl::onUpdate()` 在连续模式下读取当前时间、更新 transition,构造包含 `TransformState`、style/source/layer immutable snapshot、图片、字体和 FileSource 的 `UpdateParameters`,再交给 `RendererFrontend::update()`。`Map::triggerRepaint()` 也只是触发同一更新路径。
|
||||||
|
|
||||||
|
`Renderer::render()` 创建并准备 `RenderTree`,`Renderer::Impl::render()` 依次执行:开始 frame、上传 source/layer 资源、更新 layer group、运行 tweaker、上传 drawable/render target、可选 3D pass、render target、清屏、opaque pass、translucent pass、debug pass、postRender、present、结束 frame,并回调 `onDidFinishRenderingFrame`。
|
||||||
|
|
||||||
|
### 6.2 Android
|
||||||
|
|
||||||
|
- `MapView.initializeDrawingSurface()` 创建 `MapRenderer` 和 `NativeMapView`;Surface 创建后回到 UI 线程初始化 `MapLibreMap` 并调用 `onStart()`。
|
||||||
|
- `MapView.onStart/onResume/onPause/onStop/onDestroy` 分别驱动 Java map/renderer;SurfaceView detach 会触发 native reset,Surface 销毁/重建可能导致 backend 和 `Renderer` 重建。
|
||||||
|
- Java `MapRenderer` 的 `onDrawFrame()` 调用 JNI `nativeRender()`;SurfaceView 由 `MapLibreSurfaceView` 的 render thread 请求 dirty/continuous frame,TextureView 由 `TextureViewRenderThread` 的事件队列和 request flag 驱动。
|
||||||
|
- `AndroidRendererFrontend::update()` 保存最新 `UpdateParameters`,异步任务把它交给 `MapRenderer` 并调用 Java `requestRender()`。C++ render thread 只持有一次 render 所需的参数快照。
|
||||||
|
- Native MapObserver 事件由 JNI 转回 Java `MapChangeReceiver`,包括 camera will/is/did、地图/样式加载、render frame start/end、idle、source 和 shader 事件。
|
||||||
|
|
||||||
|
### 6.3 iOS
|
||||||
|
|
||||||
|
- `MLNMapView` 创建 Core `Map` 和 `MLNMapViewImpl`;实现通过编译宏选择 OpenGL、Metal 或 WebGPU backend。
|
||||||
|
- 显示刷新由 `CADisplayLink` 调用 `updateFromDisplayLink:`;当 `needsDisplayRefresh` 或 pending completion 存在时,调用 backend View 的 `setNeedsDisplay`。
|
||||||
|
- `renderSync` 清除刷新标记后调用 `_rendererFrontend->render()`;渲染 View delegate 的 `drawInRect:`/`drawInMTKView:` 再调用 `MLNMapViewImpl::render()`,形成地图自己的渲染帧。
|
||||||
|
- View 不可见、进入后台、销毁或恢复时,DisplayLink、drawable、backend 资源和 dormant 状态分别处理;具体资源重建依赖 backend 实现。
|
||||||
|
- iOS delegate 可获得相机、地图/样式加载、render frame start/end、idle 等事件;CustomStyleLayer 的 `drawInMapView:withContext:` 在 MapLibre 的自定义 layer 绘制时调用。
|
||||||
|
|
||||||
|
### 6.4 TS/RN 层与内部事件
|
||||||
|
|
||||||
|
当前仓库没有 TS/RN 层,所以“TS/RN 直接获得的生命周期事件”:`未确认/不适用`,原因是该层不存在。Native SDK 可观察的事件如上;只存在于 Core/Renderer 的 `UpdateParameters`、RenderTree、LayerGroup、GPU command encoder 的内部状态,当前没有 JS 层封装。
|
||||||
|
|
||||||
|
## 7. 自定义渲染扩展点分类
|
||||||
|
|
||||||
|
### A:当前项目已经直接使用
|
||||||
|
|
||||||
|
- Core `style::CustomLayer` + `CustomLayerHost` + `RenderCustomLayer`:现有 Android Test App 的 `CustomLayerActivity` 和 C++ `ExampleCustomLayer`/`ExampleVulkanCustomLayer` 实际使用。
|
||||||
|
- Darwin `MLNCustomStyleLayer`:iOS/Swift 示例实现 `drawInMapView:withContext:`,获得同一 frame 的 projection matrix;Metal 示例使用当前 render encoder。
|
||||||
|
- Darwin `MLNCustomDrawableStyleLayer`/Core `CustomDrawableLayer`:示例 `ExampleCustomDrawableStyleLayer` 和 UI 测试实际使用,属于 Drawable/Builder 路径而不是 raw GL callback。
|
||||||
|
- 内置 `RenderLayer`、`LayerGroup`、`DrawableBuilder`、`DrawableTweaker`、shader manifest、texture/buffer/render target:是当前地图本身的生产渲染链,粒子尚未接入。
|
||||||
|
|
||||||
|
### B:当前依赖已暴露,但目标粒子业务尚未使用
|
||||||
|
|
||||||
|
- Android Java `CustomLayer(String id, long host)` 暴露了 native host 指针入口,但 Java API 明确标注 experimental/do not use;当前只在 SDK test app 示例使用,粒子业务未使用。
|
||||||
|
- Android `CustomGeometrySource` 和 iOS `MLNComputedShapeSource` 已暴露,能提供按 tile 的 GeoJSON/几何数据;当前没有粒子/格点数据实现。
|
||||||
|
- Core `gfx::Context` 已有 `createTexture2D`、`createDynamicTexture`、`createRenderTarget`、uniform buffer、`createDrawableBuilder`、generic shader 和 upload pass 接口;这些是现有通用基础设施,不等于粒子专用 API。
|
||||||
|
|
||||||
|
### C:Native Core 支持,但当前高层封装未统一暴露
|
||||||
|
|
||||||
|
- `CustomLayerHost::render()` 可拿到 width/height、center、zoom、bearing、pitch、FOV、projection matrix 和 near-clipped matrix;Vulkan/Metal 还有 backend-specific render parameters。Android 高层 Java host 没有一个类型安全的 `CustomLayerRenderParameters` API,应用级可直接使用范围未确认。
|
||||||
|
- Core `RenderLayer`、`RenderOrchestrator`、`LayerManager`、`RendererObserver` 和 gfx backend 内部 hook 可承载 native 粒子实现,但它们是 Core 内部边界,不是当前 RN/业务 API。
|
||||||
|
- iOS `MLNBackendResource` 可暴露 Metal view/device/render pass/command buffer;这是 iOS Native API,当前仓库没有 RN 统一桥接。
|
||||||
|
|
||||||
|
### D:当前代码无法确认
|
||||||
|
|
||||||
|
- React Native UI/Main Thread、JS bridge、TurboModule/Fabric、RN `requestAnimationFrame` 或 Reanimated frame callback:当前项目没有 RN host,无法确认。
|
||||||
|
- 任何外部 Overlay 与 MapLibre View 的同步质量:当前正式边界禁止外部 Overlay,仓库也没有粒子 Overlay 实现,因此没有实测数据。
|
||||||
|
- 未实现的粒子 shader、GPU ping-pong state、trail render target、velocity texture 的具体格式和生命周期:当前只有架构草图,没有实现。
|
||||||
|
|
||||||
|
## 8. Android 渲染架构
|
||||||
|
|
||||||
|
- 原生 View:`MapView` 是 `FrameLayout`,将 SurfaceView 或 TextureView renderer 放在 child index 0;不是 RN `View`。
|
||||||
|
- renderer:Java `MapRenderer` 仅负责生命周期、JNI 和调度;C++ `MapRenderer` 持有 Android backend、Core `Renderer`、`ActorRef`、update mutex 和 renderer observer。
|
||||||
|
- backend:Gradle flavor 通过 `-DMLN_WITH_OPENGL=ON` 或 `-DMLN_WITH_VULKAN=ON` 构建;C++ `AndroidGLRendererBackend` 和 `AndroidVulkanRendererBackend` 分别管理 EGL/GL 或 Vulkan surface/swapchain/device。
|
||||||
|
- 帧驱动:SurfaceView 默认可以 WHEN_DIRTY,CONTINUOUS 用于连续渲染/benchmark;TextureView 自有 render thread。Core update 通过 `AndroidRendererFrontend` 调用 `requestRender`。
|
||||||
|
- 相机:Java `MapLibreMap`/`Transform` 调 JNI,NativeMapView 调 Core `jumpTo/easeTo/flyTo`、坐标投影和反投影;完整 matrix 仍在 Core Transform/Paint/CustomLayer 参数中。
|
||||||
|
- 自定义入口:Core CustomLayer 和 Android wrapper/test example 明确存在;但 Java host 是原始 `long`,生命周期、指针归属、context loss 和参数传递要求高,不能当作稳定业务接口。
|
||||||
|
- Native Module:未找到 RN NativeModule/TurboModule/Fabric。Android 的 `NativeMapView` 是 MapLibre Java/C++ peer/JNI,不是 RN module;当前不存在“Native Module 接触 RN MapView 实例”的事实。
|
||||||
|
|
||||||
|
## 9. iOS 渲染架构
|
||||||
|
|
||||||
|
- 原生 View:`MLNMapView` 继承 `UIView`;内部由 `MLNMapViewImpl` 将 Core MapObserver 与 ObjC delegate 接起来。
|
||||||
|
- OpenGL:`MLNMapViewOpenGLImpl` 持有 `gl::RendererBackend`、Renderable 和 `GLKView`/EAGL context;GLKView delegate 的绘制回调进入 Core render。
|
||||||
|
- Metal:`MLNMapViewMetalImpl` 持有 Metal backend、`MTKView`、command queue/buffer 和 render pass descriptor;`drawInMTKView:` 调 `impl->render()`,Core 负责 command encoding/present。
|
||||||
|
- Camera/transform:Core `TransformState` 生成 `TransformParameters` 的 projection/aligned/near-clipped matrices;`MLNCustomStyleLayer` 将这些和中心、zoom、方向、pitch、FOV 传给 ObjC drawing context。
|
||||||
|
- 自定义接口:`MLNCustomStyleLayer` 是可确认的 Native custom layer;Metal 下可以使用当前 encoder,OpenGL 下存在 deprecated EAGL context API。`MLNCustomDrawableStyleLayer` 是另一条 Drawable/Builder 接口。
|
||||||
|
- Native 对象范围:iOS adapter 能访问 Core Map、RendererFrontend 和 backend resource;公开的业务层主要通过 `MLNMapView`、`MLNStyle`、`MLNStyleLayer` 及 delegate 访问。没有 RN 连接层,RN 侧范围为 `未确认`。
|
||||||
|
- Android/iOS 差异:Android 有明确 OpenGL/Vulkan flavor 和实验性 raw host;iOS 示例有成熟的 ObjC/Swift CustomStyleLayer 与 Metal encoder 上下文。两者虽共享 Core CustomLayer 接口,但高层资源和生命周期 API 不一致。
|
||||||
|
|
||||||
|
## 10. JS/UI/Native/Worker/Render/GPU 边界
|
||||||
|
|
||||||
|
```text
|
||||||
|
[不存在的 JS/RN 业务层]
|
||||||
|
│ 当前仓库无桥接事实
|
||||||
|
▼
|
||||||
|
Android UI thread / iOS main thread
|
||||||
|
├─ Native MapView、Camera、Style API
|
||||||
|
├─ Core Map 所在线程(由宿主创建线程决定)
|
||||||
|
└─ MapObserver 回调
|
||||||
|
▼ immutable UpdateParameters / mailbox / async task
|
||||||
|
MapLibre Worker/FileSource
|
||||||
|
├─ Style/tile 解析、布局、bucket/buffer 计算
|
||||||
|
└─ FileSource 网络/缓存/离线 I/O
|
||||||
|
▼
|
||||||
|
MapLibre Render thread(Android 明确独立;iOS 当前 render delegate 路径需遵循宿主主线程/渲染上下文约束)
|
||||||
|
→ Renderer → RenderTree → LayerGroup/Drawable → gfx Context/CommandEncoder
|
||||||
|
→ OpenGL/Vulkan/Metal backend → GPU
|
||||||
|
```
|
||||||
|
|
||||||
|
- 高频数据若走不存在的 JS bridge,当前没有事实可测;按现有边界,不能将每帧大数组交给 Java/ObjC API 作为已支持能力。
|
||||||
|
- Android 的 `UpdateParameters` 在 frontend 与 render peer 之间异步保存并加锁,渲染使用最新快照;Java `requestRender` 只触发帧,不传粒子数据。
|
||||||
|
- iOS `setNeedsRerender` 只设置刷新标记,`CADisplayLink`/UIView backend 再进入 render;这不是 JS 每帧回调。
|
||||||
|
- Native 每帧回调存在:Core `MapObserver`/`RendererObserver` 的 render frame 事件,以及 CustomLayerHost render callback;它们位于地图渲染链内部。
|
||||||
|
- GPU 资源直接更新位置存在于 Core `gfx::Context`、UploadPass、Drawable、Texture、RenderTarget 和 backend command encoder;当前没有对业务开放的跨平台粒子资源接口。
|
||||||
|
|
||||||
|
## 11. Camera、Matrix 与坐标转换能力
|
||||||
|
|
||||||
|
| 信息 | JS/RN | Native SDK | Core 内部/CustomLayer | 当前结论 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| center/zoom/bearing/pitch | 当前无 RN host,未确认 | Android `MapLibreMap`、iOS `MLNMapView`/delegate 可访问部分状态 | `TransformState` 全部存在,CustomLayer 参数传出 | 可由 Native/Core 获取 |
|
||||||
|
| bounds/viewport | 无 RN host | SDK 有尺寸和相机/投影 API | `TransformState::getSize`、frustum offset、tile cover | Core 信息完整 |
|
||||||
|
| screen ↔ geographic | 无 RN host | Android NativeMapView 和 iOS 公共 API 有相应能力 | `latLngToScreenCoordinate`、`screenCoordinateToLatLng` | 已确认 Core 能力 |
|
||||||
|
| projection/unprojection | 未确认 | 高层不统一暴露 raw matrix | Core `getProjMatrix`、projection/inverse matrices、camera utility | 内部可用 |
|
||||||
|
| view/transform matrix | 未确认 | iOS CustomStyleLayer 以 projection context 形式收到;Android Java wrapper 未提供同等类型 API | `TransformState::matrixFor`、`TransformParameters`、`PaintParameters` | 跨平台高层不一致 |
|
||||||
|
|
||||||
|
证据:`TransformParameters` 保存 `projMatrix`、`alignedProjMatrix`、`nearClippedProjMatrix`;`CustomLayerRenderParameters` 从同一 `PaintParameters.state` 读取中心、zoom、bearing、pitch、FOV 和 projection matrix;`PaintParameters::matrixForTile` 负责 tile matrix。
|
||||||
|
|
||||||
|
严格同步结论:在 Core 的同一渲染帧内,CustomLayer callback 可以获得地图当前 transform,因此“Core 内部同帧使用地图矩阵”已确认;但“Android/iOS 业务高层均能以统一公开 API 获取并接入粒子”未确认,当前不能宣称跨平台正式实现已具备严格同步能力。
|
||||||
|
|
||||||
|
## 12. 帧驱动能力
|
||||||
|
|
||||||
|
已确认:Android SurfaceView/TextureView render loop、Android `requestRender`/CONTINUOUS、iOS CADisplayLink、GLKView/MTKView draw callback、MapLibre `triggerRepaint`、Core CustomLayer render callback。
|
||||||
|
|
||||||
|
未确认/不适用:当前没有 RN `requestAnimationFrame`、RN animation frame、Reanimated frame callback 或 JS 每帧回调。
|
||||||
|
|
||||||
|
地图 Renderer 的帧驱动不是独立于地图的粒子动画时钟:Core `Map::Impl::onDidFinishRenderingFrame` 在 `needsRepaint` 或 transition 时重新 `onUpdate()`;平台再请求下一帧。若未来另建独立动画循环,现有代码无法保证它与相机快照、drawable context、present 时刻一致,可能产生一帧延迟、不同步或重复渲染;这里只记录风险,不作方案选择。
|
||||||
|
|
||||||
|
## 13. 当前数据与粒子相关实验代码
|
||||||
|
|
||||||
|
- `particle-fixtures/README.md` 明确第一阶段禁止真实天气服务器,具体数据格式在设计冻结后确定;`coastal-mask/`、`shear/`、`uniform-east/`、`uniform-north/`、`vortex/` 目前均只有 `.gitkeep`。
|
||||||
|
- 全仓库排除 vendor、node_modules、build 后搜索 `particle/wind/current/wave/vector field/rasterarray/velocity`,没有发现粒子运行时、速度场解析、粒子 shader、GPU ping-pong 或 trail 实现。
|
||||||
|
- 现有 `raster` 文件是 MapLibre 常规 RasterSource/RasterLayer 测试和内置 raster shader;`interpolate` 多数是 Style Spec/通用工具,不是向量场插值实现。
|
||||||
|
- `shaders/` 有 raster、heatmap、hillshade、fill、line、symbol、custom geometry 等现有地图 shader,没有 `particle`、`velocity` 或 trail shader。
|
||||||
|
- Android/iOS custom layer 示例是图形 API 示例/测试用途,不是粒子基础代码;应分类为“仅实验/示例用途”,不能直接判定为最终可复用粒子实现。
|
||||||
|
|
||||||
|
## 14. 海洋/气象格点数据可经过的现有层
|
||||||
|
|
||||||
|
| 阶段 | 当前仓库事实 |
|
||||||
|
|---|---|
|
||||||
|
| Fetch | Core `FileSource` 支持样式、tile、资源和缓存;没有粒子气象数据协议或 fixture loader |
|
||||||
|
| ArrayBuffer/TypedArray | JS 侧只有 Node/脚本和测试用途;未发现粒子 ArrayBuffer/TypedArray 模型 |
|
||||||
|
| 二进制解码 | Core 有 tile、PBF、图片等既有解码;没有速度场/海洋格点解码器 |
|
||||||
|
| Grid 数据模型 | 没有项目粒子 Grid/经纬度格点类型;`grid_index` 等是通用地图工具或测试 |
|
||||||
|
| 插值 | 有通用数值/Style 插值工具;没有已确认的向量场双线性/球面插值模块 |
|
||||||
|
| 内存结构 | Core 可用 immutable/vector/CPU buffer;没有粒子数据生命周期模型 |
|
||||||
|
| GPU upload | gfx Context、UploadPass、Texture2D、UniformBuffer、RenderTarget、Drawable 已存在 |
|
||||||
|
| Shader uniform | 内置 shader 与 global/layer uniform 已存在;没有粒子 uniform contract |
|
||||||
|
| 缓存/生命周期 | FileSource、tile cache、style snapshot 和 gfx resource 生命周期已存在;粒子数据生命周期未实现 |
|
||||||
|
|
||||||
|
可复用基础设施只能确认到通用地图层:FileSource/离线 fixture 测试体系、immutable snapshot、thread pool、gfx Context/Texture/Buffer/RenderTarget/Drawable、shader registry 和 frame observer。未来粒子数据格式、解析、插值、上传策略均未确定。
|
||||||
|
|
||||||
|
## 15. 性能与线程风险
|
||||||
|
|
||||||
|
| 风险 | 等级 | 当前证据与边界 |
|
||||||
|
|---|---|---|
|
||||||
|
| JS 高频循环/每帧 JS→Native | 尚无法判断 | 当前无 JS/RN 宿主;若未来新增,仓库没有现成桥接基准 |
|
||||||
|
| 大数组/JSON/ArrayBuffer 跨桥复制 | 高 | 当前 MapLibre update 是 immutable snapshot/异步传递,未提供粒子大数组桥接;跨平台高层接口若采用逐帧对象传输会形成风险 |
|
||||||
|
| Native→GPU 重复复制 | 中 | gfx upload/update 能力存在,但粒子资源尚未定义,复制次数无法实测 |
|
||||||
|
| Texture 高频重建 | 中 | Context 有 Texture2D/DynamicTexture;RenderCustomLayer 示例未提供粒子资源复用策略 |
|
||||||
|
| Camera 变化触发大规模重算 | 中 | Map update 会重新计算 RenderTree、tile/layer evaluation;未来粒子若绑定 camera 需避免把每帧状态误当 style/source 变更 |
|
||||||
|
| React re-render | 尚无法判断 | 没有 React/RN 工程 |
|
||||||
|
| 外部 Overlay 不同步 | 低(当前)/架构禁止 | 当前没有外部粒子 Overlay;正式规则也禁止该路径 |
|
||||||
|
| Android/iOS 实现分叉 | 高 | 同 Core 但高层 CustomLayer、context/encoder、线程和 surface 生命周期不同 |
|
||||||
|
| GPU context/surface loss | 高 | Android `onSurfaceCreated` 会 mark context lost 并重建 backend/Renderer;CustomLayer 有 contextLost;iOS 后台/视图重建也涉及资源生命周期 |
|
||||||
|
| MapView 销毁后资源泄漏 | 中 | Core/RenderCustomLayer 有 deinitialize/contextLost 路径,raw `long` host 和应用归属仍需后续设计验证 |
|
||||||
|
| Render pass 顺序/状态污染 | 高 | CustomLayer 在 translucent pass;raw backend callback 需要遵守当前 render pass 和状态恢复约束 |
|
||||||
|
|
||||||
|
## 16. 当前架构图
|
||||||
|
|
||||||
|
### 16.1 App / MapLibre 总体调用图
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android 示例或 iOS 示例
|
||||||
|
└→ 平台 MapView
|
||||||
|
└→ 平台 RendererFrontend / MapObserver adapter
|
||||||
|
└→ mbgl::Map
|
||||||
|
├→ Style / Source / Layer snapshot
|
||||||
|
├→ Worker + FileSource
|
||||||
|
└→ Renderer → RenderTree → gfx → 平台 GPU backend
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16.2 JS → Native → MapLibre 调用链
|
||||||
|
|
||||||
|
```text
|
||||||
|
JS/RN 业务入口:不存在于当前仓库
|
||||||
|
└→ RN MapLibre binding:不存在于当前仓库
|
||||||
|
└→ Android Java MapView 或 iOS MLNMapView:由外部宿主提供
|
||||||
|
└→ JNI/ObjC++ adapter
|
||||||
|
└→ mbgl::Map → RendererFrontend → Renderer → GPU
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16.3 Android
|
||||||
|
|
||||||
|
```text
|
||||||
|
UI thread: MapView / MapLibreMap / NativeMapView JNI
|
||||||
|
└→ AndroidRendererFrontend + AsyncTask/mailbox
|
||||||
|
Render thread: MapRenderer → Renderer → RenderOrchestrator
|
||||||
|
└→ OpenGL EGL 或 Vulkan device/swapchain
|
||||||
|
└→ SurfaceView/TextureView → GPU
|
||||||
|
Workers/FileSource: tile/style parsing、layout、I/O
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16.4 iOS
|
||||||
|
|
||||||
|
```text
|
||||||
|
main/UI: MLNMapView(UIView) + CADisplayLink
|
||||||
|
└→ MLNMapViewImpl(MapObserver) + RendererFrontend
|
||||||
|
draw callback: GLKView 或 MTKView
|
||||||
|
└→ mbgl::Renderer → RenderTree → gfx command encoder
|
||||||
|
└→ EAGL/OpenGL 或 Metal command buffer → GPU
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16.5 数据与渲染边界
|
||||||
|
|
||||||
|
```text
|
||||||
|
FileSource / tile / offline fixture
|
||||||
|
→ immutable Source/Layer/UpdateParameters
|
||||||
|
→ RenderTree / RenderSource / RenderLayer
|
||||||
|
→ gfx Texture/Buffer/Drawable/RenderTarget
|
||||||
|
→ backend command encoding
|
||||||
|
→ GPU
|
||||||
|
|
||||||
|
粒子 field / particle state / trail:当前不存在;未来位置为 [未确认]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 17. 关键结论分类
|
||||||
|
|
||||||
|
### 17.1 已确认事实
|
||||||
|
|
||||||
|
1. 当前仓库是 MapLibre Native monorepo,不是 RN app。
|
||||||
|
2. Android 13.4.1、iOS 6.28.0 和 Core commit 已从版本文件/基线确认。
|
||||||
|
3. Core 的真实主链是 Map → RendererFrontend → UpdateParameters → Renderer → RenderTree/Orchestrator → Layer/Drawable → gfx backend。
|
||||||
|
4. Android 有 OpenGL/Vulkan flavor、独立 renderer thread、JNI Map peer 和实验性 CustomLayer。
|
||||||
|
5. iOS 有 `MLNMapView`、CADisplayLink、OpenGL/Metal backend、Native CustomStyleLayer 和 Drawable 示例。
|
||||||
|
6. Core CustomLayer render 参数包含相机和 projection 信息;CustomLayer callback 在地图 render pass 内执行。
|
||||||
|
7. 现有 fixture 只有目录占位;没有粒子运行时、格点模型、粒子 shader 或速度场解析器。
|
||||||
|
8. Core 已有可复用的 immutable snapshot、worker/FileSource、gfx texture/buffer/render target/drawable/upload/shader registry 基础设施。
|
||||||
|
|
||||||
|
### 17.2 当前限制
|
||||||
|
|
||||||
|
- 没有 RN 页面、RN binding、Metro/Babel、NativeModule/TurboModule/Fabric,因此不能从本仓库确认 RN 到 MapView 的调用和生命周期。
|
||||||
|
- Android CustomLayer 的 Java API 使用 raw native host 指针且标记 experimental;与 iOS CustomStyleLayer 的上下文能力不对称。
|
||||||
|
- 正式粒子数据格式、内存模型、插值、GPU state、trail 和跨平台资源生命周期尚不存在。
|
||||||
|
- 不可把现有 RasterSource 直接解释为 Raster/Vector Field source;它当前只处理常规 raster tile/image。
|
||||||
|
- 本次未进行设备运行时验证,不能把静态可见接口写成已验证的帧率、延迟或 context-loss 行为。
|
||||||
|
|
||||||
|
### 17.3 候选扩展点(不作最终选择)
|
||||||
|
|
||||||
|
1. Core `CustomLayerHost`/`RenderCustomLayer` 的同帧 callback。
|
||||||
|
2. Core `CustomDrawableLayer` + DrawableBuilder/Tweaker/gfx resource。
|
||||||
|
3. 新的隔离 Core particle render layer,与现有 RasterLayer/WeatherColorLayer 分离。
|
||||||
|
4. 现有 Source/Tile/FileSource/fixture 管线的薄接入,用于离线数据而非直接改造 RasterSource。
|
||||||
|
5. 平台薄接入:Android 复用 renderer surface/context 生命周期,iOS 复用 CustomStyleLayer/Metal 或 Core adapter 的同帧 transform。
|
||||||
|
|
||||||
|
这些只是调查候选;本报告不决定 CPU/GPU、CustomLayer 最终是否采用、OpenGL/Vulkan/Metal 路线、数据纹理格式、shader 架构或线程模型。
|
||||||
|
|
||||||
|
### 17.4 未确认问题
|
||||||
|
|
||||||
|
| 问题 | 当前无法确认原因 | 是否阻塞 `20_架构设计` | 最小确认方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 实际目标产品的 RN 宿主和 binding 版本 | 不在当前仓库 | 否;当前阶段可先按 Native Core 事实设计边界 | 后续由产品宿主提供 package/native 工程清单 |
|
||||||
|
| Android 业务侧能否稳定创建并持有 CustomLayerHost | 当前只有 raw `long` 示例和 JNI peer | 否,但影响平台薄接入细节 | 单独最小 Android 示例运行验证,不改本阶段代码 |
|
||||||
|
| iOS 当前正式发布构建究竟启用 OpenGL 还是 Metal | 源码同时支持,构建宏由外部工程决定 | 否;设计需保留 backend 分支 | 读取目标 Pod/Xcode 构建设置或做最小设备验证 |
|
||||||
|
| Core render thread 与 iOS app main thread 的确切调度策略 | 静态源码能确认 DisplayLink/MTKView callback,不能确认目标宿主调度 | 否 | 目标 iOS scheme 的运行时线程标记/最小 sample |
|
||||||
|
| 粒子目标场的具体文件格式、范围、时间步长 | fixture 只有占位目录且设计尚未冻结 | 否;属于后续数据设计输入 | 由设计阶段冻结离线 fixture contract |
|
||||||
|
| 目标设备的 GPU 特性、纹理格式和可用 compute 能力 | 当前没有设备矩阵或运行时探测结果 | 否;可在设计中标注 capability gate | Android/iOS 设备专项最小验证 |
|
||||||
|
| 旋转、pitch、wrap、frustum 下粒子坐标的最终视觉约束 | Core matrix 可用,但粒子规则尚未定义 | 否 | 进入 `20_架构设计` 后以 fixture 和截图测试确认 |
|
||||||
|
|
||||||
|
## 18. 是否追加调查、是否进入 20_架构设计
|
||||||
|
|
||||||
|
选择:**A,可以直接进入 `20_架构设计`。**
|
||||||
|
|
||||||
|
理由:本次已确认项目实际形态、Core 调用链、Android/iOS render lifecycle、CustomLayer/gfx 位置、相机矩阵来源、frame driver、现有数据基础设施和主要风险。未确认项主要是外部目标宿主、运行时设备和未来数据 contract,不满足“必须先追加一个调查任务”的必要条件;它们可作为 `20_架构设计` 的约束和验证清单。
|
||||||
|
|
||||||
|
下一阶段必须继续遵守:不复制 Mapbox 新源码或 Windy APK 代码;粒子模块高度隔离;WeatherColorLayer 与 RasterParticleLayer 分离;Wind=GLOBAL;Wave/Tide/Current=OCEAN_ONLY 并共用 Land/Ocean Mask;第一阶段只使用离线 fixture;平台只做薄接入。
|
||||||
|
|
||||||
|
## 19. 本次未做和边界声明
|
||||||
|
|
||||||
|
- 未创建粒子实现、shader、CustomLayer 实现、Native Module 或架构重构。
|
||||||
|
- 未升级/安装 npm、Gradle、Pod 或其他依赖。
|
||||||
|
- 未修改 Android/iOS 原生工程、Gradle/Podfile/Metro/Babel/TypeScript 配置。
|
||||||
|
- 未运行构建和测试,因为任务明确为只读调查,且构建可能写入产物;历史基线构建 PASS 仅引用已有基线记录,不作为本次重新验证结果。
|
||||||
|
- 按用户前置要求,未自行创建或改写 Step 文件;现有 `docs/50_Step记录/STEP-000_完整环境初始化_v1.md` 的文件内容仍为“进行中”,本报告不改动其历史状态。
|
||||||
104
docs/20_调查/Particle_V13_严格代码审查报告_v1.md
Normal file
104
docs/20_调查/Particle_V13_严格代码审查报告_v1.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
# Particle V13 严格代码审查报告
|
||||||
|
|
||||||
|
日期:2026-09-25(Asia/Tokyo)
|
||||||
|
审查性质:Mac 仓库只读代码审查、历史任务/报告交叉核对、定向原生测试复跑。未修改业务源码、未执行设备 GPU runtime 或 profiler。
|
||||||
|
|
||||||
|
## 1. 审查基线和证据分级
|
||||||
|
|
||||||
|
- Mac:`/Users/tei/dev/MapLibreParticleLab`;分支:`particle/main`。
|
||||||
|
- HEAD 与 `origin/particle/main` 一致:`ee0b89e711bf01335b3aa71d36def785cd97fec3`;工作树 clean。
|
||||||
|
- V13 功能提交:`016fd1fff6a33cf795c10d4f5c5413d308383677`。发布版本:`13.4.1-particle.13`。
|
||||||
|
- Integration 主线:`39ad8415fbdb76c1822460bb09f3a3f35fe141e0`。
|
||||||
|
- 参照:`docs/40_任务/STEP-092...`、`STEP-093...`、`STEP-094...` 任务;对应 `docs/报告/` 与 `docs/50_Step记录/`;STEP-088/089/090 性能历史报告。
|
||||||
|
- 本次独立复跑:Render Segment/Retained/Fast Path 43/43 PASS;Field/QBIN/Viewport Reseed 69/69 PASS;V13 RN package TypeScript PASS。
|
||||||
|
- 当前 RN Test Host `npm run typecheck` **失败(exit 2)**:TS2307 无法解析 `@navsea/maplibre-particle-rn`,伴随 TS7006。Host package.json 仍指向 `.10`。此前 Codex 使用临时工作树依赖通过,不等于当前默认安装可重复验证。
|
||||||
|
- STEP-094 报告记录 OpenGL、Metal 构建、Codegen、Android Validation APK 通过;**Android/iOS V13 runtime、GPU frame/visual、功耗与长时间运行仍 NOT RUN**。此报告不将历史 Android 性能测量冒充 V13 性能。
|
||||||
|
|
||||||
|
## 2. 审查结论
|
||||||
|
|
||||||
|
没有在已复跑的纯数据结构/协议测试中发现失败;但 V13 在高频渲染、Metal 多段重建、GPU 资源分配和真实运行态验收上存在明确风险。不能由 43 项纯 Native 测试和编译 PASS 推断任意 R/C 交错的 GPU 输出、跨平台性能已经验收。
|
||||||
|
|
||||||
|
### F01 — P1:完整 Diagnostics 深复制进入每帧热路径(源码确定)
|
||||||
|
|
||||||
|
证据:`src/mbgl/renderer/renderer_impl.cpp:206-208,500-505`;`src/mbgl/renderer/retained_underlay_runtime_state.hpp:243-254`。
|
||||||
|
|
||||||
|
`tryRenderParticleFastFrame()` 和 `render()` 调用 `renderSegmentSnapshot()`;该函数在互斥锁内复制完整 `RenderSegmentDiagnostics`(含 `styleLayers` 与每个 Segment 的重复 `layers`/字符串),再补几个状态字段。一次 fast frame 可能复制两次。与 pull-based 诊断的设计意图不一致。
|
||||||
|
|
||||||
|
修复:增加只读的轻量 hot-path 状态(enabled/configRevision/topologyRevision 等);完整快照只在外部 pull 时构造;快照构建/复制次数做计数,稳定 30/60 FPS 应接近零。
|
||||||
|
|
||||||
|
### F02 — P1:Metal Retained 构建逐段 GPU 同步等待(源码确定,运行影响待 profile)
|
||||||
|
|
||||||
|
证据:`renderer_impl.cpp:790-824`;`retained_underlay_cache.cpp:316-322`;`src/mbgl/mtl/command_encoder.cpp:29-31`;`src/mbgl/mtl/offscreen_texture.cpp:86-91`。
|
||||||
|
|
||||||
|
`finishBuild()->present(*offscreenTexture)->swap()` 内部 `commit()` 后 `waitUntilCompleted()`。一帧多个 Retained 段失效时,可能产生多次串行 CPU/GPU 同步。使用 Metal System Trace / GPU Capture 测量 `waitUntilCompleted` 时间和 command-buffer 数量,再设计保持依赖顺序的异步提交/跨 pass 协调;不可仅删除 wait 而不确保纹理可读。
|
||||||
|
|
||||||
|
### F03 — P1:缓存内容失效与 GPU 资源销毁耦合(源码确定)
|
||||||
|
|
||||||
|
证据:`retained_underlay_cache.cpp:203-209,241-255,364-374`。
|
||||||
|
|
||||||
|
`revisionChanged` 使 `invalidate()` 与 `releaseResources()` 一起发生,即使 viewport 未变、GPU context 有效,重建时仍需重新创建纹理和 composite group。普通 camera、source/tile、content revision 在多 Retained 段下会放大分配/销毁和相关同步。
|
||||||
|
|
||||||
|
修复:分离 `contentDirty` 与 `resourceInvalid`,同尺寸同 context 保留 allocation、重新 clear/build;context loss、尺寸不兼容、显式关闭/释放时才销毁。用 peak GPU bytes、textureCreate、resourceRelease、每帧 build/reuse 证明收益与正确性。
|
||||||
|
|
||||||
|
### F04 — P1:多缓存没有 GPU 内存预算(源码确定,OOM 影响待量化)
|
||||||
|
|
||||||
|
证据:`renderer_impl.cpp:506-524`;`retained_underlay_cache.cpp:241-251`;`mtl/offscreen_texture.cpp:20-55`;`gl/offscreen_texture.cpp:18-35`。
|
||||||
|
|
||||||
|
每个 Retained Segment 均拥有完整 viewport 的 RGBA + depth/stencil 离屏资源;总成本约随 retainedCount×width×height 增长。相邻 R-R 也各自独立,符合契约,但高像素 iPad 与多段场景缺乏实际 bytes/peak/budget 观测。先增加预算与失败时的安全 full-render fallback,切勿通过强制合并相邻 R-R 破坏 V13 契约。
|
||||||
|
|
||||||
|
### F05 — P1:V13 Test Host 默认安装不可复现(已复现失败)
|
||||||
|
|
||||||
|
证据:`integration/react-native/MapLibreParticleRNTestHost/package.json` 指向 `.10`;当前 `npm run typecheck` exit 2;报告也明确临时本地替换后已恢复旧依赖。
|
||||||
|
|
||||||
|
修复:将 Test Host 依赖和 lockfile 与已发布 `.13` 精确对应,重新安装并验证 V13 typecheck/codegen/iOS & Android bridge/真实运行;测试日志注明源包及 Native 精确版本。不得将临时链接环境结果写成默认环境 PASS。
|
||||||
|
|
||||||
|
### F06 — P1:多 Continuous 的 Particle Fast Path 固定 fallback(明确设计限制)
|
||||||
|
|
||||||
|
证据:`renderer_impl.cpp:45-57,198-287` 和 `particle_fast_path.hpp:110-140`,STEP-094 设计与报告。
|
||||||
|
|
||||||
|
N 个 Continuous 中只支持恰好一个的 Fast Path;R-C-R-C-R 场景总走 Full CPU。STEP-090 历史 A/B 场景 RenderThread 约为 fast ON 21.7% / OFF 97.0%,只是历史参考,不能直接视作 V13 数据。先采集 V13 多 C 的 CPU/GPU 帧时间与频率,再设计可组合的 multi-C fast path;绝不可为性能偷偷移动 Style Layer 或合并相邻段。
|
||||||
|
|
||||||
|
### F07 — P2:范围访问反复计算线性 ordinal(源码确定)
|
||||||
|
|
||||||
|
证据:`src/mbgl/renderer/render_orchestrator.hpp:156-180`,`std::distance(multimap.begin(), lower_bound(...))`。每个 Segment 的 opaque/translucent、build、upload/tweaker 路径可重复触发定位。
|
||||||
|
|
||||||
|
修复:在 LayerGroup topology 变化时预计算有序索引/ordinal;高频 render 只用稳定区间和 ordinal,避免多段场景重复 O(N) 前缀遍历。
|
||||||
|
|
||||||
|
### F08 — P2:多段失效粒度仍过宽(明确设计限制)
|
||||||
|
|
||||||
|
证据:`renderer_impl.cpp:565-646`;`render_orchestrator.cpp:156-158,1005-1018`。Layer Impl fingerprint 可局部失效;source/tile/image/glyph/placement 与聚合 `retainedSupported` 仍可能使全部 R fallback/build。应先用 `sourceId` 与 Segment 关联建立有证据的保守依赖关系,无法归属继续全局失效,不得牺牲安全。
|
||||||
|
|
||||||
|
### F09 — P2:Diagnostics 资源指标可能发生归属偏差(源码路径风险)
|
||||||
|
|
||||||
|
证据:`retained_underlay_runtime_state.hpp:173-240` 更新新 topology/entry;`renderer_impl.cpp:506-524` 随后 `clear()` 旧 caches;旧对象析构在 `retained_underlay_cache.cpp:364-374` 用旧 segmentIndex 报 release。不同 topology 的相同 index 可能指向新段,旧资源的释放计数可能归于新段或被 Continuous cache=null 丢弃。建议使用 cache generation/unique ID 归属,或在更换 diagnostics topology 前记录旧 generation 的 release;聚合 totals 与 per-generation 分离。
|
||||||
|
|
||||||
|
### F10 — P2:发布与协调状态资料漂移(核对事实)
|
||||||
|
|
||||||
|
Integration 的 `status/`、`compatibility/MATRIX.md` 仍描述 `.5` / iOS XCFramework 等旧状态,Mac 粒子仓库已发布 `.13`。应由粒子负责人维护正式兼容矩阵,区分“Particle 已发布”和“NavSea 已接入/验证”,不得仅根据旧矩阵断言 V13 不存在或已在 NavSea 生效。
|
||||||
|
|
||||||
|
## 3. 正确性与验证缺口
|
||||||
|
|
||||||
|
以下不是已证实的渲染 bug,而是必须在设备上验证的风险:
|
||||||
|
|
||||||
|
- V13 segmented pass 改变了 opaque/translucent、offscreen composite 的执行拆分;纯 resolver GTest 不覆盖 GPU blending/depth/stencil 与 Metal/OpenGL 的最终像素。
|
||||||
|
- 必须覆盖 R-C-R、C-R-R、R-R-C、R-C-R-C-R、相邻 R-R 独立 cache、全 R、全 C,特别是透明航标/半透明颜色场与动态粒子叠加。
|
||||||
|
- hidden boundary、invalid/recovery、Style layer insert/remove/reorder、Resize/Rotate/Pan/Zoom/Pitch、Context loss、Cache ON/OFF、Source/Tile mutation、切换 Plan 后的下一帧,均需要截图/帧采样和资源 counter 交叉验收。
|
||||||
|
- STEP-093/094 报告已明确运行态 NOT RUN,因此不以原生编译 PASS 代替此项。
|
||||||
|
|
||||||
|
## 4. 推荐执行顺序(分批,降低反复安装成本)
|
||||||
|
|
||||||
|
A. **无需设备的修复**:先处理 F01 快照深复制、F05 Test Host 精确版本与复现、F07 ordinal、F09 诊断归属;增加纯 C++ 定向回归。每阶段只做对应范围,不把源文件无关改动混入。
|
||||||
|
|
||||||
|
B. **Metal 资源修复**:合并 F02/F03/F04,统一处理 GPU 对象分配与同步、资源预算;OpenGL/Metal 静态编译和 Native regression 先行;再一次集中到 iPhone/iPad 采集 GPU Frame Capture。
|
||||||
|
|
||||||
|
C. **正式运行态验收**:集中执行 topology、alpha/depth、invalid/recovery、30/60 FPS、稳定/变更、内存峰值、30 分钟以上长稳态;记录相同设备、相同 Style、相同视口的 V12/V13 或 V13 ON/OFF 成对指标,不外推历史数字。
|
||||||
|
|
||||||
|
D. **后续性能架构**:在基础正确性通过后,再研究多 Continuous Fast Path 和 source-to-segment 精确依赖。保持 Style Z-order 唯一顺序源、不合并同策略相邻段、不引入业务频率分类。
|
||||||
|
|
||||||
|
## 5. 最终判定边界
|
||||||
|
|
||||||
|
- 纯 Native tests:43/43 + 69/69 PASS;RN V13 package TS PASS。
|
||||||
|
- Test Host 默认环境:FAIL(缺失模块 / `.10` 引用)。
|
||||||
|
- 源码性能问题:F01/F02/F03/F07 的触发调用链得到静态确认;真实节省量仍需设备测量。
|
||||||
|
- V13 Android/iOS GPU runtime、完整画面、内存和长时间性能:本次 NOT RUN;不声称生产级全量 PASS。
|
||||||
|
- Mac Git:HEAD 与 origin 一致,工作树 clean;未修改粒子源码。
|
||||||
3
docs/20_调查/README.md
Normal file
3
docs/20_调查/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 调查目录
|
||||||
|
|
||||||
|
第一份建议 `MapLibre粒子接入点调查_v1.md`,只读调查 Style/Source/Tile/RenderLayer/gfx/ShaderRegistry/OpenGL/Vulkan/Metal。
|
||||||
436
docs/20_调查/RenderSegment多Retained段全局Placement门槛_粒子管理员审查说明_v1.md
Normal file
436
docs/20_调查/RenderSegment多Retained段全局Placement门槛_粒子管理员审查说明_v1.md
Normal file
@@ -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 真机验证。
|
||||||
82
docs/20_调查/STEP-048_下一阶段规划调查报告_v1.md
Normal file
82
docs/20_调查/STEP-048_下一阶段规划调查报告_v1.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# STEP-048 下一阶段规划调查报告
|
||||||
|
|
||||||
|
## 基线与方法
|
||||||
|
|
||||||
|
- 分支:`particle/main`;本地和 `origin/particle/main` HEAD 均为 `46dceb5cb6110c94393b7413465f843844dd6502`。
|
||||||
|
- 使用 Gitea API `api/v1/repos/tei/MapLibreParticleLab/branches/particle%2Fmain` 确认,不依赖网页缓存。
|
||||||
|
- 工作区只有用户既有 untracked 任务书、日志和 `STEP-046-e48fa087-review.diff`;本调查不触碰它们。
|
||||||
|
|
||||||
|
## 当前实际结构
|
||||||
|
|
||||||
|
### Core Runtime
|
||||||
|
|
||||||
|
- `src/mbgl/style/sources/raster_array_source.cpp` 的 `RasterArrayTimelineRuntime` 持有 entries、cursor (`t0`/`mix`)、speed、state、gap/invalid、monotonic clock;`play/pause/setSpeed/seek/step/recover` 是低频 Core 命令。
|
||||||
|
- 同文件的 `RasterArraySource::updateRuntimeFieldWindow()` 是已可作为正式输入窗口 API 的最小接入点:检查 validity/compatibility/content identity/generation,推进 T0/T1 并通知 source changed。`advanceRuntimeFieldWindow()`、`repeatRuntimeFieldWindow()` 和 `controlRuntimeTimeline()` 是 validation convenience/control 面。
|
||||||
|
- `tryInitializeTimeline()` 目前将 initial/next/runtime fixture 解码为 6 个 Source-owned snapshot,Validation cache capacity=8;这是有界 validation preload,并非生产 forecast cache。
|
||||||
|
- `src/mbgl/renderer/layers/render_particle_validation_layer.cpp` 的 `RenderRasterParticleLayer` 读取 `RenderRasterArraySource::impl().field/nextField` 或 shared timeline;Field identity 决定 GPU upload/reuse,window overlap 保留旧 T1 作为新 T0。其 `ParticleGpuValidationTweaker::execute()` State pass 负责连续 tick/UBO mix;无需 Android per-frame JNI。
|
||||||
|
|
||||||
|
### RasterParticleLayer 与 Renderer
|
||||||
|
|
||||||
|
- `include/mbgl/style/layers/raster_particle_layer.hpp` / `src/mbgl/style/layers/raster_particle_layer_impl.hpp` 定义正式 layer 和强类型 active、mask、timeMix、count、streak/trail/color properties。
|
||||||
|
- GPU state/trail/field texture 生命周期在 `render_particle_validation_layer.cpp` 的 `ParticleGpuResources`;visual property 更新只更新 resources/UBO,不要求 Source reload。
|
||||||
|
- Renderer 已是正式 raster-particle 路径,但同一实现文件仍含 validation coast mask、legacy validation display 和 fixture diagnostics。这些属于验证快捷路径,不应让 provider/network 进入 renderer。
|
||||||
|
|
||||||
|
### 数据输入边界
|
||||||
|
|
||||||
|
- `src/mbgl/particle/field/offline_fixture_particle_adapter.hpp/.cpp` 的 `ParticleFieldSnapshot` 是 backend-neutral immutable value;包含网格、EPSG:4326 bounds、canonical U=east/V=north、NoData、dataVersion、generation、SHA-256、velocity/valid。
|
||||||
|
- `OfflineFixtureParticleAdapter::load(bytes)` 已是纯 C++ bytes loader,但它强制 fixture-v1 schema 和 `knownFixture()`(east/north/vortex/nodata)。`RasterArraySource::loadDescription()` 从 FileSource 请求 metadata/field bytes,再调用它。
|
||||||
|
- Android `ParticleCoreLayerValidationActivity.kt::validationStyle()` 拼 `asset://integration/particle-fixtures/...` 与 runtime fixture descriptors;Kotlin 是 Validation Host,不持有 T0/T1/tick/clock,但确实构造 synthetic fixture source。
|
||||||
|
|
||||||
|
### Android/JNI 与 WeatherColorLayer
|
||||||
|
|
||||||
|
- Android 的 Timeline 控制经 `MapLibreMap → NativeMapView → native_map_view.cpp::controlRasterArraySourceTimeline()` 低频进入 Source;没有 per-frame JNI。TIME/VIS/COUNT/MAP/MORE 都是 Test App 验证面,不应进入正式业务链路。
|
||||||
|
- 当前仓库没有 `WeatherColorLayer` 实现类或 renderer 文件;它只出现在架构规范,故与当前 `RasterParticleLayer` 没有运行时 source/field/timeline 耦合。下一阶段不应实现或合并它。
|
||||||
|
|
||||||
|
## Validation-only 技术债
|
||||||
|
|
||||||
|
状态栏自动 MIX、WAITING/INVALID 可观测性不够明显,已验证为 `NON-BLOCKING`。它适合未来有 Android 综合 APK 时顺带改善,不能单独触发一次 Android build,也不是 Timeline/Renderer failure。
|
||||||
|
|
||||||
|
## 候选阶段
|
||||||
|
|
||||||
|
| 候选 | 目标与价值 | 风险/冻结边界 | Android 综合验收 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| A:Provider-neutral C++ Data Adapter | 将 fixture-only decode contract 抽为通用 immutable snapshot adapter,保留 offline fixture adapter 为一个实现;使 Weather Service 未来可向 Source 提交数据而不进入 renderer。 | 中等;必须维持 snapshot identity、NoData、U/V、T0/T1/reuse/fail-closed。 | 高,适合一次验证 raw/in-memory T0/T1、A/B→B/C、invalid。 |
|
||||||
|
| B:生产 Renderer 清理 | 从 renderer 中拆 validation coast/diagnostics。 | 高,容易触碰已验证 particle path,当前没有真实 data input 的收益低。 | 中等,不推荐现在做。 |
|
||||||
|
| C:Validation UI 可观测性 | 状态行显示真实 playback state。 | 低但仅工具收益,不能单独编译。 | 仅作为 A 的附带项。 |
|
||||||
|
| D:WeatherColorLayer | 增加量化颜色层。 | 新模块且缺正式输入 contract,会提前耦合两层。 | 不推荐现在做。 |
|
||||||
|
|
||||||
|
## 推荐:STEP-048「Provider-neutral C++ Field Data Adapter 与 Runtime 输入契约」
|
||||||
|
|
||||||
|
英文短名:`Field Data Adapter Contract`。
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
1. 定义 provider-neutral C++ metadata+bytes → immutable `ParticleFieldSnapshot` adapter contract,解除 `knownFixture()` 对正式输入的限制。
|
||||||
|
2. 保持 `OfflineFixtureParticleAdapter` 为 fixture-v1 validation implementation,不让 Android/asset API 跨入 Core adapter。
|
||||||
|
3. 为 Source 增加面向已解码 snapshot/时间片描述的明确生产入口,复用现有 `updateRuntimeFieldWindow()` 的 T0/T1 identity、reuse、NOOP、fail-closed 语义。
|
||||||
|
4. 为 invalid/incompatible/provider decode failure 给出明确 error,不污染当前窗口。
|
||||||
|
5. 将 Validation UI observability 作为同一 APK 的小附带项,而非单独阶段。
|
||||||
|
|
||||||
|
### 不做什么
|
||||||
|
|
||||||
|
不接真实 HTTP/GRIB/provider/vendor;不改 Timeline clock、Particle Renderer/shader、GPU simulation、WeatherColorLayer、iOS/Metal/Vulkan、React Native、容量或独立 UI 重构。
|
||||||
|
|
||||||
|
### 最小修改面(预期)
|
||||||
|
|
||||||
|
- Core:新增 `src/mbgl/particle/field/` 下 provider-neutral adapter/interface 与定向 C++ tests;保留 `ParticleFieldSnapshot` ownership(adapter 创建 immutable value,Source 接管/复制一次)。
|
||||||
|
- Source:`raster_array_source.hpp/.cpp` 仅增加显式 production submit/decode boundary;复用现有 runtime window API。
|
||||||
|
- Renderer:不需要修改,除非为读取已经存在的 snapshot identity 做极小编译适配。
|
||||||
|
- Android:Test App 仅增加一次可发现的 in-memory/provider-neutral validation scenario 与状态展示;JNI 只发低频 submit 命令,绝无 per-frame API。
|
||||||
|
- 文档:STEP/报告/测试矩阵。
|
||||||
|
|
||||||
|
### 接口方向与生命周期
|
||||||
|
|
||||||
|
adapter 输入为 provider-neutral descriptor(logical timestamp、field type/domain/grid/vector/NoData/integrity)与 bytes;输出 immutable snapshot 或 structured decode error。Adapter 不拥有 GPU、Style、Renderer 或网络。Source 是当前 T0/T1/timeline authority;Renderer 只消费 snapshot/GPU textures。decode invalid、time interpolation incompatible、generation/content identity conflict 均 fail-closed,继续最后有效 window。
|
||||||
|
|
||||||
|
### 一次 Android 综合验收
|
||||||
|
|
||||||
|
Codex 完成完整批次后,用户在 Linux 一次构建,Windows 一次 copy/install,再在设备 `192.168.1.146` 用 `adb -s 192.168.1.146 ...` 验收:初始 provider-neutral A/B;A/B→B/C→C/D reuse/only-new-T1;duplicate NOOP;invalid/incompatible fail-closed/recover;PLAY/PAUSE/MIX/VIS/MAP 回归。命令设计为 Windows PowerShell/CMD,不依赖 Linux grep/sed/awk/tee。
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
推荐直接编写 STEP-048 实现任务书。需要一个小型 C++ adapter contract(新模块/文件,非新 renderer 架构);不需要新的 Renderer、Timeline 或 WeatherColorLayer 架构。
|
||||||
199
docs/20_调查/STEP-050_正式数据链路下一阶段调查报告_v1.md
Normal file
199
docs/20_调查/STEP-050_正式数据链路下一阶段调查报告_v1.md
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# STEP-050 正式数据链路下一阶段调查报告
|
||||||
|
|
||||||
|
## 1. Git / 调查范围
|
||||||
|
|
||||||
|
- 仓库:`MapLibreParticleLab`
|
||||||
|
- 分支:`particle/main`
|
||||||
|
- Starting HEAD:`1572b6c3b1bcc2ab0007181bad0609e7d0134575`
|
||||||
|
- `origin/particle/main`:`1572b6c3b1bcc2ab0007181bad0609e7d0134575`
|
||||||
|
- Gitea API 分支 HEAD:同上
|
||||||
|
- 调查性质:代码与既有文档只读调查;未修改 Core、Renderer、Android、Tests 或 build files。
|
||||||
|
|
||||||
|
## 2. 结论摘要
|
||||||
|
|
||||||
|
STEP-049 的 provider-neutral contract 已经存在,但当前真正落点仍是 Core 内部的“已校验 bytes → immutable Snapshot”边界,不是生产数据源。当前实际链路为:
|
||||||
|
|
||||||
|
`FileSource::request(Resource::source(...))`
|
||||||
|
→ `RasterArraySource` 收集 metadata/field bytes
|
||||||
|
→ `OfflineFixtureParticleAdapter::load()` 解析并校验 `fixture-v1`
|
||||||
|
→ `ParticleFieldDataAdapter::decode()`
|
||||||
|
→ `ParticleFieldSnapshot`
|
||||||
|
→ `RasterArraySource::Impl::field/nextField`
|
||||||
|
→ `RasterArrayTimelineRuntime` / `updateRuntimeFieldWindow()`
|
||||||
|
→ Renderer。
|
||||||
|
|
||||||
|
仓库没有 Weather Service client、provider abstraction、正式天气网络 schema、GRIB/NetCDF decoder、Particle 生产 cache 或 production feed API。MapLibre 的通用 `FileSource`/HTTP 能力不能视为 Particle Weather Service。
|
||||||
|
|
||||||
|
推荐 STEP-051:**Local Production-like Field Feed 与 Core Snapshot Submit Bridge**。先用本地/离线外部 binary feed 驱动正式 neutral adapter 和既有 Runtime,建立稳定的 production-like producer/submit 边界,再在后续独立阶段讨论 provider/network decode。
|
||||||
|
|
||||||
|
## 3. ParticleFieldDataAdapter 现状
|
||||||
|
|
||||||
|
### 3.1 API 与真实文件
|
||||||
|
|
||||||
|
- `include/mbgl/particle/field/field_data_adapter.hpp`
|
||||||
|
- `ParticleFieldDescriptor`:`fieldID`、`fieldType`、`displayDomain`、`validTime`、尺寸、地理 bounds、`noData`、`dataVersion`、`byteLength`、`stride`、`layout`、`encoding`、可选 `fieldSha256`。
|
||||||
|
- `ParticleFieldSnapshot`:拥有自己的 `std::string` 与 `std::vector<float>`/`std::vector<uint8_t>`,保存 canonical Runtime 数据和 `generation`。
|
||||||
|
- `ParticleFieldAdapterError` / `ParticleFieldAdapterResult`:结构化错误与可日志化 message。
|
||||||
|
- `ParticleFieldDataAdapter::decode(const ParticleFieldDescriptor&, const std::vector<char>&, uint64_t generation)`:当前唯一正式 decode API。
|
||||||
|
- `src/mbgl/particle/field/field_data_adapter.cpp`
|
||||||
|
- 校验 identity/generation、dimensions/bounds/noData、`interleaved-uv`、`float32-le`、stride、byte length 与 finite data。
|
||||||
|
- 将 payload copy 到 Snapshot;adapter 不保存外部 bytes、Android 对象、Timeline、Renderer resource 或 provider 对象。
|
||||||
|
- 当前 renderer 语义仍限定 `WIND` + `GLOBAL`,Wave/Tide/Current 尚未实现。
|
||||||
|
- `src/mbgl/particle/field/offline_fixture_particle_adapter.cpp/.hpp`
|
||||||
|
- `OfflineFixtureParticleAdapter::load()` 是当前 caller 的 validation wrapper:解析 `maplibre-particle-fixture/v1`、校验 fixture whitelist、metadata、checksum,再构造 neutral descriptor 调正式 adapter。
|
||||||
|
|
||||||
|
### 3.2 Caller 与 generation/identity
|
||||||
|
|
||||||
|
当前调用者为:
|
||||||
|
|
||||||
|
1. `src/mbgl/style/sources/raster_array_source.cpp::tryLoad()`:初始 T0/T1 使用 `nextRasterArrayFieldGeneration`,再写入 `Impl::field/nextField`。
|
||||||
|
2. `RasterArraySource::tryLoadRuntimeField()`:runtime candidate bytes 完整后调用 fixture wrapper,candidate 在 adapter 前获得 Core generation。
|
||||||
|
3. `src/mbgl/renderer/layers/render_particle_validation_layer.cpp`:Validation renderer 的 fixture-only fallback/validation 资源路径仍调用 fixture wrapper。
|
||||||
|
4. `test/style/source.test.cpp`:直接 adapter、fixture wrapper、Runtime Window 与 Timeline 定向测试。
|
||||||
|
|
||||||
|
因此当前没有 Weather provider 或 production caller。`generation` 是 Core runtime revision,由 Source/Runtime 负责分配;adapter 只校验调用者传入的非零 generation。`fieldID`/identity 由 descriptor producer 提供,adapter 不生成业务 identity。
|
||||||
|
|
||||||
|
## 4. ParticleFieldSnapshot ownership 与正式输入适配性
|
||||||
|
|
||||||
|
Snapshot 已基本 provider-neutral:不含 URL、token、provider 名称、网络客户端、Android 对象或 GPU resource;vector ownership 稳定,适合作为 Runtime 输入。现存 fixture 语义不再进入 Snapshot,fixture ID 在 wrapper 中映射为 `fieldID`。
|
||||||
|
|
||||||
|
它已包含当前 Runtime 所需的最小 grid/field metadata,但存在两项生产化边界:
|
||||||
|
|
||||||
|
- `validTime` 已存在于 neutral descriptor/Snapshot,但当前 fixture metadata 的 `timestamp` 未映射,Timeline Source 仍使用固定逻辑时间。
|
||||||
|
- Snapshot 的 `isValid()` 目前按 Wind/Global renderer 语义判定;未来 Ocean-only field/mask 需要另行设计,不应在本阶段偷偷扩大。
|
||||||
|
|
||||||
|
## 5. RasterArraySource / Runtime submit 入口
|
||||||
|
|
||||||
|
### 5.1 当前入口
|
||||||
|
|
||||||
|
`include/mbgl/style/sources/raster_array_source.hpp` 提供:
|
||||||
|
|
||||||
|
- `updateRuntimeFieldWindow(ParticleFieldSnapshot newT1)`:已校验 Snapshot 进入唯一 authoritative T0/T1 Window 的正式入口。
|
||||||
|
- `advanceRuntimeFieldWindow()` / `repeatRuntimeFieldWindow()`:当前 Source-owned validation convenience API。
|
||||||
|
- `controlRuntimeTimeline()`:Android 低频命令桥,Core 持有 Timeline/Playback。
|
||||||
|
|
||||||
|
如果未来已经拥有一个合法 `ParticleFieldSnapshot`,最短路径是直接调用 `updateRuntimeFieldWindow(std::move(snapshot))`;它会复用现有 `OLD_T1 → NEW_T0`、only `NEW_T1`、Duplicate NOOP 和 fail-closed 逻辑。当前该路径只适合已有 T0/T1 的 Source,尚未提供独立的 production feed/submit queue 或外部 source ownership bridge。
|
||||||
|
|
||||||
|
### 5.2 当前 Source 的 fixture 限制
|
||||||
|
|
||||||
|
`src/mbgl/style/conversion/source.cpp::convertRasterArraySource()` 要求 `fixture`、`metadata`、`field`,runtime candidates 要求 `fixture`、`metadata`、`field`。`RasterArraySource::loadDescription()` 通过 MapLibre `FileSource` 请求 bytes,但随后仍直接调用 fixture wrapper。
|
||||||
|
|
||||||
|
所以 Source 已有可复用的异步 byte transport,却不是 production-neutral source:它的 style schema、成员命名、metadata parser 和 candidate preload 仍是 fixture validation 入口。下一阶段应增加一个极薄的 neutral feed/submit bridge,或把现有 Source 输入适配到 neutral descriptor;不应复制第二套 Window。
|
||||||
|
|
||||||
|
## 6. Timeline snapshot 数据来源
|
||||||
|
|
||||||
|
`RasterArraySource::tryInitializeTimeline()` 构造 `RasterArrayTimelineRuntime::Entry`:
|
||||||
|
|
||||||
|
- `Impl::field` 作为 index 0/T0;
|
||||||
|
- `Impl::nextField` 作为 index 1/T1;
|
||||||
|
- `runtimeFields[index].snapshot` 作为后续 candidates;
|
||||||
|
- 逻辑时间当前硬编码为 `0`、`40000`、`80000 + index * 40000`。
|
||||||
|
|
||||||
|
`RasterArrayTimelineRuntime` 本身只消费 immutable Snapshot shared pointers、严格递增 timestamp 和有限 cache(capacity 8),其 `play/pause/tick/seek/step/gap/invalid/recover` 不认识 fixture schema。它能够消费外部 production Snapshot,但当前 Source 的 builder 只能从 fixture candidate 生成 Snapshot,且 candidate preload 仍由 fixture wrapper 完成。
|
||||||
|
|
||||||
|
结论:下一阶段原则上不改 Timeline;提供新的 Snapshot producer,并在 Source 侧把 producer 结果接到现有 entries/submit 入口即可。若生产 feed 需要真实 forecast timestamp,应在 producer/Source metadata 接线中补齐 Entry timestamp,而不是重做 Playback。
|
||||||
|
|
||||||
|
## 7. 仓库已有数据源能力与边界
|
||||||
|
|
||||||
|
### 已存在但不是 Particle 生产业务
|
||||||
|
|
||||||
|
- MapLibre 通用 `FileSource`、`Resource::source()`、异步 `request()`:可请求 asset/file/HTTP 资源,已有于 `GeoJSONSource`、`ImageSource`、`TileSource` 等通用 Source。
|
||||||
|
- `src/mbgl/util/io.cpp::read_file()`:通用本地文件工具。
|
||||||
|
- `platform/default/src/mbgl/storage/offline_database.cpp`、offline download:MapLibre 离线资源能力,不是天气字段 decoder 或 Particle field cache policy。
|
||||||
|
- `test/fixtures/resources` 和 `particle-fixtures/*/field.f32`:测试资源。
|
||||||
|
|
||||||
|
### 不存在
|
||||||
|
|
||||||
|
搜索 `weather`、`forecast`、`provider`、`grib`、`netcdf`、`windy` 等 Particle 业务能力后,没有 Weather Service client、provider registry/factory、外部天气 schema、GRIB/NetCDF decoder、production field downloader 或专用 cache。当前唯一 binary field loader 是 fixture wrapper 的 `std::vector<char>` 读取/异步 bytes 路径;没有 production local binary source。
|
||||||
|
|
||||||
|
因此不能因为 MapLibre 有 HTTP/FileSource 就直接引入网络。当前最小 production-like 验证应使用本地或离线 bytes,保持 provider-specific decode 在 adapter 上游,Renderer 只消费 Snapshot。
|
||||||
|
|
||||||
|
## 8. 候选方案比较
|
||||||
|
|
||||||
|
### 候选 A:Core Production Field Feed / Snapshot Submit API
|
||||||
|
|
||||||
|
- 目标:建立接收已校验 descriptor/payload 或 Snapshot 的极薄 Core feed bridge。
|
||||||
|
- 原因:当前 adapter 已稳定,但 Source 仍只有 fixture style schema;A 能先解耦 producer 与 fixture schema。
|
||||||
|
- 涉及:新建小型 Core feed 类型,最小调整 `RasterArraySource`;复用 `ParticleFieldDataAdapter` 与 `updateRuntimeFieldWindow()`。
|
||||||
|
- 新模块/新架构:可有一个小模块,不建立 Provider Framework。
|
||||||
|
- network/vendor:不需要;不允许进入 Renderer。
|
||||||
|
- Timeline/Renderer:不修改 Timeline 语义,不修改 Renderer。
|
||||||
|
- RasterArraySource:需要最小 submit/initial-window 接线。
|
||||||
|
- Android APK:若只做 Linux Core contract,可不需要;若增加 validation entry,则和 B 合并后一次 APK。
|
||||||
|
- 风险:初始 T0/T1 建立、generation 分配与 timestamp ownership 需要明确,不能让 feed 管理第二套 Window。
|
||||||
|
|
||||||
|
### 候选 B:Local Binary / File-backed Production-like Field Source
|
||||||
|
|
||||||
|
- 目标:用真实外部 binary + neutral metadata(不含 fixture whitelist/provider 信息)驱动 adapter、Timeline 和 Runtime,替代 synthetic fixture 验证生产链路。
|
||||||
|
- 原因:当前缺口最具体,能验证 payload ownership、时间 metadata、preload 和 fail-closed,而不提前绑定天气供应商。
|
||||||
|
- 涉及:local/asset feed loader、neutral metadata decode、`RasterArraySource` 最小 bridge、离线 fixture/test resource。
|
||||||
|
- 新模块/新架构:一个轻量 source/loader;不需要 Provider Framework。
|
||||||
|
- network/vendor:不需要;provider-specific decode 留在上游边界。
|
||||||
|
- Timeline/Renderer:原则上不修改 Timeline/Renderer;复用现有 entries 与 Window。
|
||||||
|
- RasterArraySource:需要最小修改,使 style/source 不再强制要求 `fixture`,或增加明确 production-like feed 构造入口。
|
||||||
|
- Android APK:建议需要一次,用同一 APK 覆盖 local feed、Timeline、Window/Playback 回归;不单独拆 build。
|
||||||
|
- 风险:本地 metadata schema 若设计过大,会重新形成第二套业务 schema;必须只覆盖当前 descriptor 与 forecast timestamp。
|
||||||
|
|
||||||
|
### 候选 C:Weather Service / Network Provider 接入骨架
|
||||||
|
|
||||||
|
- 目标:直接建立网络 weather service/provider client。
|
||||||
|
- 原因:最终产品需要,但当前仓库没有成熟服务边界或外部 decoder。
|
||||||
|
- 涉及:Android/Core network、鉴权、retry、cache、provider schema 和 decoder。
|
||||||
|
- 新模块/新架构:会引入明显新架构。
|
||||||
|
- network/vendor:需要,且风险最高;绝不能进入 Renderer。
|
||||||
|
- Timeline/Renderer:应不改 Timeline/Renderer,但上游数据生命周期和错误映射复杂。
|
||||||
|
- RasterArraySource:可能需要异步 feed/preload API。
|
||||||
|
- Android APK:需要,且会显著扩大综合验收。
|
||||||
|
- 风险:违反第一阶段离线边界,容易把 vendor/network 业务塞进 Core/Android 或模糊 fail-closed 语义。当前不推荐。
|
||||||
|
|
||||||
|
### 候选 D:Timeline/Preload 通用化
|
||||||
|
|
||||||
|
- 目标:先把 Timeline candidate/cache 从 fixture 命名和固定逻辑时间中抽象出来。
|
||||||
|
- 原因:当前 `RasterArrayTimelineRuntime` 的消费接口其实已 neutral,只有 Source builder 仍 fixture-specific。
|
||||||
|
- 涉及:`RasterArraySource` builder、timestamp metadata、candidate ownership。
|
||||||
|
- 新模块/新架构:不应新增模块;属于现有 Source 小范围整理。
|
||||||
|
- network/vendor:不需要。
|
||||||
|
- Timeline/Renderer:尽量不修改 Timeline state machine,可能需要 Entry timestamp 接线;Renderer 不改。
|
||||||
|
- Android APK:只有和 A/B 合并时需要一次。
|
||||||
|
- 风险:单独做 D 会在没有真实 production producer 的情况下提前抽象,收益小于 B;不得变成第二套 cache/state machine。
|
||||||
|
|
||||||
|
## 9. 推荐下一实现阶段
|
||||||
|
|
||||||
|
- STEP 编号:`STEP-051`
|
||||||
|
- 中文名称:**Local Production-like Field Feed 与 Core Snapshot Submit Bridge**
|
||||||
|
- 英文短名:`Local Production-like Field Feed`
|
||||||
|
|
||||||
|
目标范围:
|
||||||
|
|
||||||
|
1. 定义一个最小 local/external binary feed 输入,使用 neutral descriptor + bytes,不使用 fixture whitelist。
|
||||||
|
2. 让该 feed 通过 `ParticleFieldDataAdapter` 生成 immutable Snapshot,并明确 generation、identity、validTime 和 payload ownership。
|
||||||
|
3. 在 `RasterArraySource` 增加最小 initial-window/next-feed bridge,继续调用既有 `updateRuntimeFieldWindow()` 和唯一 Timeline。
|
||||||
|
4. 让 Timeline 使用 producer 提供的 forecast timestamps,验证 preload、PLAY、auto MIX、boundary、Gap/Invalid fail-closed。
|
||||||
|
5. 仅使用离线 local/asset payload;增加 C++/Core 定向测试与一次 Android 综合验证入口。
|
||||||
|
|
||||||
|
明确不做:
|
||||||
|
|
||||||
|
- 不进入 iOS/Metal;
|
||||||
|
- 不重做 Timeline、Playback 或 Runtime Field Window;
|
||||||
|
- 不重做 Renderer/GPU simulation/trail;
|
||||||
|
- 不把 provider/network/vendor 逻辑放入 Renderer;
|
||||||
|
- 不实现 Weather Service、HTTP client、鉴权、retry、cache server 或真实 provider decoder;
|
||||||
|
- 不合并 WeatherColorLayer 与 RasterParticleLayer;
|
||||||
|
- 不为 MIX observability 单独触发 Android build;
|
||||||
|
- 不为 Rotation/Viewport 技术债单独触发 Android build。
|
||||||
|
|
||||||
|
## 10. Rotation / Viewport 与 MIX 技术债
|
||||||
|
|
||||||
|
当前 `ParticleCoreLayerValidationActivity.kt` 具备 `onStart/onResume/onPause/onStop/onDestroy` 对 `MapView` 的标准生命周期转发,并通过 `onSaveInstanceState()` 保存 Validation 控件状态;代码中没有独立的 Particle viewport/camera 恢复状态机。历史文档仍记录过 Rotation/Viewport 自动恢复延期与后续 control-mode 恢复,现状不足以把它当作生产数据链路已完全证明的 contract。
|
||||||
|
|
||||||
|
STEP-051 推荐阶段不需要修改 Android Validation Host、viewport 或 source reload,因此 Rotation/Viewport 保持独立技术债,不合并、不为其单独 build。若 STEP-051 为增加 local feed 入口必须改 UI,则只在同一 APK 中顺带回归,不扩展为独立修复。
|
||||||
|
|
||||||
|
MIX UI 文本滞后仍是 `NON-BLOCKING observability`;Core MIX 已通过。STEP-051 若本来增加 Android 状态入口,可顺带刷新显示;若不需要 UI 变化则继续保留,不单独触发 build。
|
||||||
|
|
||||||
|
## 11. Android 成本与一次验收建议
|
||||||
|
|
||||||
|
STEP-051 先完成 Linux Core/OpenGL 精确编译、adapter/source/timeline 定向测试、离线 local feed 资源与验证入口,再由用户用一次 APK 覆盖:local feed load、Timeline READY/PLAY、auto MIX、至少两个 Window boundary、OLD_T1 reuse、only NEW_T1 upload、Pause/Resume、Step/Seek、Gap/Invalid、Duplicate NOOP、Renderer fail-closed、rotation/lifecycle 回归(若入口有改动)。不执行 Jest;Codex 不执行 Android Gradle、APK、ADB 或 App 启动。
|
||||||
|
|
||||||
|
## 12. 是否可进入 STEP-051 实现 MD
|
||||||
|
|
||||||
|
可以。调查已确定最小实现方向、边界和验收策略:先写 STEP-051 设计/任务 MD,冻结 local feed descriptor/metadata、timestamp/generation ownership 与 Source bridge,再实现。不要在 STEP-051 直接引入 Weather Service/network/provider framework。
|
||||||
225
docs/20_调查/STEP-052_正式数据源接入下一阶段调查报告_v1.md
Normal file
225
docs/20_调查/STEP-052_正式数据源接入下一阶段调查报告_v1.md
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
# STEP-052 正式数据源接入下一阶段调查报告
|
||||||
|
|
||||||
|
## 1. Git / 调查基线
|
||||||
|
|
||||||
|
- 仓库:`MapLibreParticleLab`
|
||||||
|
- 分支:`particle/main`
|
||||||
|
- Starting HEAD:`e4ab1b9d421a4559fa3627df07ee91dcd9b680e4`
|
||||||
|
- `origin/particle/main`:`e4ab1b9d421a4559fa3627df07ee91dcd9b680e4`
|
||||||
|
- STEP-049:`CLOSED / PASS`
|
||||||
|
- STEP-051:`CLOSED / PASS`
|
||||||
|
- 调查性质:代码与既有文档只读;本任务不修改功能代码。
|
||||||
|
|
||||||
|
## 2. 结论摘要
|
||||||
|
|
||||||
|
STEP-051 已把一条真实可验证的 local production-like 链路接到 Core:
|
||||||
|
|
||||||
|
`asset metadata + binary payload → LocalParticleFieldFeed → ParticleFieldDescriptor → ParticleFieldDataAdapter → ParticleFieldSnapshot → RasterArraySource → Core Timeline/Runtime Field Window → Renderer`。
|
||||||
|
|
||||||
|
但它仍不是正式生产数据源。当前 local metadata 是新的 `maplibre-particle-field/v1`,payload 仍复用仓库已有的 canonical `particle-fixtures/*/field.f32`;`RasterArraySource` 直接拥有资源请求、candidate 收集、解析和 Timeline 初始化。仓库没有 Particle 专属的 feed coordinator、forecast catalog/manifest、异步 field-arrival API、retry/timeout/stale policy 或专用 transport cache。
|
||||||
|
|
||||||
|
MapLibre 通用 `FileSource` 已提供 asset、filesystem、HTTP、offline database 等资源能力,但这不等于已有 Weather Service。当前最合理的下一实现阶段不是直接绑定天气供应商,而是先建立 provider-neutral feed/manifest 与低频 arrival coordinator,并用 Local/File/Mock transport 验证。之后再单独接入真实 HTTP/provider。
|
||||||
|
|
||||||
|
## 3. STEP-051 后的真实数据链路
|
||||||
|
|
||||||
|
### 3.1 Local feed 起点与终点
|
||||||
|
|
||||||
|
入口和 contract:
|
||||||
|
|
||||||
|
- `include/mbgl/particle/field/local_particle_field_feed.hpp`
|
||||||
|
- `src/mbgl/particle/field/local_particle_field_feed.cpp`
|
||||||
|
- metadata:`particle-production/field-00.json` 至 `field-03.json`
|
||||||
|
- Android metadata:`platform/android/MapLibreAndroidTestApp/src/main/assets/integration/particle-production/`
|
||||||
|
|
||||||
|
`LocalParticleFieldFeed::load(metadataText, payload)` 只处理 provider-neutral metadata 和内存 bytes,调用唯一正式 `ParticleFieldDataAdapter::decode()`。它不打开文件、不请求网络、不保存缓存,也不认识 Android、Timeline 或 Renderer。
|
||||||
|
|
||||||
|
STEP-051 的 Android `ParticleCoreLayerValidationActivity.kt` 使用 `production-fields` style source,metadata 走 `asset://integration/particle-production/field-*.json`,binary 仍来自既有 `asset://integration/particle-fixtures/*/field.f32`。这已经脱离 fixture metadata/wrapper,但 payload 内容尚是验证用 canonical binary,而非远程生产数据。
|
||||||
|
|
||||||
|
### 3.2 Provider-neutral 边界
|
||||||
|
|
||||||
|
正式 neutral 边界位于:
|
||||||
|
|
||||||
|
`ParticleFieldDescriptor + payload bytes → ParticleFieldDataAdapter → immutable ParticleFieldSnapshot`。
|
||||||
|
|
||||||
|
`include/mbgl/particle/field/field_data_adapter.hpp` 的 Descriptor/Snapshot 不含 URL、token、HTTP client、provider name、Android object 或 GPU ownership。Descriptor/Snapshot 当前满足 WIND/GLOBAL 的 Runtime 最小输入;Ocean-only field、mask、更多物理变量尚未进入本阶段 contract。
|
||||||
|
|
||||||
|
当前仍留在上游/Source 的内容是资源 URL、metadata 文本、文件请求、asset 读取和 candidate 编排。它们不应进入 `RasterParticleLayer` 或 `ParticleFieldSnapshot`。
|
||||||
|
|
||||||
|
## 4. Adapter、Snapshot 与 Source 现状
|
||||||
|
|
||||||
|
### 4.1 Adapter / Snapshot
|
||||||
|
|
||||||
|
- `ParticleFieldDataAdapter::decode(const ParticleFieldDescriptor&, const std::vector<char>&, uint64_t)` 是唯一正式 decode API。
|
||||||
|
- `ParticleFieldDescriptor` 已有 `fieldID`、`fieldType`、`displayDomain`、`generation`、`validTime`、dimensions、bounds、`noData`、`dataVersion`、`byteLength`、stride、layout、encoding、可选 checksum。
|
||||||
|
- `ParticleFieldSnapshot` 自有 metadata、velocity 和 valid arrays;不拥有外部 bytes、FileSource、GPU texture 或 Timeline。
|
||||||
|
- generation 必须由 producer/source 在 adapter 前提供,adapter 不把 0 改成 1;`fieldId` 与 `validTime` 由 producer metadata 提供。
|
||||||
|
- `OfflineFixtureParticleAdapter` 仍存在,但只是 fixture-v1 validation wrapper,不是正式 production feed。
|
||||||
|
|
||||||
|
### 4.2 RasterArraySource / submit
|
||||||
|
|
||||||
|
实际文件:
|
||||||
|
|
||||||
|
- `include/mbgl/style/sources/raster_array_source.hpp`
|
||||||
|
- `src/mbgl/style/sources/raster_array_source.cpp`
|
||||||
|
- `src/mbgl/style/sources/raster_array_source_impl.hpp`
|
||||||
|
- `src/mbgl/style/conversion/source.cpp`
|
||||||
|
|
||||||
|
STEP-051 新增 `RasterArrayLocalFieldDescriptor`、local candidates 和 `submitInitialRuntimeFieldWindow(T0, T1)`。Source 对每个 metadata/field URL 使用 `FileSource::request(Resource::source(...))`,收齐后交给 `LocalParticleFieldFeed`,再由 bridge 建立 authoritative initial T0/T1。后续 Timeline 从 local candidates 构造 entries,使用 Snapshot 的 producer `validTime`。
|
||||||
|
|
||||||
|
已有 `updateRuntimeFieldWindow(ParticleFieldSnapshot)` 是已有 T0/T1 之后的最短 submit 路径,继续实现 `OLD_T1 → NEW_T0`、only NEW_T1、Duplicate NOOP 和 fail-closed。它不是一个独立的外部 feed queue,也没有 feed-level arrival state、manifest index 或持久化生命周期。
|
||||||
|
|
||||||
|
### 4.3 Timeline / Playback
|
||||||
|
|
||||||
|
`RasterArrayTimelineRuntime` 位于 `raster_array_source.hpp/.cpp`,只消费 immutable Snapshot entries 和严格递增 timestamp,持有 Core-owned cursor/speed/paused/gap/invalid 状态;cache capacity 当前为 8。
|
||||||
|
|
||||||
|
这属于 Timeline memory cache,不是 transport/download cache,也不是 persistent field cache。Timeline 已足够消费外部 Snapshot,下一阶段不应重写其 state machine 或 Playback ownership。缺口在 Source/feed producer 如何按时间目录、到达、失败和预取 Snapshot。
|
||||||
|
|
||||||
|
## 5. 仓库已有 transport / network / cache 能力
|
||||||
|
|
||||||
|
### 已有但属于 MapLibre 通用基础设施
|
||||||
|
|
||||||
|
- `include/mbgl/storage/file_source.hpp`:异步 `request()`、`Resource`、cache-only 能力、pause/resume 和资源转发。
|
||||||
|
- `include/mbgl/storage/resource.hpp`:`asset/file/network` 资源请求语义、loading method、storage policy、etag/expiry 等 HTTP/cache 元数据。
|
||||||
|
- `src/mbgl/storage/file_source_manager.cpp`:按类型与配置复用 FileSource 实例。
|
||||||
|
- `platform/android/MapLibreAndroid/src/cpp/asset_manager_file_source.cpp`:Android `asset://` 读取。
|
||||||
|
- `platform/android/MapLibreAndroid/src/cpp/http_file_source.cpp` 与 `include/mbgl/storage/http_file_source.hpp`:Android 通用 HTTP FileSource。
|
||||||
|
- `include/mbgl/storage/database_file_source.hpp`:MapLibre ambient cache/offline database 的 put、invalidate、clear、容量和 revalidation API。
|
||||||
|
- `include/mbgl/util/lru_cache.hpp`:通用内存 LRU 工具。
|
||||||
|
- `include/mbgl/storage/response.hpp`:通用成功、NotFound、Server、Connection、RateLimit、etag、expires、retryAfter 等 Response/Error 字段。
|
||||||
|
|
||||||
|
这些能力可以作为 STEP-053 的底层 transport 适配点,但目前没有 Particle field 的 manifest、字段语义、时间序列目录、payload checksum policy、retry/stale policy 或 adapter 错误映射。
|
||||||
|
|
||||||
|
### 当前不存在的 Particle 正式能力
|
||||||
|
|
||||||
|
搜索当前 `src/`、`include/`、Android JNI/Host 和测试区域,没有发现:
|
||||||
|
|
||||||
|
- Weather Service client、Open-Meteo/Windy/vendor client;
|
||||||
|
- Particle provider abstraction、provider registry 或 provider-specific decoder;
|
||||||
|
- GRIB/NetCDF/external weather decoder;
|
||||||
|
- Particle remote manifest/forecast endpoint contract;
|
||||||
|
- Particle 专用 download queue、transport coordinator、persistent field cache;
|
||||||
|
- Particle retry、timeout、stale-data、gap inventory 或 availability state policy;
|
||||||
|
- token/auth/config 与 Particle field service 的绑定。
|
||||||
|
|
||||||
|
因此不能把 MapLibre 的通用 HTTP/FileSource 误认成 Weather Service 已接入。
|
||||||
|
|
||||||
|
## 6. Cache、preload 与数据生命周期
|
||||||
|
|
||||||
|
当前存在四种不同层次,不能合并:
|
||||||
|
|
||||||
|
1. `RasterArrayTimelineRuntime` 的 entries/cache capacity 8:已经解码的 Snapshot memory cache,服务 Timeline cursor。
|
||||||
|
2. `RasterArraySource` 的 local/runtime candidate vectors:Source-owned preload candidates,服务初始窗口和后续 boundary。
|
||||||
|
3. MapLibre `FileSource`/`DatabaseFileSource` ambient/offline cache:通用资源缓存,按 Resource URL/HTTP metadata 工作,不理解 fieldId、validTime、generation 或 Snapshot。
|
||||||
|
4. Renderer GPU slots:RasterParticleLayer/validation layer 的 T0/T1 texture 与 simulation state,不是下载缓存,不能由数据源层直接管理。
|
||||||
|
|
||||||
|
STEP-051 当前是四个资源请求全部到达后构建 Timeline;没有独立的 manifest-first inventory、有限并发下载、field dedup、retry 或 stale policy。已有 MapLibre cache 可以降低资源获取成本,但不能替代 Particle feed lifecycle。
|
||||||
|
|
||||||
|
建议下一阶段先定义低频 field arrival/availability 状态和 manifest 顺序,明确:manifest 可用但 payload 缺失、metadata reject、payload mismatch、gap、stale、retry exhausted 时如何保持 last valid Field。Persistent cache 不应在没有 feed key/manifest 生命周期之前先做;第一步只需要 memory/injected transport validation,是否接 DatabaseFileSource 应后置。
|
||||||
|
|
||||||
|
## 7. Forecast manifest/catalog 最小模型
|
||||||
|
|
||||||
|
不要把所有未来业务字段塞入 Descriptor。进入正式 feed 必要的最小分层如下。
|
||||||
|
|
||||||
|
### 已属于 `ParticleFieldDescriptor/Snapshot`
|
||||||
|
|
||||||
|
- field identity:`fieldID`
|
||||||
|
- producer revision:`generation`
|
||||||
|
- forecast `validTime`
|
||||||
|
- `fieldType` / `displayDomain`
|
||||||
|
- grid width/height、bounds
|
||||||
|
- vector layout/encoding/noData
|
||||||
|
- expected byte length、stride、data version、可选 payload checksum
|
||||||
|
|
||||||
|
这些字段是解码、插值兼容性、fail-closed 和 Renderer canonical input 所必需的。
|
||||||
|
|
||||||
|
### 应属于上游 manifest/catalog,而不是 Snapshot/Renderer
|
||||||
|
|
||||||
|
- forecast `runTime`/cycle
|
||||||
|
- `leadTime`(可由 runTime 与 validTime 推导,但目录可显式记录)
|
||||||
|
- ordered snapshot list / window membership
|
||||||
|
- grid identity、resolution、coverage version
|
||||||
|
- metadata URL 与 payload URL/resource key
|
||||||
|
- source revision、availability、retry hint、expiry/stale policy
|
||||||
|
|
||||||
|
manifest 可以有 source/provider identity,但它不应穿过 Adapter 进入 Snapshot,也不应被 Renderer 或 Runtime Window 解释为业务 provider。
|
||||||
|
|
||||||
|
### 目前不应新增
|
||||||
|
|
||||||
|
不要仅为“以后可能需要”加入 level、ensemble、unit system、auth token、HTTP headers、vendor schema、download path、cache database key 或 ocean mask 字段。若未来 Wave/Tide/Current 需要这些语义,应单独设计并遵守 Ocean-only 与 mask 边界。
|
||||||
|
|
||||||
|
## 8. Weather Service 直接接入判断
|
||||||
|
|
||||||
|
当前不具备安全的直接接入条件。虽然 Android 有 `HTTPFileSource`,MapLibre 有通用 Response/cache/error,当前 Particle 仍缺少:
|
||||||
|
|
||||||
|
- provider-specific metadata/payload decoder 与其到 neutral Descriptor 的映射;
|
||||||
|
- forecast run/lead/valid-time inventory;
|
||||||
|
- provider-independent feed API 和 arrival ordering;
|
||||||
|
- retry/timeout/stale/gap policy;
|
||||||
|
- service auth/config ownership;
|
||||||
|
- 可在不改 Renderer/Timeline 的情况下将网络数据稳定送入 Snapshot submit。
|
||||||
|
|
||||||
|
直接接入会把 HTTP/provider 选择、目录和失败策略塞进 `RasterArraySource` 或 Android Host,容易越过冻结边界。结论:先建立 provider-neutral feed/manifest coordinator,再以独立阶段接 Weather Service;真实 network 不是 STEP-052 后的立即实现目标。
|
||||||
|
|
||||||
|
## 9. 候选下一阶段
|
||||||
|
|
||||||
|
| 候选 | 目标 | 新架构/模块 | Network/Provider | Timeline/Renderer | Android | 主要风险 |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| A. 直接 Weather Service | 立即接真实远程天气接口 | 新增 client、decoder、auth、生命周期 | 需要 | 不应修改,但 Source 会膨胀 | 需要一次高成本 build/test | 缺少 manifest、retry、provider 边界,容易把业务逻辑塞入 Core/Host |
|
||||||
|
| B. Provider-neutral Feed/Manifest Coordinator | 先定义 manifest、低频 arrival、Snapshot submit 和错误生命周期,用 Local/File/Mock transport 验证 | 一个薄跨平台 feed/coordinator;不建立 Provider Framework | 不需要;可注入 Local/Mock transport | 不改 state machine;不改 Renderer | 若只做 Core 可不需要;若补入口则合并一次 | arrival ordering、initial T0/T1 和 last-valid 语义需冻结 |
|
||||||
|
| C. Transport Cache/Preload | 先建立持久缓存、重试、过期和预取 | 新增 cache policy/lifecycle | 可不需要 | 不改 Renderer,但会依赖未定义 feed key | 建议需要 | 过早把 MapLibre ambient cache 当 Particle cache,生命周期和 key 尚未稳定 |
|
||||||
|
| D. Provider Decoder | 先做 GRIB/NetCDF 或某一供应商 decoder | 明显 provider-specific 模块 | 通常需要 | 输出应是 neutral Snapshot | 视入口而定 | 当前无真实 provider contract,容易锁死错误 schema |
|
||||||
|
|
||||||
|
候选 B 能以最小修改面冻结真正缺口,并兼容未来 Local/File、HTTP 和 provider decoder;C 应在 B 的 manifest/key/error contract 之后做,A/D 应后置。
|
||||||
|
|
||||||
|
## 10. 唯一推荐的下一实现阶段
|
||||||
|
|
||||||
|
### Recommended Next Implementation: STEP-053 Provider-neutral Field Feed / Manifest 与 Arrival Coordinator
|
||||||
|
|
||||||
|
目标范围:
|
||||||
|
|
||||||
|
1. 定义最小 provider-neutral field manifest/catalog 与 ordered snapshot arrival contract。
|
||||||
|
2. 将 metadata/payload transport 与 `ParticleFieldDataAdapter`、`ParticleFieldSnapshot`、现有 initial T0/T1/update submit 接口连接起来。
|
||||||
|
3. 以 injected Local/File/Mock transport 验证 initial T0/T1、subsequent T1、strict validTime ordering、duplicate、gap、invalid、retry exhausted 和 last-valid hold。
|
||||||
|
4. 只做低频异步 callback/future 或等价 arrival API,不做 Android per-frame JNI,不拥有 Timeline cursor/speed/pause。
|
||||||
|
5. 建立结构化 feed availability/error 分类,明确 metadata reject、payload mismatch、not-yet-available、gap 和 stale 的边界。
|
||||||
|
6. 保持 provider/vendor identity、URL、auth、HTTP headers 和 cache policy 在上游 feed/transport 层,不进入 Snapshot、Runtime 或 Renderer。
|
||||||
|
|
||||||
|
输入:manifest/catalog entry、metadata bytes、payload bytes,或注入的 transport response。
|
||||||
|
|
||||||
|
输出:已通过正式 Adapter 的 `ParticleFieldSnapshot`/arrival result;初始窗口和后续窗口仍调用现有 Core submit/update 语义。
|
||||||
|
|
||||||
|
允许修改:新增薄的 Core-neutral feed/coordinator、Source 的最小接线、定向 C++ tests、local/mock transport test seam 和必要日志/文档。若增加 Android validation 入口,应与同一阶段其他功能合并一次 build/install。
|
||||||
|
|
||||||
|
禁止修改:Timeline state machine、Playback、Runtime Field Window 语义、RasterParticleLayer/GPU simulation/trail、WeatherColorLayer、Adapter 合法性、Provider Framework、Weather Service、HTTP client、vendor decoder、第二套 Snapshot/Timeline/Window。
|
||||||
|
|
||||||
|
STEP-053 默认不需要真实 network、服务器、真实 provider 或 Android APK;先完成 Linux/Core/OpenGL 精确验证即可。只有要验证 Android Host 的低频 feed arrival/错误可观测性时才需要一次 Android build/install。
|
||||||
|
|
||||||
|
## 11. 建议的后续 2~4 阶段顺序
|
||||||
|
|
||||||
|
1. **STEP-053 Provider-neutral Field Feed / Manifest 与 Arrival Coordinator**:先冻结 manifest、arrival、错误和 last-valid 生命周期,使用 Local/File/Mock transport。
|
||||||
|
2. **STEP-054 HTTP Transport 与 Weather Service Adapter**:在不改变 neutral Snapshot/Runtime/Renderer 的前提下,把真实 HTTP/provider decoder 置于 feed 上游;此阶段才处理 auth/config 和真实服务契约。
|
||||||
|
3. **STEP-055 Field Cache、Retry、Stale 与 Preload Hardening**:基于已冻结的 manifest key、generation、validTime 和 error policy,决定 memory/cache/database 组合、重试和容量。
|
||||||
|
|
||||||
|
这三个阶段足够表达当前真实缺口,不提前规划更多 provider 或 Ocean field 变体。
|
||||||
|
|
||||||
|
## 12. 平台 / Core / Renderer 责任边界
|
||||||
|
|
||||||
|
- Transport/platform:负责 FileSource/HTTP/asset 的资源获取、平台凭据配置和底层 request cancellation;不生成 Timeline 状态。
|
||||||
|
- Feed/coordinator(建议跨平台 Core-owned seam):负责 manifest 顺序、低频 arrival、metadata/payload 配对、调用正式 Adapter、错误分类和 submit 意图;不负责 GPU。
|
||||||
|
- `ParticleFieldDataAdapter`:只做 descriptor/payload contract validation 与 canonical Snapshot construction。
|
||||||
|
- `RasterArraySource`:保持 Source-owned authoritative T0/T1、Timeline 接线和已有 Window update;不变成 provider client。
|
||||||
|
- Timeline/Playback:Core-owned,消费 Snapshot 时间和 low-frequency feed 结果;不请求网络。
|
||||||
|
- Renderer:只消费规范化 Snapshot/Window,继续执行 GPU upload/reuse/simulation/trail;不包含 provider/network/vendor/cache policy。
|
||||||
|
- Android/Kotlin:只提供低频用户意图和可选资源配置;不拥有 Timeline、Window、feed state 或 per-frame JNI。
|
||||||
|
|
||||||
|
## 13. Android 成本、技术债与风险
|
||||||
|
|
||||||
|
后续实现应遵守:先 Linux/Core/OpenGL precise build 与定向测试,再由用户在 Windows 11 对 `192.168.1.146` 用一次 APK 完成综合验证;若使用 ADB,必须显式 `adb -s 192.168.1.146 ...`。不为单个 MIX 文本问题单独触发 Android 周期。
|
||||||
|
|
||||||
|
Validation UI 的 auto MIX 文本滞后、WAITING/INVALID 视觉提示不足继续是 `NON-BLOCKING observability`,不阻塞正式 feed 架构,不推荐作为 STEP-053 主任务。Rotation/Viewport 自动恢复继续保留技术债;STEP-053 若不触及 lifecycle/surface/source reload,不合并修复。
|
||||||
|
|
||||||
|
主要风险是把 manifest/catalog 与 Descriptor 重复建模,或让 Source 直接拥有下载/重试/缓存策略。应优先薄化并用测试锁定:Snapshot immutable、validTime strict ordering、invalid/gap fail-closed、last valid Field、Window reuse、MIX property-only 和 Renderer 分离。
|
||||||
|
|
||||||
|
## 14. 调查结论
|
||||||
|
|
||||||
|
当前可以进入 STEP-053 实现 MD 编写,但不能跳过 provider-neutral feed/manifest 与 arrival 生命周期直接实现 Weather Service。STEP-051 production-like local feed 作为已验证过渡层保留;STEP-052 本身不修改代码、不引入网络、不新增缓存或 Provider。
|
||||||
147
docs/20_调查/STEP-056_RN天气渲染桥接现状调查_v1.md
Normal file
147
docs/20_调查/STEP-056_RN天气渲染桥接现状调查_v1.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# STEP-056 React Native 天气渲染桥接现状调查
|
||||||
|
|
||||||
|
状态:调查完成(只读);STEP-055 仍为 `IMPLEMENTED / WAITING_ANDROID_VALIDATION`。
|
||||||
|
|
||||||
|
Starting HEAD:`44a50e6e4a4e79fae7ddf36af007b5e35e969a92`(`particle/main`,`origin/particle/main` 同步确认)。本报告没有修改功能代码、没有执行 Android Gradle/APK/ADB/设备测试或 Jest。
|
||||||
|
|
||||||
|
## A. 当前真实调用架构
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[ParticleCoreLayerValidationActivity.kt\nsubmitRealWeatherDataset] --> B[MapLibreMap.submitRasterArraySourceHostField]
|
||||||
|
B --> C[NativeMapView.java\nnativeSubmitRasterArraySourceHostField]
|
||||||
|
C --> D[native_map_view.cpp\nNativeMapView::submitRasterArraySourceHostField]
|
||||||
|
D --> E[RasterArraySource::submitHostField]
|
||||||
|
E --> F[LocalParticleFieldFeed::load]
|
||||||
|
F --> G[ParticleFieldDataAdapter::decode]
|
||||||
|
G --> H[immutable ParticleFieldSnapshot]
|
||||||
|
H --> I[RasterArrayTimelineRuntime\nT0/T1 + cursor/mix]
|
||||||
|
I --> J[RenderRasterParticleLayer]
|
||||||
|
I --> K[RenderWeatherColorLayer]
|
||||||
|
J --> L[State/Trail/particle GPU passes]
|
||||||
|
K --> M[WeatherColor GPU pass]
|
||||||
|
```
|
||||||
|
|
||||||
|
当前真实 WeatherV2 Test App 资源位于 `platform/android/MapLibreAndroidTestApp/src/main/assets/integration/particle-weatherv2/ecmwf-hres-wind-20260807/`;`ParticleCoreLayerValidationActivity.kt::submitRealWeatherDataset()` 读取每个 `metadata.json` 与 `field.f32`,依次提交 `2026080700/06/12/18`。
|
||||||
|
|
||||||
|
仓库没有被跟踪的 React Native package、TurboModule、Fabric、JSI 或 RN NativeModule。现有可复用入口是 Android Test App 的 Java/JNI/C++ Host Field Submission,不是 RN binding。
|
||||||
|
|
||||||
|
## B. 文件与职责清单
|
||||||
|
|
||||||
|
| 文件 | 类/函数 | 当前职责 | RN v1 可复用 | 备注 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `include/mbgl/particle/field/field_data_adapter.hpp` | `ParticleFieldDescriptor` / `ParticleFieldSnapshot` / `ParticleFieldDataAdapter` | canonical descriptor、immutable snapshot、校验/解码 contract | 是 | U=eastward、V=northward、float32-le、interleaved U/V;不含 provider/URL |
|
||||||
|
| `src/mbgl/particle/field/field_data_adapter.cpp` | `decode()` | 校验 identity/generation/time/dimension/bytes,复制并解码 payload | 是 | Adapter 不补 generation,也不理解 JS/provider |
|
||||||
|
| `include/mbgl/style/sources/raster_array_source.hpp` | `RasterArraySource::submitHostField` | Host ingress、submit result、Timeline holder | 是 | 当前缺少 `clearWindFields()` |
|
||||||
|
| `src/mbgl/style/sources/raster_array_source.cpp` | `submitHostField` / `RasterArrayTimelineRuntime` | T0/T1 创建、后续 append、Timeline/Window state | 是 | 有 bounded timeline(8 entries)及 log,不向 Host 发事件 |
|
||||||
|
| `platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java` | `submitRasterArraySourceHostField` / `controlRasterArraySourceTimeline` | Android public facade | 是 | RN Android module 可调用或包裹它 |
|
||||||
|
| `platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java` | native declarations | Java 到 JNI | 是 | payload 目前仅 `byte[]` |
|
||||||
|
| `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp` | `NativeMapView::submitRasterArraySourceHostField` | `byte[]` 拷贝进 C++、调用 Source | 是 | 返回 `jint` result;没有 callback |
|
||||||
|
| `platform/android/MapLibreAndroidTestApp/.../ParticleCoreLayerValidationActivity.kt` | `submitRealWeatherDataset` / `controlTimeline` | Test App asset 模拟 Host | 仅作参考 | 不是正式 RN 入口 |
|
||||||
|
| `src/mbgl/style/layers/raster_particle_layer.cpp` | RasterParticle properties | particle style property parser/model | 部分 | 无 arbitrary particle/trail color |
|
||||||
|
| `src/mbgl/renderer/layers/render_particle_validation_layer.cpp` | `RenderRasterParticleLayer` | field upload、Timeline tick、GPU simulation/trail | 不直接 | 需保持 C++ ownership |
|
||||||
|
| `src/mbgl/style/layers/weather_color_layer.cpp` | WeatherColor property parser | 仅 opacity | 部分 | 无 dynamic speed stops |
|
||||||
|
| `src/mbgl/renderer/layers/render_weather_color_layer.cpp` | `RenderWeatherColorLayer` | 同一 Timeline frame 的色场绘制 | 不直接 | 重用 T0/T1 GPU content |
|
||||||
|
| `shaders/particle_gpu_validation.fragment.glsl` | `weatherSpeedColor` / `particleSpeedColor` | 当前固定色表/粒子 speed-color | 将来需扩展 | 色带硬编码在 shader |
|
||||||
|
|
||||||
|
## C. 当前 Host Field Submission 路径
|
||||||
|
|
||||||
|
`NativeMapView::submitRasterArraySourceHostField(JNIEnv&, String, String, byte[])` 用 `jni::GetArrayRegion` 把 Java `byte[]` 复制到 `std::vector<char>`,再调用 `RasterArraySource::submitHostField(metadataText, payload)`。
|
||||||
|
|
||||||
|
1. `LocalParticleFieldFeed::load()` 解析 provider-neutral metadata,并调用 `ParticleFieldDataAdapter::decode()`。
|
||||||
|
2. Adapter 严格检查非空 fieldId、合法 generation/validTime、width/height、`float32-le` + `interleaved-uv`、精确 payload bytes;成功后生成拥有 `velocity` 与 `valid` 的 immutable Snapshot。
|
||||||
|
3. 第一个合法 Field 成为 pending T0,结果为 `AcceptedPendingInitialWindow`。
|
||||||
|
4. 第二个严格较新的 compatible Field 通过既有 `submitInitialRuntimeFieldWindow(T0,T1)`,建立 `RasterArrayTimelineRuntime`,结果为 `InitialWindowReady`。
|
||||||
|
5. 后续 Field 按 producer `validTime` 追加同一个 bounded Timeline,结果为 `AcceptedNextSnapshot`。
|
||||||
|
6. 相同 GPU content 返回 `Duplicate`;非法、非单调、capacity 等返回结构化负 result,最后有效 Field 保留(fail-closed)。
|
||||||
|
|
||||||
|
当前 513×769 单帧为 `513 × 769 × 2 × 4 = 3,155,976` bytes,约 3.01 MiB。Android Test App 路径至少有:asset 到 Java `ByteArray`、`ByteArray → vector<char>`、`vector<char> → Snapshot::velocity` 三段 materialization/copy;随后 renderer 还有 GPU image upload。它不适合转换成巨大 JSON 数组。
|
||||||
|
|
||||||
|
## D. Timeline 与时间插值
|
||||||
|
|
||||||
|
`RasterArrayTimelineRuntime`(`raster_array_source.hpp/.cpp`)持有按 timestamp 排序的 Snapshot entries、T0 index、mix、speed 与 state。`tick()` 使用 `steady_clock` wall elapsed 推进 mix;相邻有效 entries 决定 T0/T1,跨界时 `moveToLocked()` 递增 window revision 并记录 `OLD_T1_TO_NEW_T0`。
|
||||||
|
|
||||||
|
`RenderRasterParticleLayer::prepare/update` 读取 `timeline->frame()`,在 revision 变化时让 `RasterArraySource` 的 authoritative window 前进;renderer 用同一 GPU content 判定复用,因而正常 A/B→B/C 只上传 new T1,不 rebuild simulation/clear trail。`RenderWeatherColorLayer` 读取同一 frame/mix,在 shader 内先插值 U/V 再计算 speed。
|
||||||
|
|
||||||
|
Android 当前低频命令路径是:`ParticleCoreLayerValidationActivity::controlTimeline` → `MapLibreMap::controlRasterArraySourceTimeline` → `NativeMapView::nativeControlRasterArraySourceTimeline` → `RasterArraySource::controlRuntimeTimeline`。现有命令为 play/pause、0.5/1/2 speed、step、按 **index** seek、Gap/Invalid/Recover;没有 `setForecastTime(timestamp)` 的公开 Host API,也没有向 JS 返回 MIX/T0/T1 的 callback。
|
||||||
|
|
||||||
|
## E. WeatherColorLayer 当前颜色实现
|
||||||
|
|
||||||
|
`WeatherColorLayer` 当前唯一公开 paint property 是 `weather-color-opacity`(`weather_color_layer.cpp`)。`RenderWeatherColorLayer` 以 UBO mode 5 运行 `particle_gpu_validation.fragment.glsl::weatherSpeedColor(speed)`;该函数把 0–30 m/s 映射到硬编码蓝/青/红色,并在 shader 用 `smoothstep/mix` 插值。
|
||||||
|
|
||||||
|
结论:当前没有 dynamic stops/ramp 数据结构,也没有 Android/JNI setter。React dynamic color stops 最小新增点应是 WeatherColorLayer 的 style property/model(有序 `speedMps,color` contract)及紧凑 shader-visible ramp(uniform/texture),由 Native 插值、绘制;React 只提交 stops。不能把 provider 色表放入 Renderer,也不能继续把固定 preset 作为唯一 API。
|
||||||
|
|
||||||
|
## F. ParticleStyle 当前实现
|
||||||
|
|
||||||
|
已有 RasterParticleLayer paint entry:`raster-particle-active`、`mask`、`time-mix`、`particle-count`、`line-width`、`particle-length`、`particle-opacity`、`trail-opacity`、`trail-fade`、`speed-color-enabled`。Android 低频 `NativeMapView::setStyleLayerFloatProperty` 已转发上述大部分 float/bool property。
|
||||||
|
|
||||||
|
| 目标项 | 当前入口/行为 | 副作用 |
|
||||||
|
|---|---|---|
|
||||||
|
| `particleCount` | `raster-particle-count` | State/draw resources 必要重建;不应 field resubmit |
|
||||||
|
| `lineWidth`、`particleLength`、`particleOpacity`、`trailOpacity`、`trailFade` | RasterParticle paint property | visual/uniform 更新;当前路径不应 style reload、field upload 或 Timeline reset |
|
||||||
|
| `speedColor` | `raster-particle-speed-color-enabled` | shader 在固定 speed palette 与固定蓝色之间切换 |
|
||||||
|
| `particleColor` | 不存在 | 需新增明确 style property |
|
||||||
|
| `trailColor` | 不存在 | 当前 trail RGB 与 particle segment 路径耦合,需独立 style/shader contract |
|
||||||
|
|
||||||
|
因此未来应将 `speedColor` 保留为可选模式;当它为 false 时,用 React 的 `particleColor`;`trailColor` 必须独立提交,不能让 WeatherColorLayer speed ramp 隐式控制粒子颜色。
|
||||||
|
|
||||||
|
## G. Runtime Enable / Disable / Release
|
||||||
|
|
||||||
|
当前 generic Layer visibility 可以隐藏 layer;`RenderRasterParticleLayer::needsRepaint()` 仅在 `active && visibility != None` 时为真,故 particle hidden 时停止该层持续 repaint。它不等价于 release:layer/source 未 remove 时 State/Trail/field GPU resources 与 Snapshot/Timeline 仍被持有。Weather color 的 `needsRepaint()` 返回 `isLayerRenderable()`,visibility none 是否不再 render/repaint 由通用 Layer renderability 决定;本调查未用设备测试验证。
|
||||||
|
|
||||||
|
真实释放的现有路径是 layer removal:`RenderRasterParticleLayer::layerRemoved()` 移除 State/Trail targets 与 draw buffers;`RenderWeatherColorLayer::layerRemoved()` reset resources。source destruction 释放 `RasterArraySource` 的 timeline/pending Snapshot 共享引用;Activity 当前 `enabled=false` 是重载 validation style 并省略 layers,不是对 RN 暴露的一次性 runtime unload API。
|
||||||
|
|
||||||
|
`setWeatherEnabled(false)` 可实现真正释放,但当前没有一条 API 完整完成它。最小高层语义应在 map/style 安全线程中:停止低频 playback → remove Particle/WeatherColor layers(GPU release)→ clear timeline/pending host fields 或 remove RasterArraySource(Snapshot release)。需要新增 `clearWindFields()`/runtime lifecycle bridge;不能以 visibility 伪装 unload,也不应向 React 暴露 State/Trail/T0/T1。
|
||||||
|
|
||||||
|
## H. RN Binary Bridge 建议
|
||||||
|
|
||||||
|
当前没有 RN binary bridge。建议 v1 使用 `ArrayBuffer`/`Uint8Array` 在 RN Android module 侧稳定 materialize 为 `byte[]`,复用现有 `MapLibreMap.submitRasterArraySourceHostField(..., byte[])` 与 JNI;metadata 继续是小型 String/JSON。约 3.01 MiB/帧和四帧队列的峰值复制需要纳入内存预算,但在先验证正确性阶段不必承诺 zero-copy。
|
||||||
|
|
||||||
|
后续 profiling 若证明 JS→Java→JNI copies 是瓶颈,再增加 **新的** direct `ByteBuffer` JNI overload(或宿主 JSI ArrayBuffer native ownership seam)。不能假称现有 `byte[]` 通路可 zero-copy;也不应把文件/mmap 作为 RN v1 的必需前提。
|
||||||
|
|
||||||
|
## I. RN API 与 Native 能力映射
|
||||||
|
|
||||||
|
| RN 目标功能 | 当前 Native 能力 | 已有入口 | 缺口 / 建议接入点 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `setWeatherEnabled` | 层 removal/资源释放存在 | generic Style remove;layerRemoved | 新增高层 lifecycle/unload + clear fields,不用 visibility 代替 |
|
||||||
|
| `setWeatherColorVisible` | generic visibility | Style Layer visibility | 可包装;隐藏不释放 |
|
||||||
|
| `setParticleVisible` | generic visibility;active 控制 simulation | Style property | 可包装;明确 hide/suspend/unload 三语义 |
|
||||||
|
| `submitWindField` | 完整 Host canonical ingress | `submitRasterArraySourceHostField` | RN module/facade 与 binary adapter 缺失 |
|
||||||
|
| `clearWindFields` | 无 | 无 | Source clear/lifecycle seam |
|
||||||
|
| `setForecastTime` | 仅 index seek | timeline command 5 | 增 timestamp→entry selection API,JS 不设 T0/T1/MIX |
|
||||||
|
| `setPlaybackState` | play/pause | timeline commands 0/1 | 可包装 |
|
||||||
|
| `setPlaybackSpeed` | 0.5/1/2 | command 2 | 枚举/范围需正式 Host contract |
|
||||||
|
| `setWindColorStyle` | opacity only | WeatherColor paint property | 增 dynamic stops style model + shader ramp |
|
||||||
|
| `setParticleStyle` | 多数 float/bool 属性 | `setStyleLayerFloatProperty` | 增 particle/trail color typed properties;不要靠 speedColor |
|
||||||
|
| `setParticlePerformance` | 无 weather-local FPS gate | global `MapView#setMaximumFps` | 独立 render scheduling 阶段 |
|
||||||
|
| runtime/error callbacks | submit `jint` + logcat | JNI result | 增低频 structured event/callback/state query;不回调 per-frame MIX |
|
||||||
|
|
||||||
|
## J. targetFps 调查
|
||||||
|
|
||||||
|
当前持续帧由 `RenderRasterParticleLayer::needsRepaint()`(active/visible)及 `RenderWeatherColorLayer::needsRepaint()`(renderable)驱动。`Map::Impl::onDidFinishRenderingFrame()` 在 `needsRepaint` 时再次 `onUpdate()`;Android frontend 最终调用 `MapRenderer::requestRender()`。因此接近 60 FPS 是 MapLibre continuous render request 链的结果,不是 Test App Timer。
|
||||||
|
|
||||||
|
现有 `MapView#setMaximumFps()` / `MapRenderer#setMaximumFps()` 是整张地图的 global cap,不应直接实现天气专属 targetFps。最安全的天气 30 FPS 节流点是 future `RenderRasterParticleLayer`/WeatherColor coordination 的 render scheduling gate:只在 deadline 到达时执行 State/Trail 与 dependent color redraw,使用累计 monotonic wall delta 驱动 lifecycle、trail fade 和 Timeline tick。这样不能把固定 1/30 秒塞给 shader;需保留真实 elapsed,并审查 simulation dt clamp/substep。WeatherColor 也必须参与 gate,否则它单独 `needsRepaint()` 仍保持全图约 60 FPS。建议该项独立于 RN Bridge v1,以免把 render scheduling 风险混入接口接线。
|
||||||
|
|
||||||
|
## K. 建议实施拆分与结论
|
||||||
|
|
||||||
|
**推荐下一实现阶段:STEP-057 Android Host Weather Runtime Bridge Foundation。**
|
||||||
|
|
||||||
|
目标是完成可被未来 RN module 包装的 Android/Native typed facade,而不是在本仓库凭空建立 RN package(当前没有 RN dependency、package 或 host app topology)。建议一个较大 Batch:
|
||||||
|
|
||||||
|
1. 低频 runtime lifecycle(enable/suspend/hide/unload)与 `clearWindFields`;安全 layer/source teardown。
|
||||||
|
2. canonical `submitWindField(metadata, binary)`、timestamp seek、play/pause/speed 的 typed Android/JNI facade,复用 Source/Timeline。
|
||||||
|
3. 可见性、现有 particle visual properties、WeatherColor opacity 的 typed API;增加 structured submission/runtime/error event,避免 per-frame callback。
|
||||||
|
4. 定义并接入 dynamic WeatherColor stops 与独立 particle/trail colors 的 style property contract;所有 provider/network/forecast decision 仍留 JS。
|
||||||
|
5. 以 Test App/Native tests 走完整 facade,最后一次 Android build/install 验收;真正 RN module 留给具备 RN host/build dependency 的应用仓库或后续明确任务。
|
||||||
|
|
||||||
|
**后续 Batch(STEP-058,建议独立):** weather-local `targetFps`、repaint gate、validation logging 降频与性能测量。不要把 `MapView#setMaximumFps()` 当作默认实现。
|
||||||
|
|
||||||
|
直接进入“RN package 实现”目前的阻塞不是 Core 数据 contract,而是仓库不存在 RN binding 与 RN Android build ownership。可立即开发的是上述 Host-native facade;等宿主提供 RN 依赖和 module placement 后,RN 层可薄包裹它。
|
||||||
|
|
||||||
|
最大三项风险:
|
||||||
|
|
||||||
|
1. 3.01 MiB 级 payload 在 JS/Java/JNI/Snapshot 的多次 copy 造成峰值内存、GC 与提交抖动。
|
||||||
|
2. visibility、suspend 与 unload 被混同,导致 Native/GPU/Snapshot 资源未真正释放,或在 style/render thread 上错误 teardown。
|
||||||
|
3. Timeline tick、particle State pass 与持续 repaint 当前耦合;错误 targetFps gate 会改变 motion、wall-time lifecycle 或 Timeline 行为。
|
||||||
|
|
||||||
|
冻结边界继续有效:Descriptor/Snapshot、T0/T1 window/reuse、Core-owned Timeline/Playback、canonical m/s U/V、WeatherColor 与 RasterParticle 分离、Renderer 不含 provider/network/vendor 逻辑。
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# STEP-065 MapLibreParticleLab V3 通用 Scalar / Vector Field 现状调查
|
||||||
|
|
||||||
|
状态:完成(只读调查)
|
||||||
|
基线:`particle/main` / `2e3ab26275a8d1873d764cf6927783a7b0406836`,`origin/particle/main` 同一提交。
|
||||||
|
|
||||||
|
## 调查范围与方法
|
||||||
|
|
||||||
|
已完整阅读项目基线、协作/构建/Step/架构/官方基线规范、STEP-064 任务与冻结记录/报告;只读核对 Particle Core、两 renderer、GLSL、RasterArray Timeline、Android Host/JNI、RN package、两个 RN Test Host,以及 `/root/sourceserver/weatherV2` 指定六文件。未修改 WeatherV2/NavSea/生产代码,未执行 Gradle、APK、ADB 或 Jest。
|
||||||
|
|
||||||
|
## A. 当前 Field Contract 与 Wind-only 点
|
||||||
|
|
||||||
|
`include/mbgl/particle/field/field_data_adapter.hpp` 的 `ParticleFieldDescriptor` 只有一个 `layout/encoding/byteLength/stride`,`ParticleFieldSnapshot` 只有 `velocity: vector<float>` 与 `valid: vector<uint8_t>`。`field_data_adapter.cpp` 只接受 `interleaved-uv` + `float32-le`、每 cell 两 float;`isValid()` 与 decode 末尾硬性要求 `fieldType == "WIND"`、`displayDomain == "GLOBAL"`。`local_particle_field_feed.cpp` 的 `maplibre-particle-field/v1` parser 也强制 `vector` 与一个 `payload`。
|
||||||
|
|
||||||
|
`fieldType` / `displayDomain` 还参与 timeline-compatible 比较;当前 fixture、Consumer metadata 皆为 `WIND/GLOBAL`。这不是 provider-neutral 的能力表达,且不能承载 scalar-only 或 Wave 的 direction vector。
|
||||||
|
|
||||||
|
## B. GPU、validity、shader 的真实结构
|
||||||
|
|
||||||
|
`render_weather_color_layer.cpp` 每快照把 U/V 按快照最大绝对值编码进 RG、validity 编入 A;T0/T1 各一张 `Texture2D`。`render_particle_validation_layer.cpp`(正式 `RasterParticleLayer` 也经此 renderer)同样把 U/V+validity 压入 RGBA,维护 `fieldTexture/nextFieldTexture`、各自 scale 和 image。二者独立拥有资源,不共享 GPU texture;均以 `sameFieldGpuContent()` 比较并可把 OLD_T1 移至 NEW_T0,仅上传新内容。
|
||||||
|
|
||||||
|
`shaders/particle_gpu_validation.fragment.glsl` 的 Field pass 从 RG 恢复 U/V,A 同时是 shared validity;`weatherRampColor(length(velocity))` 是图场唯一值来源。无独立 scalar texture、mask texture 或 channel-level validity;粒子另有验证用 coast mask,但不属于 field validity。
|
||||||
|
|
||||||
|
## C. Particle 数值与单位假设
|
||||||
|
|
||||||
|
GLSL `fieldVelocityAt()` 混合 T0/T1 的 U/V,要求两时刻 A 有效且 `length > 1e-5`。状态 pass 用 `dx=u*simulationSeconds/EarthCircumference`、`dy=-v*simulationSeconds/(EarthCircumference*cos(latitude))`,明确 U east、V north、物理 m/s。C++ 的 `VisualSimulationSecondsPerRealSecond=1200` 只是展示时钟;不会改变 canonical U/V 单位。故现状不适合直接把 Wave unit direction 当 velocity。
|
||||||
|
|
||||||
|
## D. RN 到 Core binary ingress 与 ownership
|
||||||
|
|
||||||
|
公共 `FieldRuntime.submitField(metadataJson, payloadFileUri)` 在 RN 薄映射内部 `submitWindField`。Kotlin bridge 只接受 `file://` 或绝对本地路径,metadata 上限 1 MiB、payload 1..64 MiB,读为 `ByteArray`;无 number[]/JSON array/Base64。`WeatherRuntime.submitWindField()` 传给 `MapLibreMap.submitRasterArraySourceHostField()`,JNI `NativeMapView::submitRasterArraySourceHostField` 把 Java `byte[]` 复制为 C++ `vector<char>`,再调用 `RasterArraySource::submitHostField()`;Core adapter 再复制解码为 float vector。因此当前 ownership 是同步、三次宿主/核心内存持有,而非 zero-copy。
|
||||||
|
|
||||||
|
## Timeline / Runtime Window
|
||||||
|
|
||||||
|
`RasterArraySource::submitHostField()` 首帧 pending、第二帧原子建立 Source-owned `RasterArrayTimelineRuntime` T0/T1;后续按 `validTime/generation` 单调 append,容量 8。`RasterArrayTimelineRuntime` 输出 immutable T0/T1 + MIX,暂停不推进。`RenderWeatherColorLayer` 与 `RenderRasterParticleLayer` 都取同一 Source timeline;正常 overlap rollover 保持 `simulationRebuild=false`、`trailCleared=false`,无 overlap 才 reset。invalid/gap 由 timeline 状态 fail-closed。
|
||||||
|
|
||||||
|
## Field Renderer 与 speedMps 链路
|
||||||
|
|
||||||
|
`WeatherColorStop.speedMps` 位于 C++ `weather_color_stop.hpp`,Android `WeatherRuntime.ColorStop`、Kotlin native bridge 和 generated TurboModule 均仍使用该内部 ABI;RN `.2` 已将公开 `FieldColorStop.value` 薄转换为 `speedMps`。当前 shader 默认 0..30 m/s palette,stop 值也按 speed 解释。因此 V3 必须新增明确 value source,不能以有无 scalar 猜测。
|
||||||
|
|
||||||
|
## WeatherV2 输入核对
|
||||||
|
|
||||||
|
`nsft.py` / `field_pmtiles.py` 证实 NSFT PMTiles `wm_grid_257`、NW origin(row north→south、column west→east)、同 zoom tile 257 样本。Wind 与 current 都写物理 U/V;`wind.py`、`tide.py` 计算 speed 为 `sqrt(u²+v²)`。Rain 写 `rain_mm_h + valid`;虽 decoder 有 `precipitation_type`,`encode_rain_tile()` 未写它。Wave 写 `height_m`、`period_s` 和 `unit_to_u/v`;`waves.py` 将 from 转 to 后归一化,非 m/s。`target_time` 由实际任务时间写入 header,不能推定小时 cadence。任务书给出的事实与代码一致。
|
||||||
|
|
||||||
|
## 直接结论
|
||||||
|
|
||||||
|
V3 应保留现有 Source-owned timeline、两个独立 renderer 和 overlap reuse 的行为,但将单个 velocity snapshot 升级为一个快照的 optional scalar、optional vector、shared validity 三资源。业务名称、单位换算、NSFT/PMTiles、tile 拼接、provider/缓存必须停在 NavSea 侧。
|
||||||
117
docs/20_调查/STEP-077_QBIN2_V4_Android_JNI_RN桥接调查报告_v1.md
Normal file
117
docs/20_调查/STEP-077_QBIN2_V4_Android_JNI_RN桥接调查报告_v1.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# STEP-077 QBIN2 V4 Android / JNI / RN 桥接调查报告 v1
|
||||||
|
|
||||||
|
状态:完成(只读调查)
|
||||||
|
|
||||||
|
日期:2026-09-13
|
||||||
|
基线:`0759a06104e1fb30f036db2efb6ef34ca5859759`(`particle/main` = `origin/particle/main` 于调查开始时)
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
V4 应新增一条与 V3 完全平行的、低频的 `WeatherRuntime` / `MapLibreMap` / `NativeMapView` 通道;不得接到现有 `submitField`、`clearFields`、`setForecastTime`、`setPlaybackState` 或 `setPlaybackSpeed`。这些现有命令分别写入 V3 Host field、`clearHostFields()`、`seekRuntimeTimelineToTimestamp()` 和 `controlRuntimeTimeline()`,会操作 V3 timeline,不能承载 V4 的 `EncodedFieldStore` / `ForecastClock`。
|
||||||
|
|
||||||
|
推荐 RN 只传绝对本地 `qbinFileUri`(或 `file://` URI)及小型、typed primitive ABI 元数据;Kotlin 有界读取为 Java `byte[]`,JNI 复制为 C++ `std::vector<uint8_t>`,再调用 `RasterArraySource::submitEncodedField(...)`。不传 Base64,不由 JS 构造 `number[]` / 大型 `ReadableArray`。V4 Core 独占每个 field 的 T0/T1/MIX 选择;桥接只传 requested time、play/pause 与速度,绝不传 endpoint、mix 或 renderer snapshot。
|
||||||
|
|
||||||
|
## 实际当前链路
|
||||||
|
|
||||||
|
```text
|
||||||
|
FieldRuntime.ts public facade
|
||||||
|
-> NativeWeatherRuntimeBridge.ts TurboModule Spec (primitive arguments / Promise)
|
||||||
|
-> WeatherRuntimeBridgeModule.kt
|
||||||
|
runOnUiQueueThread -> Fabric reactTag
|
||||||
|
UIManagerHelper(FABRIC).resolveView -> MLRNMapView.mapLibreMap
|
||||||
|
local file bounded read -> WeatherRuntime
|
||||||
|
-> MapLibreMap (map-owned WeatherRuntime facade)
|
||||||
|
-> NativeMapView Java (checkState) -> registered NativeMapView JNI
|
||||||
|
-> native_map_view.cpp: style.getSource(id), dynamic_cast<RasterArraySource*>
|
||||||
|
-> RasterArraySource V3 Host API OR isolated V4 Host API
|
||||||
|
```
|
||||||
|
|
||||||
|
源码事实如下。
|
||||||
|
|
||||||
|
- `FieldRuntime.submitField` 仍是 V3 public API,封装 `FieldPayloadRefs` 为 validity/scalar/vector 三个 URI;`submitLegacyVectorField` 保留 v1 兼容入口。`NativeWeatherRuntimeBridge.ts` 的 codegen Spec 已刻意使用 primitive 参数,空 string 仅表达 V3 optional plane。
|
||||||
|
- Kotlin 对 V3 已验证本地绝对路径或 `file://`、单文件和 aggregate 64 MiB、读中长度变化;再形成 `ByteArray`。它不保存 reactTag、map 或 timeline,而是每次在 UI queue 解析当前 Fabric view。
|
||||||
|
- `WeatherRuntime.submitField` 把 V3 bytes 重新 pack 后调用 `submitRasterArraySourceHostFieldV2`;`NativeMapView` 用 `GetArrayRegion` 复制到 C++,然后拆 planes 并调用 `submitHostField`。这是可复用的“file -> Java byte[] -> JNI copy -> vector” seam,而不是可复用的 V4 API。
|
||||||
|
- V4 `RasterArraySource` 已有独立 `EncodedFieldStore`、`ForecastClock`、binding intent 和 `submitEncodedField` / `set*Binding` / `setRequestedForecastTime` / `setForecastPlayback*` / `clear*EncodedFields`;注释和 STEP-076 public-path 测试都证明它不写 V3 T0/T1/MIX 或 renderer snapshot。
|
||||||
|
|
||||||
|
## QBIN 传输形态
|
||||||
|
|
||||||
|
| 方案 | 当前支持与成本 | 结论 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| A:RN `qbinFileUri`,Kotlin 读取 | 已有 `readLocalPayload` / `resolveLocalFile` 精确先例;仅接受本地文件、1..64 MiB,16 KiB buffer,有 read-time size guard;Java/JNI 仍各有一次受控 copy | 推荐。新增 V4 方法复用读取 helper,但不复用 V3 submit/timeline API。 |
|
||||||
|
| B:RN `byte[]` / `number[]` | 当前 TurboModule Spec 无 binary array ABI;`ReadableArray` 是现有 color stops 的小对象集合,不适合 QBIN。JS number array 造成 JS/native 转换、对象/number 开销与额外 copy;Base64 更扩大内容且无现有 ABI | 拒绝。不得为了方便绕过 64 MiB file guard。 |
|
||||||
|
| C:直接复用 Java/JNI `byte[]` seam | Kotlin -> `WeatherRuntime` -> Map -> `NativeMapView` 的 `byte[]` 和 `GetArrayRegion` 已存在,适合作为 A 的下半段 | 复用实现模式;新增命名为 V4-specific JNI 方法,不复用 V3 方法。 |
|
||||||
|
|
||||||
|
64 MiB 是现有 Kotlin `MAX_PAYLOAD_BYTES` 与 Java `MAX_FIELD_PAYLOAD_BYTES` 的边界;V4 要在 Kotlin、Java、JNI 均检查 raw QBIN 长度 `1..64 MiB`,随后 Core Decoder 继续执行其自身 QBIN 全文件/decoded-raw 限制。该限制不授权桥接解析 QBIN。
|
||||||
|
|
||||||
|
## 推荐 V4 API 与跨层 ABI
|
||||||
|
|
||||||
|
所有 String 以 UTF-8 byte length 做小型上限校验;`reactTag`、generation、dataVersion、time 使用 JS 安全整数检查(当前 bridge 对 timestamp 已采用 finite/nonnegative/integral guard)。枚举使用固定 string;所有 enum numeric 值只在 Kotlin/Java/C++ 边界转换。
|
||||||
|
|
||||||
|
| RN public / Turbo Spec | Kotlin / Java facade | JNI / Core | 参数形态及责任 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `submitEncodedField(tag, fieldKey, fieldID, generation, dataVersion, contractJson, qbinFileUri)` | `submitEncodedField(...)` | `submitRasterArraySourceEncodedField(sourceId, fieldKey, fieldID, long generation, long dataVersion, contractJson, byte[] raw)` | URI 为文件;metadata 用 primitive;contractJson 是唯一结构化小协议,Kotlin/Java 各只作长度、空值、range;JNI 解析为 Core structs 并调用 V4 API。 |
|
||||||
|
| `setColorBinding(tag, fieldKey, semanticID, groupID, expectedValueClass, expectedUnitID)` | 同名 | `setRasterArraySourceEncodedColorBinding(...)` | 完全 primitive;JNI 构造 `ColorBinding`,Core resolver 仍做最终 plane assertion。 |
|
||||||
|
| `setParticleBinding(tag, mode, fieldKey, firstSemanticID, firstGroupID, secondSemanticID, secondGroupID, expectedUnitID)` | 同名 | `setRasterArraySourceEncodedParticleBinding(...)` | mode-dependent selector 仍占固定 primitive slots;Direction 使用 first selector,second 传 0/0。Kotlin/Java 检查 mode 和 uint16 range,不引入 `ReadableMap`。 |
|
||||||
|
| `setRequestedForecastTime(tag, timestampMs)` | `setEncodedForecastTime` | `setRasterArraySourceEncodedForecastTime` | 独立 V4 名称,`long`;Core ForecastClock 选择各 field window/mix。 |
|
||||||
|
| `setForecastPlaybackState(tag, "PLAY"|"PAUSE")` | `setEncodedForecastPlaybackState` | `setRasterArraySourceEncodedForecastPlaybackState` | V4 enum,绝不调用 V3 `controlRasterArraySourceTimeline`。 |
|
||||||
|
| `setForecastPlaybackSpeed(tag, "HALF"|"NORMAL"|"DOUBLE")` | `setEncodedForecastPlaybackSpeed` | `setRasterArraySourceEncodedForecastPlaybackSpeed` | Kotlin/Java 映射为 0.5/1/2;Core 保留 speed validity。 |
|
||||||
|
| `clearEncodedField(tag, fieldKey)` / `clearAllEncodedFields(tag)` | 同名 | 同名 V4-only JNI | 不清 V3 Host fields、不改变 V3 cadence/timeline。single-clear 返回 `CLEARED` / `FIELD_NOT_FOUND`;all-clear 返回 accepted command。 |
|
||||||
|
|
||||||
|
`ResolvedFieldProfileContract` 需要 `contractSchemaVersion, profileID, profileVersion, allowAdditionalPlanes, sharedValidityRequired, timeRelation, requiredPlanes[]`,每个 plane 为 `semanticID, valueClass, unitID, groupID, component, requiredFlag`。它是唯一确实需要 array 的稳定数据;当前 TurboModule codegen 对 public object ABI 已有历史约束,故不建议 `ReadableMap/ReadableArray`,也不应将每个平面拆成无边界的 JS 数组。推荐在 RN/Spec 中传受大小限制的 `contractJson`,在 JNI 的单一 parser 转为 C++ contract;Kotlin/Java 不语义解析。这样 Host/SDK 负责已经 resolved 的 profile contract,Native 不知道 provider/profile 业务;Core `FieldProfileValidator` 仍是 structural/semantic 唯一裁决者。
|
||||||
|
|
||||||
|
这不是让 JSON 承载 QBIN 或 server manifest:`contractJson` 是受 schema 版本约束的小 control-plane ABI。若 RN codegen 后续稳定支持 readonly typed object + typed array,才可在破坏性 ABI 版本中替换;本 Step 不建议 `ReadableMap`,因为它绕过 generated Spec、会把多层 field parsing 复制进 Kotlin。
|
||||||
|
|
||||||
|
## 兼容与所有权策略
|
||||||
|
|
||||||
|
- V3 `FieldRuntime.submitField`、`submitLegacyVectorField` 与现有 `FieldSubmitResult` 保留不变;V4 新增 `submitEncodedField` 与新的 `EncodedFieldSubmitResult` union,不能改名覆盖。
|
||||||
|
- `WeatherRuntime.setForecastTime`、`setPlaybackState`、`setPlaybackSpeed` 必须不复用:它们当前调用 V3 `seekRasterArraySourceTimelineToTimestamp` / `controlRasterArraySourceTimeline`。V4 用 `setEncoded...` Java/JNI names;RN public 名可按任务书使用 `setRequestedForecastTime` 等,但需明确是 V4 namespace / facade(例如 `EncodedFieldRuntime`),避免误导。
|
||||||
|
- `clearFields` 和 `clearEncodedFields` 必须分离。前者还参与 V3 state/cadence/disable 行为并调用 `clearHostFields()`;后者只清 `EncodedFieldStore`,保留 V4 bindings,正如 STEP-076 证明,且不得触及 V3。
|
||||||
|
- `setColorBinding` / `setParticleBinding` 不修改 WeatherColor/RasterParticle renderer properties;它们只存 V4 binding intent。实际 renderer 接线不在本 Step 范围。
|
||||||
|
|
||||||
|
## 错误模型
|
||||||
|
|
||||||
|
Promise rejection 仅用于桥接前条件、生命周期或无法执行;已进入 Core 的 `RasterArrayEncodedFieldSubmitResult` 必须 resolve 成可区分的 V4 result string,不能折叠为 `UNKNOWN` 或 `NATIVE_REJECTED`。
|
||||||
|
|
||||||
|
| Core result | 推荐 RN resolved result |
|
||||||
|
| --- | --- |
|
||||||
|
| `AcceptedInserted` | `ACCEPTED_INSERTED` |
|
||||||
|
| `AcceptedReplacedRevision` | `ACCEPTED_REPLACED_REVISION` |
|
||||||
|
| `Duplicate` | `DUPLICATE` |
|
||||||
|
| `RejectedDecode` | `REJECTED_DECODE` |
|
||||||
|
| `RejectedContract` | `REJECTED_CONTRACT` |
|
||||||
|
| `RejectedInvalidFieldKey` | `REJECTED_INVALID_FIELD_KEY` |
|
||||||
|
| `RejectedIdentityConflict` | `REJECTED_IDENTITY_CONFLICT` |
|
||||||
|
| `RejectedStaleRevision` | `REJECTED_STALE_REVISION` |
|
||||||
|
| `RejectedIncompatible` | `REJECTED_INCOMPATIBLE` |
|
||||||
|
| `RejectedCapacity` | `REJECTED_CAPACITY` |
|
||||||
|
|
||||||
|
桥接层 rejection code 应为:`INVALID_ARGUMENT`(tag、enum、整数、UTF-8/control size)、`PAYLOAD_FILE_UNREADABLE`(空/越限/非本地/不可读/读中变更)、`MAP_VIEW_NOT_FOUND`、`MAP_NOT_READY`、`STYLE_NOT_READY`、`WEATHER_OBJECTS_NOT_READY`、`RUNTIME_DISPOSED`,以及 V4 source 获取失败时的 `SOURCE_MISSING`。JNI 在 Source 不存在或类型不符时不得伪装为 Core `RejectedDecode`;应返回明确 native bridge status,再由 Java 映射为 `SOURCE_MISSING`。style 已存在但 source 尚未 ready 与 runtime disposed 同样不能混成 submit result。
|
||||||
|
|
||||||
|
## 生命周期与线程
|
||||||
|
|
||||||
|
`WeatherRuntime` 被 `MapLibreMap` 私有拥有,且是 style-scoped;`replaceWeatherRuntime` 先 dispose,`setStyle` 也 dispose,Kotlin 的 MapView detach listener 调用 `map.disposeWeatherRuntime()`。每个 bridge call 在 `runOnUiQueueThread` 通过 Fabric `reactTag -> UIManagerHelper -> MLRNMapView -> mapLibreMap` 当场解析;不允许新增 map registry、缓存 tag、长期保存 `MapLibreMap*` 或 native source pointer。
|
||||||
|
|
||||||
|
未来 V4 command 应沿用 `withRuntime`,在 Android UI 主线程上从当前 style 查 source。`NativeMapView.checkState` 是 Java 层 native lifetime guard;C++ 必须每次 `style.getSource(sourceId)` 和 `dynamic_cast<RasterArraySource*>`,不可缓存指针。style reload/detach/dispose 后,后续命令得到上述 lifecycle error;旧 V4 state 随 Source/style 销毁失效,Host 要在重新 attach、style ready 后按自己的缓存重新提交 raw QBIN 与 binding intent。
|
||||||
|
|
||||||
|
## 可用验证与后续验收
|
||||||
|
|
||||||
|
本调查没有执行 Android 编译、APK、ADB 或实体机操作。当前 Linux 可用入口与边界:
|
||||||
|
|
||||||
|
| 层 | 后续可验证项 | 边界 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| TS | `npx tsc -p packages/maplibre-particle-rn/tsconfig.json --noEmit`;Test Host `npm run typecheck` | package 未定义独立 typecheck script;依赖由 Test Host node_modules/paths 提供。 |
|
||||||
|
| TurboModule | RN codegen 由 package `codegenConfig`(`WeatherRuntimeBridgeSpec`、`src`、Kotlin package)与 Android Gradle/RN integration 驱动 | 没有发现完全脱离 Gradle 的仓库级 codegen 命令;实现 Step 应记录实际 generation task。 |
|
||||||
|
| C++/JNI | 头文件/`native_map_view.cpp` 的精确 compile 与 Linux Core unit tests;JNI registration list 与 Java native declarations 静态一一审计 | 精确 Android ABI/JNI compile 仍需 Android NDK Gradle/native build;Linux Core build 不证明 Android linkage。 |
|
||||||
|
| Java/Kotlin | `WeatherRuntimeStateTest` 是 Mockito/JVM facade 状态测试,可新增 V4 mapping/隔离测试 | 它依赖 Android Gradle test classpath;未发现无需 Gradle 的现成 Kotlin/Java standalone validation。 |
|
||||||
|
|
||||||
|
建议实施拆分:
|
||||||
|
|
||||||
|
1. 先冻结 RN Spec/types 与 Kotlin file/control validation,新增 V4 result union,并以 typecheck + codegen 审计验收。
|
||||||
|
2. 再新增 Java `WeatherRuntime` V4-only facade、`MapLibreMap` / `NativeMap` / `NativeMapView` Java declarations、C++ JNI registration 与 source lookup;JVM Mockito 覆盖 V3/V4 time-clear 不串线和 error mapping。
|
||||||
|
3. 最后才接 renderer consumer(独立 Step),以 C++ Source tests 证明 V4 projection、style reload/detach rejection、JNI exact build、Android Debug OpenGL/Vulkan 与离线 fixture 验收。不得下载真实 QBIN。
|
||||||
|
|
||||||
|
## 实施文件清单与风险
|
||||||
|
|
||||||
|
预期修改:`packages/maplibre-particle-rn/src/{types.ts,NativeWeatherRuntimeBridge.ts,FieldRuntime.ts}`、`WeatherRuntimeBridgeModule.kt`、`WeatherRuntime.java`、`MapLibreMap.java`、`NativeMap.java`、`NativeMapView.java`、`native_map_view.hpp/.cpp`,以及相应 JVM/C++ tests。不会改 package version、Test Host、Renderer/GPU。
|
||||||
|
|
||||||
|
主要风险为 codegen 对 complex object/array 的限制、64 MiB 下 Kotlin/Java/JNI 三份瞬时内存、JS number 精度(所有 uint64 必须限制在 safe integer 或改 decimal string ABI)、source/style 生命周期竞争、以及错接旧 V3 timeline。前四项通过 primitive+small-contract JSON、固定 64 MiB guards、UI queue late lookup 和 typed result 处理;最后一项通过 V4-only 方法命名、独立 NativeMap API 和隔离测试消除。
|
||||||
370
docs/20_调查/STEP-086_Particle连续渲染唤醒MapLibre整图成本调查_v1.md
Normal file
370
docs/20_调查/STEP-086_Particle连续渲染唤醒MapLibre整图成本调查_v1.md
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
# STEP-086 Particle 连续渲染唤醒 MapLibre 整图成本调查 v1
|
||||||
|
|
||||||
|
状态:`INVESTIGATION / SOURCE_EVIDENCE_CONFIRMED / RUNTIME_PROFILE_PENDING`
|
||||||
|
日期:2026-09-21
|
||||||
|
|
||||||
|
## 1. 调查目标
|
||||||
|
|
||||||
|
本调查针对 NavSea 完整场景中出现的以下现象:
|
||||||
|
|
||||||
|
- Particle 独立 Host / NavSea 最小 Particle Host 中,Particle 本体性能基本正常。
|
||||||
|
- 完整 NavSea 在 Particle OFF、camera 静止时能够正常 idle。
|
||||||
|
- 完整 NavSea 在 Particle ON 后,RenderThread、JS、main CPU 明显高于最小 Particle Host。
|
||||||
|
- 已排除 NavSea 侧两个已确认的 JS 问题:NavigationEngine renderPulse 导致整树重渲染,以及 JsSchedulerProbe 常驻 RAF/16ms timer。
|
||||||
|
|
||||||
|
核心问题是验证:
|
||||||
|
|
||||||
|
> Particle 连续动画是否通过 MapLibre 的全局 repaint/update/render 生命周期,把本来可 idle 的 PBF/vector/style/map scene 持续唤醒。
|
||||||
|
|
||||||
|
本调查优先检查 Shared Core / Renderer / Android frame scheduling,不修改 NavSea ↔ Particle C-001 公共责任边界。
|
||||||
|
|
||||||
|
## 2. 调查基线
|
||||||
|
|
||||||
|
Integration 仓库已同步最新 main:
|
||||||
|
- repo:`/root/sourceserver/navsea-particle-integration`
|
||||||
|
- branch:`main`
|
||||||
|
- HEAD:`39ad8415fbdb76c1822460bb09f3a3f35fe141e0`
|
||||||
|
- Contract:`C-001`
|
||||||
|
Particle 源码调查基线:
|
||||||
|
- repo:`/mnt/sda1/MapLibreParticleLab`
|
||||||
|
- branch:`origin/particle/main`
|
||||||
|
- HEAD:`70f112ea27acaaa4da0a307563b7868937f69691`
|
||||||
|
- commit:`fix: close STEP-085 V4 renderer efficiency`
|
||||||
|
- 当前该 HEAD 中 package/version 字段仍为 `13.4.1-particle.7`。
|
||||||
|
|
||||||
|
注意:用户当前运行环境称为 `.8`,而本次可直接审计的最新远端源码 HEAD 仍显示 `.7` 版本字段。
|
||||||
|
因此本文的源码结论针对 `70f112ea2` 的 MapLibre/Particle 调度架构;后续如 `.8` 对应精确发布提交不同,应再做一次 commit-level 对照。
|
||||||
|
不过本文确认的全局 repaint/render 机制位于 MapLibre Renderer 主链,不依赖 NavSea 业务代码。
|
||||||
|
|
||||||
|
## 3. 当前实测背景
|
||||||
|
|
||||||
|
用户提供的 Android 模拟环境参考量级:
|
||||||
|
|
||||||
|
### 最小 Particle Host
|
||||||
|
- RenderThread:约 38–40%
|
||||||
|
- JS `mqt_v_js`:约 4–5%
|
||||||
|
- main:约 11%
|
||||||
|
|
||||||
|
### 完整 NavSea,Particle OFF
|
||||||
|
- JS:7.08%
|
||||||
|
- RenderThread:1.31%
|
||||||
|
- main:3.34%
|
||||||
|
|
||||||
|
说明完整 NavSea 的静止地图、PBF、UI 和业务逻辑本身能够进入低成本 idle。
|
||||||
|
|
||||||
|
### 完整 NavSea,Particle ON,且已排除两个 NavSea JS 问题
|
||||||
|
- JS:29.07%
|
||||||
|
- RenderThread:80.13%
|
||||||
|
- main:15.51%
|
||||||
|
|
||||||
|
相对于最小 Particle Host,大约额外:
|
||||||
|
- JS:+24 percentage points
|
||||||
|
- RenderThread:+40 percentage points
|
||||||
|
- main:+4~5 percentage points
|
||||||
|
## 4. 源码确认的核心结论
|
||||||
|
|
||||||
|
核心假设成立:
|
||||||
|
|
||||||
|
> 当前 Particle 没有独立的“只绘制 Particle Layer”的动画循环。Particle 的连续动画通过 MapLibre 全局 `needsRepaint` / `onUpdate` / `Renderer::render` 链维持,因此一次合法的 Particle animation frame 会进入完整 MapLibre frame orchestration。
|
||||||
|
|
||||||
|
主链如下:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RenderRasterParticleLayer::needsRepaint()
|
||||||
|
↓
|
||||||
|
RenderOrchestrator::hasTransitions()
|
||||||
|
↓
|
||||||
|
RenderTreeParameters::needsRepaint = true
|
||||||
|
↓
|
||||||
|
Renderer::Impl::render() 完成当前整帧
|
||||||
|
↓
|
||||||
|
Map::Impl::onDidFinishRenderingFrame()
|
||||||
|
↓
|
||||||
|
if (needsRepaint) onUpdate()
|
||||||
|
↓
|
||||||
|
重新构造完整 UpdateParameters
|
||||||
|
↓
|
||||||
|
AndroidRendererFrontend::update()
|
||||||
|
↓
|
||||||
|
MapRenderer.requestRender()
|
||||||
|
↓
|
||||||
|
Renderer::render()
|
||||||
|
↓
|
||||||
|
RenderOrchestrator::createRenderTree()
|
||||||
|
↓
|
||||||
|
完整 Map frame
|
||||||
|
```
|
||||||
|
|
||||||
|
因此 `needsRepaint()` 的语义不是“仅让 Particle Layer 再绘制一次”,而是“MapLibre 需要下一张完整地图 frame”。
|
||||||
|
|
||||||
|
## 5. Particle 每帧如何请求下一帧
|
||||||
|
|
||||||
|
`RenderRasterParticleLayer::needsRepaint()` 在 Particle active、visible、field-ready 且 cadence 到期时返回 true。
|
||||||
|
|
||||||
|
`RenderOrchestrator::hasTransitions()` 会遍历全部 RenderLayer:
|
||||||
|
- `hasTransition()`
|
||||||
|
- `needsRepaint()`
|
||||||
|
|
||||||
|
任一 layer 返回 true,整个 RenderTree 即被标记为 `needsRepaint`。
|
||||||
|
帧结束后,`Map::Impl::onDidFinishRenderingFrame()` 在 Continuous 模式下执行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
if (needsRepaint || transform.inTransition()) {
|
||||||
|
onUpdate();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
而 `Map::triggerRepaint()` 本身也只是:
|
||||||
|
```text
|
||||||
|
impl->onUpdate();
|
||||||
|
```
|
||||||
|
|
||||||
|
Android 显式 `FPS_30/FPS_60` 还存在一层 platform wake:
|
||||||
|
- FPS_30:Handler 约每 34ms 调用一次 `map.triggerRepaint()`
|
||||||
|
- FPS_60:Handler 约每 17ms 调用一次 `map.triggerRepaint()`
|
||||||
|
- AUTO:不使用该 Handler,但 Particle 的 `needsRepaint()` 仍可使连续 frame 自行串联。
|
||||||
|
|
||||||
|
结论:当前没有 layer-scoped repaint 调度语义。
|
||||||
|
|
||||||
|
## 6. 一次 Particle repaint 会重新进入哪些 MapLibre 工作
|
||||||
|
|
||||||
|
`Map::Impl::onUpdate()` 每次都会重新生成完整 `UpdateParameters`,其中包括:
|
||||||
|
- transform state
|
||||||
|
- style loaded state
|
||||||
|
- light
|
||||||
|
- images
|
||||||
|
- sources
|
||||||
|
- layers
|
||||||
|
- glyph/font/sprite 状态
|
||||||
|
- annotation manager
|
||||||
|
- tile LOD 参数等
|
||||||
|
|
||||||
|
随后 `Renderer::render(updateParameters)` 会重新调用:
|
||||||
|
- `RenderOrchestrator::createRenderTree()`
|
||||||
|
- `renderTree->prepare()`
|
||||||
|
- `Renderer::Impl::render()`
|
||||||
|
|
||||||
|
因此不是复用一张静态 RenderTree 后只执行 Particle pass。
|
||||||
|
|
||||||
|
## 7. camera 静止时仍重复执行的非 Particle 工作
|
||||||
|
在当前源码中,即使 camera 完全静止,连续 frame 仍会重复:
|
||||||
|
|
||||||
|
1. 遍历全部 source。
|
||||||
|
2. 对每个 source 遍历 ordered layers,计算 source/layer 关系、visibility、zoom fit、renderability。
|
||||||
|
3. 对启用 source 调用 `source->update()`。
|
||||||
|
4. Vector/PBF source 进入 `RenderTileSetSource::update()` → `TilePyramid::update()`。
|
||||||
|
5. TilePyramid 会重新处理 wrap、zoom、covering tiles、retain 等逻辑。
|
||||||
|
6. 对全部 enabled source 执行 `prepare()`。
|
||||||
|
7. 对全部 renderable layer 执行 `prepare()`。
|
||||||
|
8. symbol layer 进入 cross-tile symbol bookkeeping。
|
||||||
|
9. 根据 placement freshness 条件周期性重新执行 placement。
|
||||||
|
10. Renderer upload pass 遍历 source/layer RenderItem。
|
||||||
|
11. `updateLayers()`、layer-group tweaker、layer-group upload 继续执行。
|
||||||
|
12. opaque pass 重新遍历/绘制普通 layer groups。
|
||||||
|
13. translucent pass 重新遍历/绘制普通 layer groups。
|
||||||
|
14. 重新 present 整个默认 framebuffer。
|
||||||
|
|
||||||
|
因此“大量 PBF/vector/fill/line/symbol 在静止 camera 下由 Particle frame 唤醒”从源码结构上成立。
|
||||||
|
|
||||||
|
## 8. 需要精确区分:不是所有 Style 工作都每帧完整重算
|
||||||
|
|
||||||
|
当前代码并非每帧无条件重新 evaluate 所有 style property。
|
||||||
|
|
||||||
|
普通 RenderLayer 的 `evaluate()` 通常受以下条件约束:
|
||||||
|
- layer added/changed
|
||||||
|
- zoom changed 且该 layer 依赖 zoom
|
||||||
|
- crossfade
|
||||||
|
- active transition
|
||||||
|
|
||||||
|
所以静止 camera、style 不变时,很多 property evaluation 可以跳过。
|
||||||
|
|
||||||
|
同样,完整 symbol placement 也不是严格 60 次/秒。
|
||||||
|
`PlacementController::placementIsRecent()` 使用 placement update period 控制重新 placement;默认逻辑与约 300ms 的 transition/update period 相关。
|
||||||
|
但这些优化不改变主结论:
|
||||||
|
|
||||||
|
> 即使 property evaluation 和完整 placement 并非每帧执行,整张地图仍持续进入 source/layer traversal、source update/prepare、layer prepare、command encoding、普通 layer draw 与 framebuffer present。
|
||||||
|
|
||||||
|
这足以解释复杂 Style 与简单 Test Host 之间的大幅 RenderThread 差异。
|
||||||
|
|
||||||
|
## 9. Particle 是否每帧制造不必要的全局状态变化
|
||||||
|
|
||||||
|
本次静态审计没有发现 Particle 每个 simulation frame 都在修改以下全局状态的直接证据:
|
||||||
|
- camera state
|
||||||
|
- 普通 vector/PBF source data
|
||||||
|
- 全局 Style revision
|
||||||
|
- 普通 layer property
|
||||||
|
- sprite/image revision
|
||||||
|
|
||||||
|
STEP-085 已针对 Particle-local 的不必要成本做过修复,包括:
|
||||||
|
- V4 WeatherColor 静态状态不再因为没有 legacy timeline 而永久 `needsRepaint()`。
|
||||||
|
- V4 Particle 增加 Source-owned renderer cadence。
|
||||||
|
- V4 snapshot 改用 immutable shared pointer,减少稳定 window 的大数组深复制。
|
||||||
|
- WeatherColor 尽量复用 drawable,仅 wrap 变化时重建。
|
||||||
|
- 增加 renderer debug counters。
|
||||||
|
|
||||||
|
所以当前问题与 STEP-085 并不矛盾。
|
||||||
|
|
||||||
|
STEP-085 解决的是:
|
||||||
|
```text
|
||||||
|
Particle 自己是否产生了多余 repaint / copy / drawable rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
本调查发现的是更外层问题:
|
||||||
|
```text
|
||||||
|
即使是“合法的 Particle frame”,当前仍等价于一次完整 MapLibre frame
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. 为什么简单 Particle Host 不能证明完整 NavSea 成本正常
|
||||||
|
简单 Host 的 Style 很小,因此:
|
||||||
|
```text
|
||||||
|
Particle simulation/render
|
||||||
|
+ 少量普通 MapLibre layer 成本
|
||||||
|
≈ 当前约 40% RenderThread
|
||||||
|
```
|
||||||
|
|
||||||
|
完整 NavSea Style 包含大量:
|
||||||
|
- PBF/vector source
|
||||||
|
- fill
|
||||||
|
- line
|
||||||
|
- symbol
|
||||||
|
- text/icon placement
|
||||||
|
- navigation marks
|
||||||
|
- 其他 MapLibre layers
|
||||||
|
|
||||||
|
当 Particle 让 MapLibre 持续出帧后:
|
||||||
|
```text
|
||||||
|
Particle 自身成本
|
||||||
|
+ 大型静态 map scene 的重复 orchestration / prepare / draw
|
||||||
|
≈ 当前约 80% RenderThread
|
||||||
|
```
|
||||||
|
|
||||||
|
因此必须使用复杂真实 Style 做性能判断,不能只使用 Particle Test Host。
|
||||||
|
|
||||||
|
## 11. JsSchedulerProbe 联动现象的当前解释边界
|
||||||
|
|
||||||
|
用户实测:
|
||||||
|
- Particle OFF:Probe OFF 7.08%,Probe ON 8.93%,约 +1.85pp JS。
|
||||||
|
- Particle ON:Probe OFF 约 27~29%,Probe ON 96.47%,约 +69pp JS。
|
||||||
|
|
||||||
|
当前 Native 源码审计没有发现 Particle 每帧主动通过 RN bridge 回调 JS 的证据。
|
||||||
|
|
||||||
|
更合理的当前解释是:
|
||||||
|
- Particle 让 MapLibre/RenderThread 持续处于 frame pressure。
|
||||||
|
- FPS_30/60 时 Android Main Looper 还有 cadence wake。
|
||||||
|
- RN RAF / Choreographer / timer / JS scheduler 在不再 idle 的系统中发生严重放大或竞争。
|
||||||
|
|
||||||
|
但这部分尚不能仅靠 Native 静态源码判定根因。
|
||||||
|
需要 RN scheduler trace / perfetto 才能进一步归因。
|
||||||
|
由于 JsSchedulerProbe 将退出正常运行路径,它不应阻塞当前 Native Renderer 主问题。
|
||||||
|
## 12. 下一阶段必须做的运行时 Profile
|
||||||
|
|
||||||
|
需要固定 camera,并至少构造以下矩阵:
|
||||||
|
|
||||||
|
| 场景 | Style | Particle |
|
||||||
|
|---|---|---|
|
||||||
|
| A0 | 简单 Style | OFF |
|
||||||
|
| A1 | 简单 Style | ON |
|
||||||
|
| B0 | 大量 PBF/vector/symbol/line/fill | OFF |
|
||||||
|
| B1 | 与 B0 完全相同 | ON |
|
||||||
|
|
||||||
|
B1 还应分别验证:
|
||||||
|
- AUTO
|
||||||
|
- 60fps
|
||||||
|
- 30fps
|
||||||
|
|
||||||
|
建议记录以下 Native 阶段的调用次数与 CPU 时间:
|
||||||
|
- `Map::Impl::onUpdate`
|
||||||
|
- `RenderOrchestrator::createRenderTree`
|
||||||
|
- source × layer traversal
|
||||||
|
- VectorSource / TilePyramid update
|
||||||
|
- source prepare
|
||||||
|
- layer prepare
|
||||||
|
- cross-tile symbol bookkeeping
|
||||||
|
- full placement
|
||||||
|
- `updateLayers`
|
||||||
|
- layer-group tweaker
|
||||||
|
- upload pass
|
||||||
|
- opaque pass
|
||||||
|
- translucent pass
|
||||||
|
- present
|
||||||
|
- Particle simulation advance
|
||||||
|
- Particle render/composite
|
||||||
|
|
||||||
|
同时记录 MapLibre 已有 `gfx::RenderingStats`:
|
||||||
|
- encodingTime
|
||||||
|
- renderingTime
|
||||||
|
- numDrawCalls
|
||||||
|
- texture/buffer/uniform updates
|
||||||
|
|
||||||
|
并结合 STEP-085 的 Particle debug counters,区分 Particle 自身和非 Particle 成本。
|
||||||
|
预期验证特征:
|
||||||
|
|
||||||
|
```text
|
||||||
|
B0 + stable camera
|
||||||
|
→ frame/update 很快降到接近 idle
|
||||||
|
|
||||||
|
B1 + stable camera
|
||||||
|
→ onUpdate/createRenderTree 与 Particle frame 数量高度相关
|
||||||
|
→ Vector/PBF source update/prepare 被持续调用
|
||||||
|
→ 普通非 Particle draw calls 持续存在
|
||||||
|
→ B1 non-Particle CPU 显著高于 A1
|
||||||
|
```
|
||||||
|
|
||||||
|
如果该特征成立,则“continuous repaint 唤醒完整 scene”的运行时证据闭环完成。
|
||||||
|
|
||||||
|
## 13. 优化方向分层
|
||||||
|
|
||||||
|
### 第一层:低风险缓解
|
||||||
|
优先验证 30fps / adaptive fps。
|
||||||
|
|
||||||
|
原因:在当前 full-map repaint 架构下,降低 Particle frame 数不仅减少 Particle 本身成本,也会近似同比减少被唤醒的整个 NavSea map scene 成本。
|
||||||
|
|
||||||
|
### 第二层:Renderer animation-only fast path
|
||||||
|
值得优先调查是否可在 MapLibre Native 内部实现:
|
||||||
|
- 复用稳定 map state / RenderTree
|
||||||
|
- Particle 下一帧不重新进入完整 `Map::Impl::onUpdate`
|
||||||
|
- 跳过普通 source update、TilePyramid update、部分 prepare 和 placement
|
||||||
|
- 仍允许必要的 framebuffer redraw
|
||||||
|
|
||||||
|
这属于内部性能实现优化,原则上不需要改变 C-001。
|
||||||
|
|
||||||
|
### 第三层:静态地图 framebuffer cache + Particle composition
|
||||||
|
若目标是让静态 fill/line/symbol/PBF draw call 在 Particle 动画期间也消失,则需要 retained/offscreen composition。
|
||||||
|
|
||||||
|
这比 animation-only fast path 复杂得多,需要处理:
|
||||||
|
- layer ordering
|
||||||
|
- transparent layers
|
||||||
|
- depth/stencil
|
||||||
|
- 3D
|
||||||
|
- world wrap
|
||||||
|
- custom layer/render target
|
||||||
|
- camera/style/source invalidation
|
||||||
|
如果最终要求 Particle 固定作为静态地图之上的独立 overlay,架构可显著简化;但若这种限制成为 Host 可观察的公共语义,则必须重新进入 Integration RFC/Contract 评审。
|
||||||
|
|
||||||
|
## 14. 当前结论
|
||||||
|
|
||||||
|
源码层面可以确认:
|
||||||
|
|
||||||
|
1. Particle 当前通过 MapLibre 全局 `needsRepaint` 维持动画。
|
||||||
|
2. Particle 没有独立 layer-only render loop。
|
||||||
|
3. 一次 Particle repaint 会进入完整 `Renderer::render`。
|
||||||
|
4. camera 静止时仍会重复大量普通 source/layer update、prepare、render 工作。
|
||||||
|
5. 普通 fill/line/symbol/PBF draw 会在连续 frame 中再次提交。
|
||||||
|
6. 并非所有 style property 和 placement 都每帧完整重算,但不足以让复杂地图回到 idle。
|
||||||
|
7. 当前没有证据表明主要原因是 Particle 每帧修改全局 Style/Source/Camera revision。
|
||||||
|
8. STEP-085 已降低 Particle-local 的多余 repaint/copy/rebuild,但没有改变“合法 Particle frame = 完整 MapLibre frame”的架构。
|
||||||
|
9. Test Host Style 太简单,不能用于排除真实大型 Style 下的 full-scene amplification。
|
||||||
|
10. 下一步应先做 A0/A1/B0/B1 定量 profile,再决定 animation-only fast path 或 framebuffer composition。
|
||||||
|
11. 现阶段不需要修改 Particle 公共 API,也不需要修改 NavSea ↔ Particle C-001。
|
||||||
|
|
||||||
|
## 15. 建议的下一任务
|
||||||
|
|
||||||
|
建议下一任务只做 Renderer observability / performance profile,不先做架构重构。
|
||||||
|
|
||||||
|
目标是把完整 frame 拆成 Particle 与 non-Particle 阶段,定量证明:
|
||||||
|
- 每秒进入多少次完整 map update/render;
|
||||||
|
- 大型 Style 中额外 CPU 具体落在哪些 source/layer/placement/draw 阶段;
|
||||||
|
- 30fps 对完整 NavSea 的实际收益;
|
||||||
|
- 哪些阶段可以通过复用 RenderTree 或 animation-only fast path 安全跳过。
|
||||||
|
|
||||||
|
完成该 profile 后,再决定是否进入具体 Renderer 优化实现。
|
||||||
285
docs/20_调查/STEP-095_V13独立严格代码与性能调查_v1.md
Normal file
285
docs/20_调查/STEP-095_V13独立严格代码与性能调查_v1.md
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
# STEP-095 V13 独立严格代码与性能调查 v1
|
||||||
|
|
||||||
|
日期:2026-09-25(Asia/Tokyo)。性质:独立静态审查;只写指定中文文档,不修复源码,不运行构建、测试、codegen、安装或设备操作。
|
||||||
|
|
||||||
|
## 1. 执行摘要与结论边界
|
||||||
|
|
||||||
|
V13 的通用分段解析、Style 顺序及保守 Fast Path 门控有源码依据,但不能据此批准 GPU 正确性或性能。本轮先检查 V12→V13 diff、真实调用链和生命周期,再阅读旧报告逐项挑战。新增优先问题为:离屏目标切换未重置 CPU stencil 掩码记录;Metal depth/stencil 清除契约未完整下传;LayerGroup 抛错后 retained build 仍可能标成功;V4 PLAY 缺少生产时钟推进调用。前三项的实际像素、故障注入和设备行为仍需验证。
|
||||||
|
|
||||||
|
旧 F01/F02/F03/F04/F07 的成本路径成立,但没有本轮 profiler 数据证明热点排序、OOM 或收益。F06 多 Continuous 退出静态 CPU Fast Path、F08 保守全局失效属于显式设计限制。F09 可给出计数跨 topology 错归的具体链,但不证明 stale texture 被复用。F05 的 Host `.10`/native `.13` 错配成立,历史 TS2307 不能仅归因于版本号;F10 只证明协调文档滞后。
|
||||||
|
|
||||||
|
新增成本还包括 V4 全网格物化、UI 线程解码、普通粒子 Metal 离屏等待及 drawable 重建。未找到足以定 P0 的证据;不宣称不存在其他缺陷。调查内容完成,Git 交付结果见阶段记录的收尾条目。**V13 产品运行态不判 PASS:本轮所有构建、测试、设备、性能验证均 NOT RUN。** RDC 仍须独立复核和验证。
|
||||||
|
|
||||||
|
## 2. 冻结基线、环境与范围
|
||||||
|
|
||||||
|
| 项目 | 核实结果 |
|
||||||
|
| --- | --- |
|
||||||
|
| 机器/目录 | `Mac-mini-tei.lan`,Darwin x86_64;`/Users/tei/dev/MapLibreParticleLab` |
|
||||||
|
| 分支/起始 HEAD | `particle/main`;`05878d662308b9a92c20c433c3cd2943d74ae718` |
|
||||||
|
| 起始状态 | 工作树 clean;子模块未见偏离记录提交的标记 |
|
||||||
|
| 远端 | cached origin 与实际 `git ls-remote` 都是上述 HEAD;origin 为 `https://git.eyaku.jp/tei/MapLibreParticleLab/` |
|
||||||
|
| 冻结源码 | `ee0b89e711bf01335b3aa71d36def785cd97fec3` |
|
||||||
|
| V13 功能/旧报告归档 | `016fd1fff6a33cf795c10d4f5c5413d308383677` / `45c4dca84928a57a2cf0226737c34cabd7fbfbfb` |
|
||||||
|
| V12 比较点 | tag 解引用提交 `7eaff6e12360e26591dceb5e011129eeab7ba4c7` |
|
||||||
|
| V13 tag | `.13` tag 对象 `bccbceddec70f7a2b6362bce653f9c577aa2db1d`,解引用为冻结源码,远端一致 |
|
||||||
|
| manifest sourceCommit | `ddf20dec9fe9cd05b77274827b6825829500dd5a`;至冻结提交为发布元数据收尾,生产源码一致 |
|
||||||
|
| 模型记录 | 用户在本次同一会话恢复指令中明确指定 `gpt-6-astra high`,与任务要求一致;这是当前会话的用户指定记录。未获得可独立核验的底层启动参数,不声称已独立验证实际模型/effort |
|
||||||
|
|
||||||
|
冻结源码至起始 HEAD 仅新增旧审查报告及 STEP-095 任务书;下文源码行号按冻结源码,与本轮起始工作树对应。V12→V13 整体 diff 为 45 文件、2393 行增加/203 行删除(含文档),主要新增 segment/parser/resolver、runtime diagnostics、renderer/orchestrator 调度与平台桥接,并非粒子数值 shader 的整体重写。
|
||||||
|
|
||||||
|
已读取 AGENTS、PARTICLE_BASELINE、项目规范、项目总则 README 所列当前有效规则及基线 v2,交叉阅读 STEP-088~094 的任务/设计/阶段/报告相关内容。总则仍含早期 `.3` 和 Linux 主线描述,本任务以 Mac `.13` 冻结点为准,不重写历史文档。官方 Core 基线 `18545ca10d296704b2cba675b182ff2c8da24588`,Android 官方 13.4.1、iOS 官方 6.28.0。
|
||||||
|
|
||||||
|
### 2.1 发布坐标(仅核对仓库记录)
|
||||||
|
|
||||||
|
来源:`release/maplibre-particle/release-manifest.json` 与 `release/maplibre-particle/ios/release-manifest.json`。
|
||||||
|
|
||||||
|
| 产物 | 坐标 | manifest 大小/SHA-256 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| RN | `@navsea/maplibre-particle-rn@13.4.1-particle.13`,MLRN 11.3.6 | 25,966 bytes;`3a726db06cb967fe56de77ca4a72feed98cd642a2d1b73fd51b1531d15ef7539` |
|
||||||
|
| Android OpenGL | `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.13` | 169,786,873 bytes;`ddf1deced00cc963d1d625c73c68c449232602113a1490349e69100473dda82b` |
|
||||||
|
| iOS Metal | `6.28.0-particle.13`,MapLibre XCFramework/SPM exactVersion | 8,418,198 bytes;`8d47d9f6d44479aff51299c213887bd9be204da320da4fd4fff15dd064930556` |
|
||||||
|
|
||||||
|
manifest 的 `PUBLISHED_PUBLIC_READBACK_PASS` 是 HISTORY。本轮没有下载、重算产物散列、重建或核实设备实际链接版本;文档提交不是新 V13 发布。
|
||||||
|
|
||||||
|
### 2.2 调用链和未覆盖范围
|
||||||
|
|
||||||
|
已沿 segment JSON/plan → runtime → orchestrator/renderer → cache → PaintParameters/GL/Metal target,及 canonical/QBIN/profile → Store/Clock/projection → RasterArraySource immutable Impl → WeatherColor/Particle RenderLayer → Field/State/Trail GPU 追踪;检查 Android Java/Kotlin/JNI、iOS ObjC++、RN typed API/JSON Spec、Host package/lock/plugin及 Integration 只读状态。阅读 resolver/cache/diagnostics/field/decoder/reseed 测试源码,不等同执行测试。
|
||||||
|
|
||||||
|
未逐行形式化验证全部官方 renderer/driver、Vulkan、业务 provider/HTTP/Route/ETA或所有并发交错;未使用设备、capture、sanitizer、fuzzer。此报告不是全仓无缺陷证明。
|
||||||
|
|
||||||
|
## 3. Finding 总表
|
||||||
|
|
||||||
|
D=源码可复核缺陷路径;C=源码确定成本;R=后果需运行验证;E=环境/文档偏差。置信度均指源码链而非事故发生概率。每项详细位置、修复与验收见后文。
|
||||||
|
|
||||||
|
| 编号 | 严重度/类别/置信度 | 触发与影响 | 旧报告关系 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| N01 | P1/D+R/高 | 相同 tiles 跨 target,CPU stencil 映射复用,后段可能被裁掉 | 新增 |
|
||||||
|
| N02 | P1/D+R/高 | Metal retained depth/stencil clear 未完整转换,模拟器 attachment 缺口 | 新增 |
|
||||||
|
| N03 | P1/D+R/高 | group 绘制抛错被 catch,仍标 cache 成功,可能持续复用残图 | 新增 |
|
||||||
|
| N04 | P1/D/高 | V4 只 PLAY 不继续 set time,生产时钟无推进调用 | 新增 |
|
||||||
|
| N05 | P2/D/高 | JSON NUL/重复 strategy 被接受,严格解析失真 | 新增 |
|
||||||
|
| N06 | P2/D+R/高 | 结构合法但数值非法的场先入库,物化失败后可用投影被置空 | 新增 |
|
||||||
|
| N07 | P2/D/高 | 同 view 重复 attach,listener 和持有引用递增 | 新增 |
|
||||||
|
| C01 | P1/C/高 | full diagnostics 锁内深复制进入 fast/render | F01 |
|
||||||
|
| C02 | P1/C+R/高 | Metal retained build 与普通 State/Trail target 等待 | F02,新增扩大范围 |
|
||||||
|
| C03 | P1/C/高 | 内容 dirty 释放同尺寸可复用 GPU allocation | F03 |
|
||||||
|
| C04 | P1/C+R/高 | 多 R 全视口资源无总预算;Store 也缺 fieldKey 总预算 | F04,新增场预算 |
|
||||||
|
| C05 | P2/C/高 | ordinal 线性前缀遍历,fast update 仍扫全部 items | F07,新增扫描 |
|
||||||
|
| C06 | P1/C+R/高 | 同时间/mix-only 整网格物化;提交链可占用 UI | 新增 |
|
||||||
|
| C07 | P2/C/高 | tick 重建 drawable/attributes/tweaker,窗口切换复制场 | 新增 |
|
||||||
|
| O01 | P2/D/高 | 新 topology 先发布,旧 cache 析构向新 index 计数 | F09 |
|
||||||
|
| E01 | P1/E/高 | Host RN `.10` 与 Android 插件 `.13` 错配 | F05 |
|
||||||
|
| E02 | P3/E/高 | Integration 仍旧状态,实际接入 UNKNOWN | F10,降级 |
|
||||||
|
|
||||||
|
## 4. 新增正确性和生命周期发现
|
||||||
|
|
||||||
|
### N01:CPU stencil 记录跨 framebuffer 复用
|
||||||
|
|
||||||
|
证据:`src/mbgl/renderer/renderer_impl.cpp:798-847,938-945`;`src/mbgl/renderer/retained_underlay_cache.cpp:304-319`;`src/mbgl/renderer/paint_parameters.cpp:185-201,365-374`;`src/mbgl/gl/layer_group_gl.cpp:67-98`;`src/mbgl/mtl/tile_layer_group.cpp:118-119`。
|
||||||
|
|
||||||
|
最短链:同一个 PaintParameters → 第一个 R 清 stencil、写 mask 并记录 tileClippingMaskIDs → 第二个 R 新 target/清 stencil → tileIDsCovered 为真而跳过写 mask → draw 仍 Equal 非零 ID。CPU 记录属于前一 target,后一 GL target 实际为零。main buffer 清除后也未清 CPU 记录;updateStencilBufferAvailability 不是映射重置。
|
||||||
|
|
||||||
|
触发用例:同 vector source、同 tile 集合的两个 fill 分置相邻 R-R,首次都 build;或 R build 后同 tiles 的 C 绘入主目标。tile 集合变化可触发清记录,所以不是所有画面必坏。V13 任意 R-R 扩大暴露面,V12 多 target 也应回归,不能断言只由 V13 引入。
|
||||||
|
|
||||||
|
最小修复:target/pass 切换时明确失效 CPU mask/编号,或按 target generation 隔离,并保证 GPU clear 一致。验收:GL/Metal cache OFF 对照,同 source R-R/R-C-R 的首帧、rebuild、reuse 像素和 stencil capture。缺口源码已确认,实际像素 NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### N02:Metal depth/stencil 描述未完整下传
|
||||||
|
|
||||||
|
证据:`retained_underlay_cache.cpp:304-310` 请求 clearDepth=1、clearStencil=0;`src/mbgl/mtl/render_pass.cpp:13-38` 只转换 clearColor;`src/mbgl/mtl/offscreen_texture.cpp:32-83,139-148` 绑定附件但该路径未设置 depth/stencil clear/load/store。模拟器不创建 stencilTexture,也未将 combined depth/stencil texture 绑定 stencilAttachment;格式选择见 `src/mbgl/mtl/texture2d.cpp:99-104`。
|
||||||
|
|
||||||
|
触发:Metal retained tile/depth 绘制,尤其首次 target、simulator。源码确认契约缺口,但未测驱动默认内容/validation 行为,不宣称所有真机黑屏。isRenderable 仅验证 color,depth/stencil create 在 bind,早期就绪判断不能证明所有附件就绪。
|
||||||
|
|
||||||
|
最小修复:完整翻译 descriptor、正确绑定 combined attachment,并验证所有附件后允许 build;保持官方接入薄。验收:Metal API Validation、真机/模拟器 capture 核对 attachment/load/store/clear,故障注入验证安全 fallback。NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### N03:绘制异常未阻止 cache 发布为成功
|
||||||
|
|
||||||
|
证据:`src/mbgl/renderer/render_orchestrator.hpp:156-178` 的 visitor catch 只通知 observer;`renderer_impl.cpp:798-824` 随后无错误结果判断,finishBuild/markBuildSucceeded。
|
||||||
|
|
||||||
|
触发:R 的一个 layerGroup.render 抛错,observer 正常返回,其他 group 继续,残缺图像可变 valid 并在稳定帧 reuse。反例:资源准备阶段已有失败回退(`retained_underlay_cache.cpp:240-255`、`renderer_impl.cpp:662-677`),问题不等于所有资源失败都没有 fallback。
|
||||||
|
|
||||||
|
最小修复:build 汇总明确成功状态,失败 target 不发布/复用,可恢复错误进入完整渲染或约定安全失败。验收:第二个 group 故障注入,确认 successful build/reuse 不增长、无 stale composite、恢复和资源释放正确。NOT RUN。
|
||||||
|
|
||||||
|
### N04:V4 PLAY 没有生产时钟推进
|
||||||
|
|
||||||
|
证据:`src/mbgl/style/sources/raster_array_source.cpp:453-470`;`src/mbgl/particle/field/field_runtime.cpp:196-220`;`src/mbgl/renderer/layers/render_particle_validation_layer.cpp:1174-1189,1276-1278`。生产树 src/include/platform/packages 中 advanceForecastClock 仅见声明和定义,无调用。Android `platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/WeatherRuntime.java:225-242`、iOS `platform/ios/src/MLNParticleFieldRuntime.mm:147-165` 的 wake 只 repaint。
|
||||||
|
|
||||||
|
最短链:设一次 requested time → PLAY 改 clock state → repaint/particle cadence → renderer 读取不变的 immutable projection.mix。粒子运动不能证明预报时间在播放。Host 每次显式 set time 可绕过,但不代表 PLAY 本身有效。
|
||||||
|
|
||||||
|
最小修复:在既有 map/source 所属线程接入唯一单调时钟,明确速度、pause/resume、后台和销毁语义,不建立第二套时间真相。验收:仅设一次时间,PLAY 的 .5/1/2 倍 mix、PAUSE、跨窗口和恢复;`test/particle/field_runtime.test.cpp:272-310` 手动 advance 单测不覆盖生产接线。本轮 NOT RUN。
|
||||||
|
|
||||||
|
### N05:Segment JSON NUL 截断与重复键
|
||||||
|
|
||||||
|
证据:`src/mbgl/renderer/render_segment_json.hpp:16-49`。Parse 用 C 字符串,strategy/startLayerId 用 GetString 未带 GetStringLength;`"RETAINED\u0000x"` 截成 `RETAINED`,`"boundary\u0000x"` 可解析到另一真实 ID。segment 两个重复 strategy 满足 MemberCount<=2,白名单不检唯一性,读取首值。
|
||||||
|
|
||||||
|
触发可由 primitive JSON/native 入口构造,TS typed API 不替代校验。普通未知 strategy/schema、missing/reversed 仍会 invalid/fallback。最小修复:长度感知解析/构造,拒绝 NUL/重复键,保持 invalid 清旧 plan 语义。验收:上述 payload、raw NUL 后缀、正常 unicode、未知字段、invalid→valid及两桥接错误归一化。NOT RUN。
|
||||||
|
|
||||||
|
### N06:数值非法的场在入库后才被拒绝物化
|
||||||
|
|
||||||
|
证据:`src/mbgl/particle/ingress/qbin_v2_decoder.cpp:41-73` 校验结构、CRC、scale/offset但不扫描 Float32 样本;`src/mbgl/particle/field/field_profile_validator.cpp:77-166` 匹配契约;`field_projection_materialization.cpp:38-67,97-135` 才检查解码数值;`raster_array_source.cpp:422-440,496-522` 先 submit,再物化,失败 next 对应投影为空。
|
||||||
|
|
||||||
|
触发:已有绑定字段,同时间更高 revision、CRC 正确、valid=1、样本 NaN/Inf 的 QBV2。结构/契约可接受,替换旧帧后物化失败,但 submit 返回 AcceptedInserted/AcceptedReplacedRevision。有限 scale 乘法溢出也应纳入验证。此处不声称 NaN 上传 GPU——物化拒绝正是反例;缺陷是 accepted/可渲染语义及旧状态保留脱节。
|
||||||
|
|
||||||
|
最小修复:明确 ingress 数值约束,入库前校验或事务式验证,保留合法无效 cell/通用未绑定 plane 语义。验收:好旧帧→坏高 revision,检查返回值、Store/投影保持、noData/validity、极值及恢复。NOT RUN。
|
||||||
|
|
||||||
|
### N07:Android attach listener 积累
|
||||||
|
|
||||||
|
证据:`packages/maplibre-particle-rn/android/src/main/java/org/navsea/maplibre/particle/weather/WeatherRuntimeBridgeModule.kt:30-63`。每 attach 新增捕获 concrete map 的 listener;显式 detach 只 dispose runtime,view detach 回调也不移除 listener。
|
||||||
|
|
||||||
|
同一个存活 view 重复 attach/detach,listener/引用及回调为 O(k)。未证明 view 回收后永久泄漏或 UAF;每次重新解析 view、没有全局 reactTag registry 是正面约束。最小修复:view/map 单一 listener、attach 幂等、明确 remove 生命周期。验收:同 view 100 次、换 view、卸载/后台恢复,listener/dispose/存活对象回基线。NOT RUN。
|
||||||
|
|
||||||
|
## 5. 性能、资源与观测
|
||||||
|
|
||||||
|
### C01:完整 diagnostics 深复制进入热路径
|
||||||
|
|
||||||
|
证据:`renderer_impl.cpp:198-208,500-505` → `retained_underlay_runtime_state.hpp:243-254` 锁内复制 styleLayers、segments.layers、字符串。成功 fast frame 可两次,不是所有帧固定两次。成本 O(L+S),锁竞争程度待测。修复:一致的轻量热快照与完整外部 pull 分开,不能无同步拆字段。验收:稳定 30/60 FPS 的完整快照次数应随外部 pull,测 alloc/frame、锁等待、CPU;无收益百分比。
|
||||||
|
|
||||||
|
### C02:Metal 等待不限于 retained rebuild
|
||||||
|
|
||||||
|
证据:`retained_underlay_cache.cpp:316-319` → `src/mbgl/mtl/command_encoder.cpp:29-31` → `mtl/offscreen_texture.cpp:86-91` commit/waitUntilCompleted,失效 R 逐段同步;稳定 reuse 没有 retained build wait。
|
||||||
|
|
||||||
|
新增链:`src/mbgl/renderer/render_target.cpp:71-119` 普通 target 也 present;`render_particle_validation_layer.cpp:1990-2111` tick 注册 State/Trail 写目标,初始化可能更多。因此 retained 稳定也可能有多个普通粒子 wait,属继承成本。修复前先 capture buffers、wait 栈及依赖,再设计有序异步提交,不能直接删 wait。验收:State A/B 无别名,Trail/composite 顺序与轨迹一致,CPU wait/GPU frame time 成对比较。NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### C03:内容失效与 allocation 销毁耦合
|
||||||
|
|
||||||
|
证据:`retained_underlay_cache.cpp:161-237` revisionChanged 同时 invalidate/release;`:240-255,365-374` 重建/释放 texture/compositor。触发为内容/相机等 revision 改变,不是稳定帧无条件分配。修复:contentDirty 与尺寸/context/格式的 resourceInvalid 分开,同尺寸有效 context 保留 allocation。验收:mutation、camera、resize、context loss 分开统计 build/create/release/peak;先保证 N01/N02 clear 正确。耗时收益待测。
|
||||||
|
|
||||||
|
### C04:多段 GPU 与多字段 CPU 无总预算
|
||||||
|
|
||||||
|
证据:`renderer_impl.cpp:506-524`;`retained_underlay_cache.cpp:240-255`;`src/mbgl/gl/offscreen_texture.cpp:18-35`、`mtl/offscreen_texture.cpp:20-55`。每 R 全视口 color/depth/stencil;RGBA8 color 理论下界 R×W×H×4 bytes,另有附件、alignment、主 framebuffer、State/Trail及延迟释放,不能把逻辑 release 当物理显存回收。
|
||||||
|
|
||||||
|
新增:`src/mbgl/particle/field/field_runtime.cpp:223-230` 对新 fieldKey 建 series;`include/mbgl/particle/field/field_runtime.hpp:115` 每 series 默认容量并非总 key/总 bytes 上限。QBIN 单文件64 MiB/解码32 MiB 上限也不约束累计持有量。
|
||||||
|
|
||||||
|
修复:map 预算、分配前溢出安全估算、超限明确 full-render/拒绝策略,不合并相邻 R、不淘汰仍绑定窗口。验收:R×viewport×字段数阶梯、allocation failure/context loss、30分钟以上 peak/回收。OOM/泄漏未证实,NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### C05:ordinal 前缀遍历和全层扫描
|
||||||
|
|
||||||
|
证据:`render_orchestrator.hpp:156-178` 的 multimap std::distance 是线性,S 次范围定位可能 O(S×G);`render_orchestrator.cpp:1202-1226` fast updateParticleLayers 仍遍历完整 items,非粒子只跳过 update;`:973-975` 按 retained 稳定性还可能扫 source fading。
|
||||||
|
|
||||||
|
修复:topology 改变时缓存 global ordinal/粒子列表,保持 generation 和原始全局深度编号。验收:固定粒子层扩大 L/G/S,测 CPU/调用次数并回归 reorder/delete。实际热点占比待测。
|
||||||
|
|
||||||
|
### C06:同时间/mix-only 仍全网格物化,UI 成本遗漏
|
||||||
|
|
||||||
|
证据:`raster_array_source.cpp:453-455,496-522` 在 sameProjection 前分别物化 color/particle;`field_runtime.cpp:196-200` 相同非零时间也返回 true;`field_projection_materialization.cpp:69-75,97-211` 分配/遍历 validity、T0/T1 scalar/vector。Wind 双绑定可重复同向量物化。mix 更新产生新 Impl/onSourceChanged,`render_orchestrator.cpp:361-365` 全局 SourceMutation 可进一步失效所有 R。
|
||||||
|
|
||||||
|
桥接:Android withMap UI 调度经 `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp:1501-1517` 同步到 Source;iOS `packages/maplibre-particle-rn/ios/WeatherRuntimeBridge.mm:105-120,235-240` 的 withMap 与 `platform/ios/src/MLNParticleFieldRuntime.mm:222-252` 也同步提交 Core。预读取文件不等于 CRC/解码/物化已经离开 UI。成本随格点/plane 增长,实际卡顿/ANR未测。
|
||||||
|
|
||||||
|
修复:相同时间早退,按端点 identity/binding 共享不可变物化、mix 单独更新;若 worker 解码,owner-thread 发布必须带 map/source generation,禁止 worker 直接改 Source。局部失效必须先有完整依赖。验收:同时间、mix-only、跨窗口、双绑定、最大网格的 CPU/alloc/UI阻塞/upload/rebuild计数。NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### C07:tick 对象重建及端点复制
|
||||||
|
|
||||||
|
证据:`render_particle_validation_layer.cpp:826-904` clearDrawables 后新 attributes/builder/tweaker;`:1668-1669,1990-2111` 更新 target 注册及 State/Trail/composite;`:657` createOrUpdate UBO 不证明跨新 drawable 复用;`:1494-1499,1538-1563` 场端点切换复制数组。
|
||||||
|
|
||||||
|
反例:State/Trail textures 和共享顶点不是每 tick 全部重建,field 复制主要在窗口边界,不能称 CPU 每帧模拟全部粒子或无条件上传场。修复:稳定 drawable 保留,仅更新 uniforms/texture,端点共享不可变存储。验收:tick/bootstrap/reseed/resize/端点轮换分别测对象、UBO、upload和峰值。NOT RUN。
|
||||||
|
|
||||||
|
### O01:旧 cache release 记入新 topology
|
||||||
|
|
||||||
|
证据:`retained_underlay_runtime_state.hpp:173-240` 先发布新 entries;`renderer_impl.cpp:500-524` 再 clear 旧 cache;`retained_underlay_cache.cpp:120-122,365-374` 析构按旧 segmentIndex 计数;runtime `:256-272` 寻找当前 entry。
|
||||||
|
|
||||||
|
旧 index 0 的 R 换成新 R,旧 release 可记入新 R;新 index 为 C/越界时 per-segment 计数丢失。不证明 GPU 未释放或旧图复用。另 runtime `:198` root fallback 只表示 resolution invalid,不能覆盖 cache OFF/unsupported/resource fallback;消费方不可读成 GPU 当前 retained 成功。
|
||||||
|
|
||||||
|
修复:计数带 cache identity/generation,聚合历史与当前段分开,明确两类 fallback。验收:真 cache R-R→C-R、相同 index 换边界、disable/restore析构计数,纯手动 counter 测试不足。NOT RUN。
|
||||||
|
|
||||||
|
## 6. 环境发现和旧 F01~F10 复核
|
||||||
|
|
||||||
|
E01 定位:`integration/react-native/MapLibreParticleRNTestHost/package.json:12`、`package-lock.json:12,1765-1766` 为 `.10`,`plugins/withParticleNativeAndroid.js:9` 注入 `.13`,当前 package/native 为 `.13`。默认干净 Host 不能证明 V13 全链。旧 typecheck exit2/TS2307/TS7006 为 HISTORY;缺模块和旧 API 是不同原因,本轮未复跑。修复方向:后续单独对齐 package/lock/native resolved version,干净安装、typecheck/codegen及设备调用;不能把临时工作树链接 PASS 当默认包 PASS。本轮不改依赖。
|
||||||
|
|
||||||
|
E02 定位:只读 `/Users/tei/dev/navsea-particle-integration` HEAD `39ad8415fbdb76c1822460bb09f3a3f35fe141e0`,`compatibility/MATRIX.md` 与 `status/` 仍含 Sept18 `.5`、iOS 等待、RN NOT_STARTED。它与本仓 `.13` 是不同时点,真实 NavSea 接入 UNKNOWN。降 P3 文档项,建议负责人以接入 commit/包/设备证据更新,不改 Core 协议;本轮不改 Integration。
|
||||||
|
|
||||||
|
| 旧项/原结论 | 当前意见 | 修正和依据 | 补证 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| F01 P1 深复制 | CONFIRMED | C01,成功 fast 可两次而非每帧必两次 | alloc/锁/CPU NOT RUN |
|
||||||
|
| F02 P1 retained wait | CONFIRMED | C02,限定 build,补普通粒子 wait | 收益 NEEDS_RUNTIME |
|
||||||
|
| F03 P1 dirty 释放 | CONFIRMED | C03,稳定 reuse 不重建 | 内容/resize/context分测 |
|
||||||
|
| F04 P1 无预算 | CONFIRMED | C04,无预算不等于已 OOM | peak/压力 NEEDS_RUNTIME |
|
||||||
|
| F05 P1 Host 不可复现 | QUALIFIED | E01,错配确定,旧 TS2307非本轮且不可仅归版本 | 干净安装 NOT RUN |
|
||||||
|
| F06 P1 multi-C full CPU | QUALIFIED | 显式设计限制,建议 P2;不等于关闭 GPU retained reuse | 相同Style A/B NOT RUN |
|
||||||
|
| F07 P2 ordinal 线性 | CONFIRMED | C05,优化定位不能改全局编号 | S/G基准 NOT RUN |
|
||||||
|
| F08 P2 全局失效宽 | QUALIFIED | 保守设计,未证明 stale;未知依赖不能裁窄 | source/tile/glyph依赖 |
|
||||||
|
| F09 P2 错归风险 | CONFIRMED | O01具体先换entries后旧析构链;不证明stale texture | 真cache生命周期 NOT RUN |
|
||||||
|
| F10 P2 文档漂移 | QUALIFIED | E02降P3;不推定实际接入 | 负责人跨项目核对 |
|
||||||
|
|
||||||
|
没有为凑数将整个旧项标 DISPROVED;被否定的是“多C关闭所有GPU reuse”“计数错归证明旧纹理复用”“.10必然造成TS2307”“单测证明像素正确”等外推。旧报告未明确作这些断言之处,也不反向指控它作过。
|
||||||
|
|
||||||
|
## 7. 反例、边界和设计限制
|
||||||
|
|
||||||
|
1. `render_segment_plan.hpp:32-157` 采用完整 ordered IDs(含 hidden),边界存在且递增,全R/全C合法,相邻同策略不合并,V12经同模型映射;首段可省 startLayerId 是契约。普通 invalid 清 resolution、恢复重解析有依据。
|
||||||
|
2. reorder 不是“仅added/removed所以遗漏”:`src/mbgl/renderer/style_diff.cpp:9-42` 序列差异体现移动,`render_orchestrator.cpp:318-329` 重建 orderedLayers,未报告无可达链的 reorder bug。
|
||||||
|
3. `renderer.cpp:37-59` 在 full createRenderTree 前清 prepared tree,fast gate 检查 style/source 等输入;不能只见缓存引用便断言 UAF。context/destroy 极端交错仍需验证。
|
||||||
|
4. `renderer_impl.cpp:798-918` 保留 opaque reverse/translucent forward 与 global ordinal,compositor premultiplied alpha/depth disabled 有源码依据。但不能推导所有分段像素等价,N01/N02仍为 gate;不凭空宣称所有 alpha 顺序错误。
|
||||||
|
5. Cache OFF、不支持内容、资源准备失败已有普通路径;多 C 只退出 CPU fast,R可reuse。3D/debug/legacy/custom gate保守,不建议绕过求性能。
|
||||||
|
6. QBIN有文件/plane/cell/decoded上限、CRC/bounds/长度检查,Clock有速度/整数溢出检查;FieldSeries按时间/revision处理重复/旧版本并shared持有窗口。未找到可证实的任意写/越界链;N06是数值发布语义。旧canonical adapter已有有限性检查,不能一概否定。
|
||||||
|
7. State A/B、Trail、T0/T1和方向/速度各有路径;`render_particle_validation_layer.cpp:1671-1810` resize重建Trail、按zoom-out gesture决定reseed并保留其他状态,不代表orientation/zoom-out设备通过。nearest/linear采样及双端点validity需数值视觉基准,本轮未断言错误插值。
|
||||||
|
8. 共享海陆mask尚无验收证据:`field_projection_materialization.cpp:77-92` snapshot displayDomain为GLOBAL;`render_particle_validation_layer.cpp:278-293,1314-1324` 有synthetic coastal mask路径。不能据此证明Wave/Tide/Current共用正式Land/Ocean Mask;陆地覆盖显示不等于mask。列为架构/接入证据缺口,不擅改协议。
|
||||||
|
9. 桥接保持map-scoped,typed object经内部JSON,没有引入业务协议;普通invalid plan会关闭旧计划。符号接线不等于ABI/codegen或销毁竞态已测。
|
||||||
|
|
||||||
|
## 8. V12/V13 架构与成本比较
|
||||||
|
|
||||||
|
L=层数、G=LayerGroup数、S=段数、R=retained数、W/H=物理视口、P=格点。仅源码模型,不是测量。
|
||||||
|
|
||||||
|
| 维度 | V12 | V13 | 判断 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| topology | 固定range,最多两个R | 任意独立R/C,相邻R不合并 | 扩大管理/资源上界,不自动提高FPS |
|
||||||
|
| GPU复用 | 固定retained部分 | 每R独立 | Layer fingerprint局部化,全局依赖仍保守 |
|
||||||
|
| CPU fast | 合格单连续区间 | 恰好一个C且gate满足 | 多C full CPU不等于GPU cache关闭 |
|
||||||
|
| diagnostics | 旧统计结构 | 完整Style和重复段层列表 | C01 O(L+S)复制 |
|
||||||
|
| 范围定位 | 固定数量 | S段前缀可能O(S×G) | 实际占比待测 |
|
||||||
|
| retained资源 | 至多两组 | O(R×W×H) | 物理峰值与OOM未测 |
|
||||||
|
| Metal等待 | 固定targets | 随失效R增加 | 普通State/Trail是继承成本 |
|
||||||
|
| 场/粒子更新 | 已有物化/drawable | 多数继承 | C06/C07不全归因V13 |
|
||||||
|
|
||||||
|
STEP-090 fast ON 21.7%/OFF97.0% RenderThread仅历史特定实验,不外推V13。历史一次退出及随后短时未复现,既不证明泄漏也不证明长稳。必须同设备、Style、源数据、视口、刷新率/热状态配对,先像素正确再比CPU/GPU。
|
||||||
|
|
||||||
|
## 9. 测试证据与 RDC 验证矩阵
|
||||||
|
|
||||||
|
| 项目 | 历史 | 本轮 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| STEP-094 GL构建、8 suites/43 tests | 报告PASS | HISTORY/NOT RUN;纯状态解析不能验证GPU |
|
||||||
|
| Field/QBIN/viewport69 tests、package TS | 旧审查PASS | HISTORY/NOT RUN |
|
||||||
|
| Metal-only iPhoneOS编译 | STEP-094 PASS | HISTORY/NOT RUN;不验证附件/像素 |
|
||||||
|
| Android APK、RN codegen | STEP-094 PASS,codegen用底层入口 | HISTORY/NOT RUN |
|
||||||
|
| Host工作树替换TS及旧默认失败 | 两种不同依赖环境 | HISTORY/NOT RUN,可同时成立 |
|
||||||
|
| 本轮静态/文档检查 | 不适用 | 已执行;Git结果见阶段收尾 |
|
||||||
|
|
||||||
|
以下所有运行项本轮均未执行。
|
||||||
|
|
||||||
|
| Gate | Android GL | iOS Metal真机/模拟器 | Vulkan/其他 | 证据要求 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| R/C/R-R/R-C-R/C-R-R/R-R-C/R-C-R-C-R | NOT RUN | NOT RUN | NOT RUN | cache OFF/ON截图、像素差、pass/ordinal |
|
||||||
|
| 同source stencil、半透明fill/line/symbol | NOT RUN | NOT RUN | NOT RUN | 首帧/rebuild/reuse、depth/stencil capture |
|
||||||
|
| hidden/reorder/delete/restore/invalid | NOT RUN | NOT RUN | NOT RUN | 下一帧无stale、revision与plan |
|
||||||
|
| OFF/资源失败/draw失败/context loss | NOT RUN | NOT RUN | NOT RUN | 失败不可reuse,资源配对与恢复 |
|
||||||
|
| PLAY/T0T1/坏数值/乱序/旧revision | NOT RUN | NOT RUN | NOT RUN | time/mix、返回码、旧窗口保持策略 |
|
||||||
|
| pan/zoom-out/pitch/resize/orientation | NOT RUN | NOT RUN | NOT RUN | state/trail/reseed及无额外field提交 |
|
||||||
|
| 30/60FPS,单C/多C、mutation | NOT RUN | NOT RUN | NOT RUN | CPU/GPU p50/p95/p99、miss/wait/alloc |
|
||||||
|
| 预算/峰值/30分钟以上长稳 | NOT RUN | NOT RUN | NOT RUN | R×视口×字段阶梯、bytes/后台/卸载 |
|
||||||
|
| attach/detach/双map/销毁中完成 | NOT RUN | NOT RUN | NOT RUN | owner thread、generation、listener |
|
||||||
|
| 干净包版本/ABI/codegen | NOT RUN | NOT RUN | 不适用 | resolved包/散列/生成接口/实际调用 |
|
||||||
|
|
||||||
|
RDC先记录HEAD/manifest/tag/构建配置;以下是既有精确命令,本轮未执行。新增缺陷尚未补测试,旧过滤集PASS不能关闭本报告。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake --build build-step088-opengl --target mbgl-test-runner -j3
|
||||||
|
build-step088-opengl/mbgl-test-runner --gtest_filter='RenderSegmentPlan.*:RenderSegmentDiagnostics.*:ContinuousLayerRange.*:RetainedDynamicBoundary.*:RetainedLayerGroupRange.*:RetainedUnderlayCache.*:RetainedUnderlayRevision.*:ParticleFastPath.*'
|
||||||
|
build-step088-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*'
|
||||||
|
cmake --build build-ios --target ios-sdk-static -- -quiet
|
||||||
|
```
|
||||||
|
|
||||||
|
Metal命令前确认构建目录MLN_WITH_METAL=ON、MLN_WITH_OPENGL=OFF;缺目录由RDC按规范配置,不复用未知缓存。RN先解决真实resolved `.13`前提,再按STEP-094/083现行入口typecheck/codegen。失败回传首个真实error、target、上下文和日志路径,成功仅exit/耗时/摘要。
|
||||||
|
|
||||||
|
## 10. 优先级与建议修复批次
|
||||||
|
|
||||||
|
只建议,不创建修复任务、不改源码。
|
||||||
|
|
||||||
|
| 批次 | 范围 | Gate/安全约束 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| A 正确性 | N01/N02/N03 target/Metal/失败发布,N04时钟另小提交 | 最小复现、故障注入、两平台OFF基线、PLAY端到端;不借机全面重构 |
|
||||||
|
| B 无设备可先回归 | N05/N06/N07/O01/E01 | 纯测试/桥接由RDC执行;版本可复现,不代替A的GPU gate |
|
||||||
|
| C CPU安全优化 | C01轻快照、C05ordinal/粒子列表、C06相同时间早退/端点复用 | 同步一致、Style唯一顺序、调用/分配次数证明 |
|
||||||
|
| D GPU资源/同步 | C02/C03/C04/C07 | 先profile,再预算和allocation/content分离;明确buffer依赖,不能直接删wait |
|
||||||
|
| E 较大架构 | multi-C fast、source依赖、异步大场、正式共享mask | 单独设计,未知依赖继续全局失效,不引入provider/HTTP业务调度 |
|
||||||
|
|
||||||
|
## 11. Git 与交付
|
||||||
|
|
||||||
|
仅四个允许路径:本文、`docs/50_Step记录/STEP-095_V13独立严格代码与性能审查_v1.md`、`docs/报告/STEP-095_V13独立严格代码与性能审查_Codex完成报告_v1.md`、根`粒子开发记录.md`末尾追加。创建前同主题输出不存在,使用v1,不覆盖旧报告。
|
||||||
|
|
||||||
|
起始HEAD `05878d662308b9a92c20c433c3cd2943d74ae718`。检查路径/引用/`git diff --check`后仅scoped staged/commit/push origin particle/main。实际SHA/message、push/remote/status及最终收尾提交自引用说明见阶段记录第4节;推送未完成前不写Git PASS。冻结源码至最终文档提交的差异只含既有两份文档及本轮四份文档,不含源码/shader/API/依赖/build/manifest/tag/产物。
|
||||||
231
docs/20_调查/STEP-096_V13隔离盲审调查_v1.md
Normal file
231
docs/20_调查/STEP-096_V13隔离盲审调查_v1.md
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
# STEP-096 V13 隔离盲审调查 v1
|
||||||
|
|
||||||
|
本报告仅使用 `/Users/tei/dev/MapLibreParticleV13Blind096` 内的当前规范、任务书与源码。发现由本轮独立阅读、追踪调用链及反例检查形成;未读取外部仓库、其他轮次审查、历史报告、会话日志、网页或既有审查结论。未构建、未执行测试、未运行设备、未修改源码。
|
||||||
|
|
||||||
|
## 1. 输入、结论边界与证据等级
|
||||||
|
|
||||||
|
- 合同:[STEP-096 任务书](../40_任务/STEP-096_V13隔离盲审_Codex任务_v1.md)。已读取根 AGENTS、PARTICLE_BASELINE、项目规范目录、总则 README 指定的当前有效文件。
|
||||||
|
- 唯一目录:`/Users/tei/dev/MapLibreParticleV13Blind096`。目标导出 SHA:`ee0b89e711bf01335b3aa71d36def785cd97fec3`,来源为任务书声明;本轮没有 Git 对象可供独立核验,未运行 Git。branch、当前 HEAD、Git clean/remote 状态均为 NOT AVAILABLE。
|
||||||
|
- [release manifest](../../release/maplibre-particle/release-manifest.json#L4) 声明 particleRevision=13、sourceCommit=`ddf20dec9fe9cd05b77274827b6825829500dd5a`,RN/Android 为 `13.4.1-particle.13`,iOS 为 `6.28.0-particle.13`。manifest 的 sourceCommit 是产物来源声明,不能替代本次快照导出 SHA,也不能据此断言导出错误。
|
||||||
|
- 当前基线总则仍包含 `.3` 发布坐标;本轮以快照源码及 `.13` manifest 描述实现。此为文档口径差异,未据此推断线上产物错误。manifest 的发布成功字样不是本轮公网回读证据。
|
||||||
|
- 当前工作机器观察值为 `Mac-mini-tei.lan`;工作日期为 2026-09-25。实际 TUI 启动命令、screen 归属、模型配置及 reasoning 设置的独立启动记录均 NOT AVAILABLE;不访问目录外补证,不声称核实了 RDC 启动要求。
|
||||||
|
|
||||||
|
“确定缺陷”指限定触发条件下源码控制流或数值关系已闭合,不表示已复现用户事故。“源码确定成本”指可证明的调用、遍历、分配或等待,不表示 profiler 排序。“设计限制”指当前能力或上界不足。所有 GPU 画面、耗时、卡顿、OOM、崩溃、性能收益均 NEEDS_RUNTIME;所有构建和测试均 NOT RUN。未发现可由本轮证据成立的 P0。
|
||||||
|
|
||||||
|
## 2. 独立架构地图
|
||||||
|
|
||||||
|
| 路径 | 实际责任和所有权 | 审查定位 |
|
||||||
|
|---|---|---|
|
||||||
|
| RN RendererRuntime → 平台 bridge → Map/Renderer → RenderOrchestrator → segment plan | Host 提交策略配置;Style 层顺序仍是绘制顺序来源,策略在该顺序上划分区间 | [RN 入口](../../packages/maplibre-particle-rn/src/RendererRuntime.ts#L22)、[分段计划](../../src/mbgl/renderer/render_segment_plan.hpp)、[Renderer](../../src/mbgl/renderer/renderer_impl.cpp#L583) |
|
||||||
|
| Renderer → RetainedUnderlayCache → gfx RenderPass/OffscreenTexture → GL/Metal | Renderer 持有各 retained 段缓存;缓存拥有离屏纹理/合成资源;同帧 PaintParameters 穿过多个目标 | [缓存生命周期](../../src/mbgl/renderer/retained_underlay_cache.cpp#L201)、[渲染调度](../../src/mbgl/renderer/renderer_impl.cpp#L798) |
|
||||||
|
| RN FieldRuntime → Android/iOS bridge → WeatherRuntime/MLNParticleFieldRuntime → RasterArraySource | bridge 定位地图并派发;Source 持有 encoded store、绑定、forecast clock;发布不可变 Impl/投影供 RenderLayer 读取 | [RN API](../../packages/maplibre-particle-rn/src/FieldRuntime.ts)、[Source 提交](../../src/mbgl/style/sources/raster_array_source.cpp#L422) |
|
||||||
|
| encoded ingress → QBIN V2/V3 → profile validator → EncodedFieldStore/FieldSeries → binding/window | magic 分派格式,校验结构和 profile;每个 fieldKey 保存时间序列;时间窗选取后物化数组 | [V3 解码](../../src/mbgl/particle/ingress/qbin_v3_decoder.cpp)、[profile](../../src/mbgl/particle/field/field_profile_validator.cpp)、[series](../../src/mbgl/particle/field/field_runtime.cpp#L144) |
|
||||||
|
| projection → WeatherColorLayer / RasterParticleLayer → GL/Metal shader | 色场与粒子分层;颜色消费标量或向量幅值;粒子持有场纹理、State A/B、Trail A/B 和 drawable/tweaker | [物化](../../src/mbgl/particle/field/field_projection_materialization.cpp#L201)、[粒子更新](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L1489)、[颜色更新](../../src/mbgl/renderer/layers/render_weather_color_layer.cpp#L205) |
|
||||||
|
|
||||||
|
正常路径存在实际保护:Style diff 处理增删/重排;无效分段与不支持场景可 fallback;稳定 retained 帧可复用;解码有结构、尺寸、CRC 等检查;同时间身份冲突和陈旧修订会拒绝;超出时间覆盖范围不会凭空外推。Source 使用共享不可变帧/投影,使读者可持有旧快照;粒子 tweaker 使用 weak_ptr,不能仅因资源与 drawable 互相引用的表象断言泄漏。
|
||||||
|
|
||||||
|
固定边界的实现情况:正式 Host 提交路径未要求 Native 承担 provider/manifest 下载;旧 fixture/local FileSource 路径应单独理解。RN 是控制入口,绘制仍在 Native Core。粒子模块有独立 field/ingress/render 层,但 Renderer/RenderOrchestrator 中也存在较多集成逻辑;没有上游对照输入,无法量化“薄接入”变更比例。Wave/Tide/Current 的共享地理掩码要求见 B09。GL 与 Metal 不能由共享 C++ 自动推出行为一致;Vulkan 完整粒子链未在本轮闭合。
|
||||||
|
|
||||||
|
## 3. 独立发现总表
|
||||||
|
|
||||||
|
编号按本轮源码证实顺序,排除的候选不占编号;优先级是建议处理顺序,不是实测事故等级。
|
||||||
|
|
||||||
|
| 编号 | 优先级 | 类型 | 置信度 | 发现及作用边界 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| B01 | P1 | 确定缺陷 | 高 | 切换离屏/主目标未重置 tile stencil 的 CPU 覆盖记录 |
|
||||||
|
| B02 | P1 | 确定缺陷 | 高 | Metal RenderPass 没有落实 gfx 的 clearDepth/clearStencil 请求 |
|
||||||
|
| B03 | P2 | 源码确定成本 | 高 | Metal 每次离屏 present 显式等待 GPU 完成 |
|
||||||
|
| B04 | P2 | 源码确定成本 | 高 | retained 内容失效同时释放同尺寸 GPU 资源 |
|
||||||
|
| B05 | P2 | 源码确定成本 | 高 | 时间更新先全量物化场,再判断投影是否相同 |
|
||||||
|
| B06 | P1 | 确定缺陷 | 高 | encoded PLAY 无生产路径推进 forecast clock |
|
||||||
|
| B07 | P2 | 确定缺陷 | 高 | 8-bit 对称场编码不能表示零,产生有符号偏差 |
|
||||||
|
| B08 | P2 | 确定缺陷 | 高 | Mercator 东向 U 位移缺少纬度修正因子 |
|
||||||
|
| B09 | P2 | 设计限制 | 高 | 当前掩码为合成 fixture,未形成共享地理 Ocean Mask |
|
||||||
|
| B10 | P2 | 源码确定成本 | 高 | 平台 UI 派发链同步解码并物化 encoded 数据 |
|
||||||
|
| B11 | P2 | 设计限制 | 高 | encoded store 仅限制每序列帧数,无全局字节/key 上界 |
|
||||||
|
| B12 | P2 | 确定缺陷 | 高 | retained 段绘制抛异常后仍可能标记缓存构建成功 |
|
||||||
|
| B13 | P1 | 确定缺陷 | 高 | 粒子场更新门槛漏掉合法时间窗内的 V/有效性变化 |
|
||||||
|
|
||||||
|
### B01 — stencil 覆盖记录没有随渲染目标切换
|
||||||
|
|
||||||
|
**触发与调用链。** 多个 retained 段,或 retained 构建后主目标绘制,复用相同瓦片集合且需要 stencil 裁剪。[Renderer buildRetainedRange](../../src/mbgl/renderer/renderer_impl.cpp#L798) 共用 PaintParameters;[beginBuild](../../src/mbgl/renderer/retained_underlay_cache.cpp#L304) 创建清 stencil=0 的离屏 pass,GL 只更新 stencil 可用性;随后[主 pass](../../src/mbgl/renderer/renderer_impl.cpp#L839) 也清 stencil=0。[updateStencilBufferAvailability](../../src/mbgl/renderer/paint_parameters.cpp#L100) 不清覆盖表;[tileIDsCovered](../../src/mbgl/renderer/paint_parameters.cpp#L147) 和[早退](../../src/mbgl/renderer/paint_parameters.cpp#L185) 只看 tile ID,不看 attachment。下一目标仍会用[旧的 Equal stencil ID](../../src/mbgl/renderer/paint_parameters.cpp#L365)。[GL LayerGroup](../../src/mbgl/gl/layer_group_gl.cpp#L67) 正常调用掩码生成,也无法绕过这个早退。
|
||||||
|
|
||||||
|
**所有权、正常/错误/销毁。** 覆盖表属于同帧 PaintParameters,stencil 内容属于每个目标附件。下一帧重建 PaintParameters 不能修复同帧跨目标;[显式 clearStencil](../../src/mbgl/renderer/paint_parameters.cpp#L156) 会重置记录,但上述目标切换没有调用。资源创建失败有 fallback,与本问题不同;正常创建成功即可触发。在 GL 已清为 0 的新目标上,旧正 ID 与实际内容不一致的路径已证实。Metal 的清屏另受 B02 影响。
|
||||||
|
|
||||||
|
**反例与边界。** 同一 attachment 内复用覆盖记录是正确优化;瓦片集合改变可能重绘掩码;无裁剪图层、没有跨目标构建或全部退回主绘制的帧不满足条件。不能据此称所有 retained 帧都缺图,实际画面 NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
**安全修复/最小验证。** 在目标/attachment 世代切换时失效掩码元数据及 ID 分配状态,保留同目标复用;不可在已绘制内容中途随意清 stencil。构造 R/C/R 和 R/R 共用瓦片的 fill/line 层,逐目标检查掩码写入及截图,与关闭 retained 对照;含稳定复用、相机变化、主目标后续 live 层。
|
||||||
|
|
||||||
|
### B02 — Metal 没有传递 depth/stencil 清屏语义
|
||||||
|
|
||||||
|
**触发与证据。** retained beginBuild 明确请求 depth=1、stencil=0;[Metal offscreen bind](../../src/mbgl/mtl/offscreen_texture.cpp#L62) 新建 descriptor 并挂接 color/depth/stencil 纹理。[Metal RenderPass 构造](../../src/mbgl/mtl/render_pass.cpp#L13) 只处理 clearColor,没有读取 clearDepth 或 clearStencil;[GL 对应构造](../../src/mbgl/gl/render_pass.cpp#L14) 把这些请求交给 clear。调用链为 Renderer → cache.beginBuild → gfx descriptor → Metal RenderPass → MTL descriptor,丢失发生在最后的转换。
|
||||||
|
|
||||||
|
**所有权与反例。** 附件属于 offscreen texture,pass/command encoder 管理本次命令。创建和销毁附件均不等于每次按请求清屏。主平台 descriptor 可自行配置清屏,不能证明新建离屏 descriptor 已正确配置;无 depth/stencil 的 pass 不受这两个字段影响。未使用外部 SDK 默认值推断确定黑屏,也未把未显式设置 StoreAction 直接定性为丢帧。模拟器 stencil attachment 分支另需设备验证。
|
||||||
|
|
||||||
|
**影响/修复/验证。** 确定的是 gfx 请求未落实,裁剪/深度表现 NEEDS_RUNTIME。统一转换各附件的 clear/load/store 语义,分别覆盖有无 clear 值、有无附件及模拟器组合格式。用非默认 depth/stencil clear 值做离屏后续比较测试,并核对 GPU capture;不得以 color 正常替代 depth/stencil 验证。
|
||||||
|
|
||||||
|
### B03 — Metal 离屏 present 中存在同步等待
|
||||||
|
|
||||||
|
**触发与链。** [RenderTarget::render](../../src/mbgl/renderer/render_target.cpp#L71) 在每个 target 绘制结束后 present;[Metal command encoder](../../src/mbgl/mtl/command_encoder.cpp#L29) 调 resource.swap;[OffscreenTexture::swap](../../src/mbgl/mtl/offscreen_texture.cpp#L86) commit 后执行 waitUntilCompleted,再重置命令缓冲。粒子稳态在[目标注册](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L2097) 中至少包含 StateWrite 和 TrailWrite,初始化还有额外读目标;retained 构建也有 present。
|
||||||
|
|
||||||
|
**所有权、边界和反例。** 等待保护命令/资源完成,是确定同步点,不是已测出的长时间阻塞。仅实际执行这些目标的帧计入;停绘、未激活层不计,retained 稳定复用不计新的 cache build 等待,GL 不经过此 Metal 函数。完成/销毁安全不能通过简单删 wait 保证。
|
||||||
|
|
||||||
|
**修复/最小验证。** 研究同队列命令依赖与完成回调持有资源,合并或延后提交,保留读写顺序及销毁保护。Metal capture 记录单粒子层每帧 command buffer、wait 次数和 CPU 等待时长,再测多粒子层、首帧、retained 重建及移除层;性能收益 NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### B04 — 内容失效与资源释放绑定
|
||||||
|
|
||||||
|
**触发与链。** 相机矩阵/内容 revision 改变而 viewport 尺寸不变。[Renderer revision 更新](../../src/mbgl/renderer/renderer_impl.cpp#L583) → [cache evaluate](../../src/mbgl/renderer/retained_underlay_cache.cpp#L201) → releaseResources → [prepareResources](../../src/mbgl/renderer/retained_underlay_cache.cpp#L244) 重新创建离屏资源。[releaseResources](../../src/mbgl/renderer/retained_underlay_cache.cpp#L365) 清掉纹理、合成组及资源键。
|
||||||
|
|
||||||
|
**正常/错误/销毁与反例。** 缓存对象持有资源,失效已实际释放所有权;稳定 valid 分支会 Reuse,不能称每帧都分配。viewport、格式、context 改变或最终销毁确需释放;资源创建异常已有 fallback。本 finding 针对同尺寸、同 context 的内容失效成本,不主张旧图继续有效。
|
||||||
|
|
||||||
|
**修复/验证。** 分开“内容有效性”和“附件可复用性”,保留匹配资源键的附件并重绘。固定 viewport 平移/缩放,对比 offscreen 创建次数、峰值显存、帧时间;再覆盖 resize/context 重建,防止错误跨 context 复用。成本路径已证实,分配耗时与收益 NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### B05 — 时间更新重复物化整个场
|
||||||
|
|
||||||
|
**触发与链。** Host 高频 setEncodedForecastTime,包括相同非零时间。[setRequestedForecastTime](../../src/mbgl/style/sources/raster_array_source.cpp#L453) → refreshV4RendererState;[clock setter](../../src/mbgl/particle/field/field_runtime.cpp#L196) 不因同值返回 false;[refresh](../../src/mbgl/style/sources/raster_array_source.cpp#L496) 先物化两类投影,再用 sameProjection 比较。[validity](../../src/mbgl/particle/field/field_projection_materialization.cpp#L69) 展开 bitset,[vectorSnapshot](../../src/mbgl/particle/field/field_projection_materialization.cpp#L105) 遍历向量;颜色 UV 与粒子各有自己的 T0/T1 物化。[sameProjection](../../src/mbgl/style/sources/raster_array_source.cpp#L65) 还会比较实际数组。
|
||||||
|
|
||||||
|
**所有权和反例。** 新数组在临时共享快照中,比较相同后可释放;发布新 Impl 时旧读者继续持有旧快照。无投影、无绑定、无法解出窗口会少做或跳过相关分支。相同内容可避免发布或 GPU 重传,故不等于每次都上传纹理;每个动画帧也不一定调用该时间 setter。完整性校验是必要成本,问题是同内容重复重建。
|
||||||
|
|
||||||
|
**成本/修复/验证。** N 个格点、两个时间面、颜色 UV 与粒子同时激活时,四份 vector+valid 基础数组约为 36N 字节的新逻辑存储,另有比较/校验遍历和旧快照重叠。缓存以完整不可变内容身份(含两分量、validity、grid、解释规则)为键,将 mix/time 与数组物化分离。统计同值时间、仅 mix 变化、跨窗、修订替换时的分配与上传次数;不得牺牲 B13 所需的完整身份。
|
||||||
|
|
||||||
|
### B06 — encoded PLAY 没有接入 forecast clock 推进
|
||||||
|
|
||||||
|
**触发与链。** 提交两帧、绑定、设置时间后,经公开 API 切 PLAY 并等待 repaint。[Source 播放设置及 advanceForecastClock](../../src/mbgl/style/sources/raster_array_source.cpp#L459) 中,播放设置只改 clock 状态,推进由独立方法完成。对 src/include/platform/RN 生产路径检索 advanceForecastClock 仅有[声明](../../include/mbgl/style/sources/raster_array_source.hpp#L240)和定义,没有调用方。Android [repaint timer](../../platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/WeatherRuntime.java#L225) 与 iOS [timer](../../platform/ios/src/MLNParticleFieldRuntime.mm#L147) 只触发重绘;粒子[读取 v4 mix/cadence](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L1262) 不推进该 clock。
|
||||||
|
|
||||||
|
**正常/错误/销毁与反例。** clock 由 Source 所有;平台 timer 的生命周期清理不提供时间增量。[advance 实现](../../src/mbgl/particle/field/field_runtime.cpp#L209) 本身存在且处理溢出;[测试](../../test/source/raster_array_v4_host_api.test.cpp#L297) 直接手动调用,所以不能证明公开 PLAY 可自行推进。Host 持续设置 forecast time、C++ 调用方自行 advance、旧 sourceTimeline 路径均是反例;粒子在固定场中移动也不代表预报时刻在播放。
|
||||||
|
|
||||||
|
**修复/验证。** 为 encoded clock 指定唯一推进所有者和单调时钟来源,处理暂停/恢复、rate、地图移除、颜色单层及多层,避免每层重复推进。仅调用一次 setTime 和 PLAY,观察 requestedForecastTime/mix 随 elapsed 改变;暂停保持、恢复不跳跃、不同 rate 成比例,窗口末端行为明确。公开路径缺口可静态确定,运行表现 NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### B07 — 零值在 8-bit 场纹理中变成负值
|
||||||
|
|
||||||
|
**触发与证据。** 有效向量为零或接近抵消。[粒子编码](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L174) 用 uint8 转换 `(x / scale * 0.5 + 0.5) * 255`,scale 至少为 1;[颜色编码](../../src/mbgl/renderer/layers/render_weather_color_layer.cpp#L45) 同类处理;[GL 解码](../../shaders/particle_gpu_validation.fragment.glsl#L76) 还原为 `(channel * 2 - 1) * scale`,Metal [解码](../../include/mbgl/shaders/mtl/particle_gpu_validation.hpp#L48) 同理。x=0 被截为 127,解码为 `-scale/255`,两个零分量都带偏差,最小 scale=1 时向量长度也高于 1e-5 的近零阈值。
|
||||||
|
|
||||||
|
**边界和反例。** 场快照的 CPU float 正确不代表上传表示正确;无效点被 valid 分支拒绝,不计入这个有效零场问题。不同数值/缩放下误差相对大小不同;方向归一化可放大近零偏差。理论截断误差小于 `2*scale/255`,这是假定合法编码区间的算术界,不是 GPU 实测。重用或销毁纹理不会改变表示误差。
|
||||||
|
|
||||||
|
**修复/验证。** 选取可精确表达零的带符号编码或 float/half 表示,并评估纹理兼容性;仅改四舍五入仍无法让奇数 255 的对称映射精确表示零。以有效零场、微小速度、正负对消、纯东/北场做 GPU 解码读回和粒子位移检查,GL/Metal 同测。实际漂移像素/速度 NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### B08 — Mercator 东向位移少一个 sec(latitude)
|
||||||
|
|
||||||
|
**触发与链。** formal profile 使用 eastward/northward、m/s 的 U/V 语义,见[profile 校验](../../src/mbgl/particle/field/field_profile_validator.cpp#L25)。上传缩放在 shader 中还原后,[GL formal 位移](../../shaders/particle_gpu_validation.fragment.glsl#L169) 取 `dx=U*dt/C`、`dy=-V*dt/(C*cos(latitude))`;[Metal](../../include/mbgl/shaders/mtl/particle_gpu_validation.hpp#L133) 同样处理。这里 C 为地球周长,输入链未见对 U 预乘 sec(latitude)。
|
||||||
|
|
||||||
|
**推导、反例和边界。** Mercator x=λ/(2π)+0.5,而局部东向速度满足 dλ/dt=U/(R*cosφ),故 dx 应为 U*dt/(C*cosφ)。赤道、U=0 时不受此项影响;60° 纬度当前东向投影位移为应有值的一半。统一 visual time scale 不修复东西/南北相对畸变。此为固定输入语义下的数学缺陷,实际视觉速度、极区处理 NEEDS_RUNTIME;与资源销毁和错误 fallback 无关。
|
||||||
|
|
||||||
|
**修复/验证。** 在一致纬度与极区夹紧策略下对两个分量使用正确度量转换,保留既定视觉时间倍率。0°/45°/60° 的等物理速度东西向和南北向场,比较 CPU 解析预测与单步 GPU 位移;含符号、反向、极区边界与跨日期变更线。
|
||||||
|
|
||||||
|
### B09 — Ocean Mask 尚未形成正式共享地理输入
|
||||||
|
|
||||||
|
**触发与证据。** Wave/Tide/Current 需要 OCEAN_ONLY,或启用当前 mask 模式。[makeCoastalMaskFixture](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L281) 用格点坐标表达式生成合成海岸,formal 资源初始化[仍安装该 fixture](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L1314)。物化[统一写 GLOBAL](../../src/mbgl/particle/field/field_projection_materialization.cpp#L77)。[顶点 shader](../../shaders/particle_gpu_validation.vertex.glsl#L56) 的 v_world 源自 quad 坐标,粒子合成[据此采样 mask](../../shaders/particle_gpu_validation.fragment.glsl#L262);[颜色分支](../../shaders/particle_gpu_validation.fragment.glsl#L113) 在该处理前返回。
|
||||||
|
|
||||||
|
**所有权、正常路径与反例。** mask 纹理由粒子资源拥有,随资源重建/销毁;缺少正式地理 mask 数据入口和两类层共享采样语义。默认关闭 mask 的 GLOBAL wind 不因此错误,Host 数据 nodata 也可能已有陆地缺测,但这不等价于独立共享 Land/Ocean Mask。不能把本限制扩展为所有风场不可用。
|
||||||
|
|
||||||
|
**修复/验证。** 由 Host 决定业务域策略,Core 接收明确地理坐标、网格和版本的共享掩码,两层统一采样;不把 provider/下载引入 Native。加载真实小型固定海岸 mask,平移、缩放、旋转后地理边界应固定;分别验证 Wind GLOBAL 与 Wave/Current OCEAN_ONLY,以及 nodata 叠加、删除与替换。当前设计缺口已证实,设备画面 NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### B10 — encoded 提交在 UI 派发链同步完成重工作
|
||||||
|
|
||||||
|
**触发与链。** RN 提交较大 QBIN。[Android bridge](../../packages/maplibre-particle-rn/android/src/main/java/org/navsea/maplibre/particle/weather/WeatherRuntimeBridgeModule.kt#L310) 经 runOnUiQueueThread 定位并调用 runtime;[Java submit](../../platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/WeatherRuntime.java#L97) → [JNI](../../platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp#L1501) 拷贝 bytes 并同步调用 Source。iOS [withMapForTag](../../packages/maplibre-particle-rn/ios/WeatherRuntimeBridge.mm#L105) 使用 UI block → [runtime submit](../../platform/ios/src/MLNParticleFieldRuntime.mm#L222) 拷贝并解码。V3 [CRC](../../src/mbgl/particle/ingress/qbin_v3_decoder.cpp#L66) 对字节做位循环,后续解码/校验/投影也在同步调用链内。
|
||||||
|
|
||||||
|
**边界、错误与反例。** Android 本地文件读取在 UI 派发之前,不能笼统写成全部文件 IO 都在 UI;小文件和 Duplicate 后的工作量不同,格式失败会提前返回。解码有大小上限,但允许的输入仍可较大。临时 bytes、decoded frame、Source 持久状态的所有权清楚;问题是所在线程及连续占用,并非已证明泄漏。未观测 ANR 或卡顿。
|
||||||
|
|
||||||
|
**修复/验证。** worker 只做不可变 bytes 解码/校验,带 map/style/source 世代令牌返回所有者线程提交;限制队列并处理取消、地图销毁和过时结果,不能把整个可变 Source 移到 worker。在中小/近上限输入、CRC 失败、连发及页面退出下记录 UI 区间耗时、峰值内存和错误回调。性能影响 NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### B11 — 每序列八帧不是全局内存预算
|
||||||
|
|
||||||
|
**触发与链。** 单 Source 连续提交不同有效 fieldKey。[EncodedFieldStore 成员](../../include/mbgl/particle/field/field_runtime.hpp#L151) 是 map 和 perSeriesCapacity=8;[submit](../../src/mbgl/particle/field/field_runtime.cpp#L223) 可为新 key 创建 series;[FieldSeries](../../src/mbgl/particle/field/field_runtime.cpp#L168) 只检查本序列容量。Source [提交](../../src/mbgl/style/sources/raster_array_source.cpp#L422) 不要求该 key 已绑定。V3 [输入上限](../../src/mbgl/particle/ingress/qbin_v3_decoder.cpp#L19) 限单帧 encoded/decoded bytes,不限 store 总量。
|
||||||
|
|
||||||
|
**所有权和反例。** store 持有共享帧,直到显式 clear/clearAll 或 Source 销毁;渲染读者可能延长旧快照寿命。单帧 decoded 上限 32 MiB 与八帧组合形成每序列最高 256 MiB 的逻辑上界,但不表示每个 profile 都能达到该值。key 数无总上界,不能据此保证 Source 总内存。Host 固定少量 key 并主动清理时可控;本轮没有 OOM 证据。
|
||||||
|
|
||||||
|
**修复/验证。** 在提交前计入全局 key/字节预算,包含共享去重与临时峰值;明确容量拒绝语义,保护当前绑定窗口,避免静默淘汰。循环提交多个合法 key,验证达到预算后稳定拒绝且现有窗口可用;clear 后可恢复,地图销毁后所有者释放,读者完成后最终释放。设计上界不足已证实,实际内存峰值 NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
### B12 — 构建过程错误没有阻止 retained 缓存成功提交
|
||||||
|
|
||||||
|
**触发与链。** retained 范围内某 LayerGroup 在 render 时抛出被捕获的异常,observer 记录错误后正常返回。[RenderOrchestrator 遍历](../../src/mbgl/renderer/render_orchestrator.hpp#L156) 捕获异常并 onRenderError,不把失败状态返回调用方。[buildRetainedRange](../../src/mbgl/renderer/renderer_impl.cpp#L798) 在 opaque/translucent 遍历后继续 finishBuild、markBuildSucceeded;[成功标记](../../src/mbgl/renderer/retained_underlay_cache.cpp#L328) 使其可在后续稳定帧 Reuse。
|
||||||
|
|
||||||
|
**所有权、错误/销毁与反例。** 缓存持有可能只完成部分绘制的纹理,直到下次失效或销毁。资源准备阶段[有 catch/fallback](../../src/mbgl/renderer/retained_underlay_cache.cpp#L240),不属于本路径;无异常、observer 再抛出异常、随后 revision 立刻改变都不满足持续复用条件。此处只讨论同步可捕获 render 异常,不将异步 GPU 错误视作同一证据。
|
||||||
|
|
||||||
|
**修复/验证。** 让范围绘制返回成败或设置本次构建错误标记,失败不提交 valid 缓存,安全 fallback/下帧重试;保留 observer 通知,不引入每帧 GPU 等待作为错误检测。用一次性抛异常的假 LayerGroup 和正常返回的 observer,检查 BuildFailed、禁止 Reuse、下一帧成功恢复,再由 GL/Metal 场景验证画面。
|
||||||
|
|
||||||
|
### B13 — 粒子更新身份没有覆盖第二分量和有效性
|
||||||
|
|
||||||
|
**触发与可构造输入。** 同 fieldKey 的三个不同 validTime 帧使用相同 fieldID/generation/dataVersion,U 内容相同、V 依次变化(或 validity 变化),网格和 schema 兼容。此输入在[FieldSeries 新时间插入](../../src/mbgl/particle/field/field_runtime.cpp#L146) 合法:newerRevision 检查针对同一 timestamp,未要求不同时间递增 generation。窗口从 T0/T1 切到 T1/T2。
|
||||||
|
|
||||||
|
**完整调用链。** [materializeParticleProjection](../../src/mbgl/particle/field/field_projection_materialization.cpp#L201) 仅以 t0/t1FirstPlane 身份传给 baseSnapshot;[baseSnapshot](../../src/mbgl/particle/field/field_projection_materialization.cpp#L77) 用 fieldID+该身份组成 fieldID,保留 producer generation/dataVersion。[Source sameProjection](../../src/mbgl/style/sources/raster_array_source.cpp#L65) 比较实际数组,可正确发布新的 V/validity。但[粒子 sourceChanged](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L1489) 只调用[fieldIdentityChanged](../../include/mbgl/particle/field/field_data_adapter.hpp#L111),其只比 generation/fieldID/dataVersion。两个槽身份都未变时,不进入内部 sameFieldGpuContent/纹理更新分支,继续使用旧场纹理。
|
||||||
|
|
||||||
|
**所有权、反例和边界。** Source 新投影存在、Renderer 仍持有旧 resources.field/nextField 及纹理,直到其他身份变化、重建或销毁。每时次主动递增 generation、U 也变化、初次资源创建都能触发更新;纯 mix 变化且窗口内容确实不变应保留纹理。此 finding 不指向颜色层:颜色投影的实际内容比较是反例,已排除“颜色仅看 U 而漏掉 V”的候选。
|
||||||
|
|
||||||
|
**修复/最小验证。** 粒子更新门槛使用包含 U/V、validity、grid 与解释语义的完整不可变内容身份;窗口时间状态单独更新,保持相同内容可复用。采用上述三帧并让 V/validity 可见地不同,seek 跨窗,检查 CPU 选窗、GPU 上传/复用判定及读回;增加不同时间同 generation、仅 V 改变、仅有效性改变、完全相同内容四组。错误门槛可静态证明,旧场画面 NEEDS_RUNTIME。
|
||||||
|
|
||||||
|
## 4. 反例检查及未成案候选
|
||||||
|
|
||||||
|
- **颜色投影身份:排除。** Source sameProjection → sameSnapshot → sameFieldGpuContent 会比较两分量/validity 实际数组;不能仅因第一 plane 参与 fieldID 就断言颜色不更新。B13 另有粒子特定的前置门槛证据。
|
||||||
|
- **粒子资源强引用环:排除。** [ValidationDrawableTweaker](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L770) 持 weak_ptr;[layerRemoved](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L1140) 移除目标。本轮未形成泄漏闭环。
|
||||||
|
- **关闭缓存后辅助段状态残留:排除简单结论。** [synchronizeRetainedUnderlayState](../../src/mbgl/renderer/renderer_impl.cpp#L142) 覆盖段缓存同步;不能仅查主缓存 setter。
|
||||||
|
- **纯重排未被处理:排除。** [Style diff](../../src/mbgl/renderer/style_diff.cpp#L19) 与[orchestrator 更新](../../src/mbgl/renderer/render_orchestrator.cpp#L318) 处理移除/添加并重建有序层;分段不是独立 Z-order 数据源。
|
||||||
|
- **任意交错不受支持:排除笼统结论。** 单 continuous 段的 fast path 限制不等于普通 segmented path 不支持多段;[普通绘制](../../src/mbgl/renderer/renderer_impl.cpp#L938) 仍按计划遍历。透明混合、深度与遮挡整体等价性尚需运行验证。
|
||||||
|
- **异常输入必然越界:未成案。** QBIN 有 MaxCells、字节预算、偏移/结构/CRC 检查,FieldSeries 有冲突/陈旧/兼容性拒绝。未完成形式化证明或 fuzz,不能声称完全安全,也未凭大输入符号制造越界 finding。
|
||||||
|
|
||||||
|
## 5. 性能与资源模型(静态模型,非 benchmark)
|
||||||
|
|
||||||
|
设视口为 W×H,retained 段数 K,格点数 N,活跃粒子层数 L,每层有效粒子数 P,encoded 字节数 E,fieldKey 数 F。
|
||||||
|
|
||||||
|
| 路径 | 静态成本模型 | 稳定路径/限制 |
|
||||||
|
|---|---|---|
|
||||||
|
| retained | 每段至少一张 RGBA8 颜色纹理约 4WH 字节,另加实际 depth/stencil 附件、对齐及 drawable;总量随 K 线性增长 | 稳定内容复用;Build 时绘制该段并合成;B04 增加同尺寸分配抖动 |
|
||||||
|
| 粒子 | State A/B 随状态纹理面积增长,Trail A/B 随视口面积增长;积分和粒子绘制约随 P 增长,全屏衰减/合成随 WH 增长 | 初始化/重播种还有额外工作;不可只凭 particleCount 推断全部成本 |
|
||||||
|
| Metal 提交 | 实际执行的粒子层稳态至少两次离屏同步点/层,加当帧 retained Build 数量及其他 target | 这是显式 wait 次数下界模型,不是每次都发生长等待;需 capture 核对实际启用分支 |
|
||||||
|
| 时间 setter | 激活投影物化和数组相等比较为 O(N),颜色 UV+粒子约 36N 字节基础新数组 | 单纯 draw 并不必然调用 setter;不同 scalar/vector 绑定成本不同 |
|
||||||
|
| QBIN | CRC 和解码大体随 E/样本数增长;提交链还会临时复制并可能物化 | 上限约束单帧;失败可以提前结束,不可套用最坏值到所有输入 |
|
||||||
|
| encoded store | 每 key 最多八帧,但总量随 F 增长;另有 materialized snapshot、上传图像、在途旧读者 | 需要显式 clear;现有容量拒绝保护单序列,未构成全局预算 |
|
||||||
|
|
||||||
|
未取得 profiler、锁竞争、allocator、GPU capture 或设备温度数据。不提供热点排名、FPS、毫秒、节省比例或确定 OOM 阈值。查到同步等待和 UI 同步调用,不等于证明死锁;本轮未形成有闭环证据的锁死 finding。
|
||||||
|
|
||||||
|
## 6. 测试源码覆盖与缺口
|
||||||
|
|
||||||
|
阅读测试源码只表示理解其断言,以下全部 NOT RUN。
|
||||||
|
|
||||||
|
| 已阅读的代表性测试 | 能说明的设计意图 | 不能替代的验证 |
|
||||||
|
|---|---|---|
|
||||||
|
| [retained 状态测试](../../test/renderer/retained_underlay_cache.test.cpp#L116)、[失败测试](../../test/renderer/retained_underlay_cache.test.cpp#L479)、[segment 测试](../../test/renderer/retained_underlay_cache.test.cpp#L657) | Build/Reuse/Fallback、范围与交错计划 | 真实 attachment stencil、Metal 清屏、异常经 orchestrator 传递、透明/深度像素等价 |
|
||||||
|
| [clock 测试](../../test/particle/field_runtime.test.cpp#L272)、[Host API clock](../../test/source/raster_array_v4_host_api.test.cpp#L297) | 手动 advance 的时间算法 | 平台 PLAY 是否接入生产推进者 |
|
||||||
|
| [materialization 测试](../../test/source/raster_array_v4_host_api.test.cpp#L392)、[颜色幅值](../../test/source/raster_array_v4_host_api.test.cpp#L433) | 快照不变性、mix、重建计算 | GPU 8-bit 数值误差、B13 跨窗同 producer 身份、数组分配次数 |
|
||||||
|
| [binding 测试](../../test/particle/field_binding.test.cpp#L84)、[V3 解码测试](../../test/particle/qbin_v3_decoder.test.cpp#L346) | 内容身份/选面、格式失败和边界 | 全 renderer 资源切换、UI 延迟、全局 key 预算及竞态销毁 |
|
||||||
|
|
||||||
|
未覆盖或未闭合:Vulkan/WebGPU 完整粒子实现、所有 Style 图层组合、真实设备 Metal simulator/device 差异、GPU 厂商驱动行为、平台业务调用频率、RN 安装/codegen/ABI、所有 error string 和 promise 回调分支、跨线程竞争注入、完整 fuzz、产物字节与 manifest 的对应关系。当前审查是有边界的静态追踪,不是逐行穷尽全仓或安全证明。
|
||||||
|
|
||||||
|
## 7. 后续可执行验证矩阵(交给 RDC,全部 NOT RUN)
|
||||||
|
|
||||||
|
本轮禁止执行以下构建/测试。RDC 在独立获准的验证阶段准备测试 runner 与设备;新增回归用例/修复属于后续代码任务,不能把下表当作本轮已提供可运行补丁。
|
||||||
|
|
||||||
|
| 项目 | 准备与执行动作 | 最小通过条件 |
|
||||||
|
|---|---|---|
|
||||||
|
| B01 | GL、Metal 各加载同瓦片 fill/line,策略 R/C/R、R/R、C/R/C;首帧/重建/复用逐帧对照关闭缓存 | attachment 切换均生成对应掩码,像素裁剪等价 |
|
||||||
|
| B02 | 离屏附件预置非默认值,创建带 depth/stencil clear 的 pass,再以比较绘制检查;device/simulator 各一次 | clear 值与请求一致,无依赖未定义旧内容 |
|
||||||
|
| B03/B04 | 1/2 粒子层、1/3 retained 段;固定视口平移 300 帧,记录 capture/分配/CPU 等待 | 给出次数、时长分布;资源复用变化有证据且无画面回归 |
|
||||||
|
| B05 | 两时间面,固定时间重复 100 次、mix 小步变化 100 次,再跨窗和修订 | 输出物化/分配/上传次数;相同内容复用且变化可靠可见 |
|
||||||
|
| B06 | 仅颜色、仅粒子、两者共存;setTime 一次后 PLAY、PAUSE、不同 rate | forecast time/mix 正确推进,每地图只推进一次 |
|
||||||
|
| B07 | 有效零场、微值、正负对消、东西南北常量场,GPU 单步读回 | 零保持零,近零阈值一致,误差有明确预算 |
|
||||||
|
| B08 | 0°/45°/60°,相同 m/s、相同 dt,比较解析 Mercator 位移 | U/V 投影度量一致,符号正确,极区有限值 |
|
||||||
|
| B09 | 同一固定地理 mask 同时供颜色/粒子,平移缩放旋转,切 GLOBAL/OCEAN_ONLY | 两层海陆边界一致且固定在地理位置 |
|
||||||
|
| B10 | 合法中小/近上限 QBIN、坏 CRC、连发、加载中移除地图 | UI 耗时可量化;回调一次;无过时提交/释放后访问 |
|
||||||
|
| B11 | 多 key 连续提交直至预算,clear 后再提交 | 可预测拒绝、峰值有界、当前窗口不破坏、最终释放 |
|
||||||
|
| B12 | 假 LayerGroup 首次抛异常,observer 记录后返回,下一帧正常 | 首次不得缓存成功;下一帧重试并恢复 |
|
||||||
|
| B13 | 同 generation/dataVersion/U 的三时次,分别仅改 V、validity,再 seek 跨窗 | 新窗口 GPU 内容正确;完全相同内容仍可复用 |
|
||||||
|
| 兼容 | 增删/重排/隐藏边界层,透明层交错、3D/custom fallback、resize/context 重建 | Style 顺序一致,fallback 可恢复,无残留目标 |
|
||||||
|
|
||||||
|
已有 C++ 定向测试的执行入口需使用平台生成的实际 runner;[test/CMakeLists](../../test/CMakeLists.txt#L1) 的 mbgl-test 是库,[macOS 配置](../../platform/macos/macos.cmake#L84) 定义 mbgl-test-runner。RDC 已构建并确认路径后可执行下列命令(本轮未执行;路径必须由 RDC 填写):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
RDC_TEST_RUNNER=/absolute/path/to/mbgl-test-runner
|
||||||
|
"$RDC_TEST_RUNNER" --gtest_filter='Retained*:ContinuousLayerRange.*:RenderSegment*:ParticleFastPath.*:ParticleField*:ParticleQbin*:ParticleEncoded*:Source.RasterArrayV4*'
|
||||||
|
```
|
||||||
|
|
||||||
|
这只运行现有匹配测试,不覆盖全部上述新增场景。RDC 应先确认 runner 的过滤输出确实命中测试,保存命令、exit code、测试数量和日志路径;设备场景记录设备/后端/版本、输入 fixture、截图或 capture。实际构建目录、签名、设备 ID、安装产物及历史日志均 NOT AVAILABLE,本轮不臆造构建命令或成功状态。
|
||||||
|
|
||||||
|
## 8. 分批建议与停止边界
|
||||||
|
|
||||||
|
1. **先补正确性闭环:** B01/B02 的目标附件状态、B06 的唯一时钟推进者、B13 的完整内容身份;加入最小回归后再做跨后端运行验证。B12 同批修复失败提交语义,避免错误缓存长期复用。
|
||||||
|
2. **再统一数值与域语义:** B07/B08 的数值表示/投影度量,以及 B09 的共享地理掩码;用解析输入和小型固定 fixture 定量验收。
|
||||||
|
3. **最后按测量做性能与预算:** B03/B04/B05/B10/B11,先记录同步、分配、线程耗时和峰值,再分别改命令提交、资源复用、物化缓存、工作线程和容量策略;避免多项同时修改而无法归因。
|
||||||
|
|
||||||
|
本轮仅交付三份中文盲审文档,不修改实现或测试。完成后停止,由 RDC 冻结三份原稿 SHA-256 和时间,再独立移交、追加开发记录并做正式仓库 scoped 提交及远端核验。本会话不参与解除盲态比较,不根据外部材料回写发现。
|
||||||
|
|
||||||
|
配套:[Step 记录](../50_Step记录/STEP-096_V13隔离盲审_v1.md) · [完成报告](../报告/STEP-096_V13隔离盲审_Codex完成报告_v1.md)
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
# MapLibreParticleLab V3 通用 Scalar / Vector Field 运行时架构设计
|
||||||
|
|
||||||
|
状态:冻结(V3.0 Contract,尚未实现)
|
||||||
|
决策:新 canonical schema 为 `maplibre-particle-field/v2`;RN package 首个 V3 实现版至少 `13.4.1-particle.3`,Native AAR 独立升版,绝不覆盖 `.2`/`.1`。
|
||||||
|
|
||||||
|
## 边界与目标
|
||||||
|
|
||||||
|
V3 从 NavSea canonical Field submission 开始,是 provider-neutral、business-neutral 的二维时序 Scalar / Vector runtime。它不解析 NSFT/PMTiles,不做 HTTP、manifest、time_index、缓存、业务单位转换,也不以 WIND/WAVE/RAIN/TIDE 决定能力。V3.0 固定为 **0/1 scalar + 0/1 vector + 1 shared validity**,拒绝 neither;不设计 N-channel graph。
|
||||||
|
|
||||||
|
```text
|
||||||
|
WeatherV2 → NSFT → NavSea JS/TS(解码、拼接、单位/业务 canonical)
|
||||||
|
│ metadata + local scalar/vector file reference
|
||||||
|
▼
|
||||||
|
MapLibreParticleLab V3: validate → Snapshot → Source Timeline T0/T1/MIX
|
||||||
|
├─ Generic Field Renderer (config.valueSource)
|
||||||
|
└─ Particle Renderer (config.vectorSemantics)
|
||||||
|
```
|
||||||
|
|
||||||
|
当前实现为 `v1 vector(U/V)+valid → Timeline → 两个各自 RGBA U/V texture renderer`;V3 仍是一份 Source snapshot,两 renderer 独立显示、独立 GPU resource,而非两份 host field。
|
||||||
|
|
||||||
|
## 冻结数据 Contract
|
||||||
|
|
||||||
|
```text
|
||||||
|
FieldSnapshot
|
||||||
|
descriptor: fieldId, generation, validTime, grid, bounds, dataVersion, content hash
|
||||||
|
scalar?: Float32[width*height] // scalar plane
|
||||||
|
vector?: Float32[width*height*2], interleaved U/V // vector plane
|
||||||
|
valid: UInt8[width*height] // shared validity
|
||||||
|
vectorSemantics?: VELOCITY | DIRECTION // only when vector exists
|
||||||
|
```
|
||||||
|
|
||||||
|
- 所有 plane 必为 `float32-le`、NW-origin row-major;vector U=east、V=north。
|
||||||
|
- scalar-only、vector-only、scalar+vector 合法;neither、尺寸/byteLength/finite/validity 不一致均 reject。
|
||||||
|
- `valid` 是一个 snapshot-wide shared logical mask;V3.0 不加 channel mask。任何无效 cell 对全部现有 channel 无效。
|
||||||
|
- `validTime` 是实际时刻,不假设固定间隔;T0/T1 必须相同 grid/bounds/channel presence/vector semantics/encoding,且都递增。
|
||||||
|
- `fieldId` 是 opaque identity,删除 `fieldType` / `displayDomain` 对能力、adapter validation 和 compatibility 的强制依赖。领域/mask 选择若仍需要,作为 renderer config 的 business-neutral spatial policy,不能作为天气 enum。
|
||||||
|
|
||||||
|
### v2 JSON 与 binary plane
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema":"maplibre-particle-field/v2", "fieldId":"opaque-id", "generation":1,
|
||||||
|
"validTime":1760000000000, "dataVersion":1,
|
||||||
|
"grid":{"width":257,"height":257,"origin":"north-west","rowOrder":"north-to-south"},
|
||||||
|
"bounds":{"west":0,"south":0,"east":1,"north":1},
|
||||||
|
"validity":{"encoding":"uint8","byteLength":66049},
|
||||||
|
"scalar":{"encoding":"float32-le","byteLength":264196,"noData":-9999},
|
||||||
|
"vector":{"layout":"interleaved-uv","encoding":"float32-le","byteLength":528392,"noData":-9999,"semantics":"DIRECTION"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
提交 API 冻结为 `submitField(metadataJson, payloadRefs)`,其中 `payloadRefs={validityFileUri, scalarFileUri?, vectorFileUri?}`;每个 URI 独立 local file、独立限额、先验证存在性与总大小再同步读入。metadata 仍小,绝不采用 JS number[]、JSON array、Base64。v2 validity 采用显式 `uint8` plane,避免让任一 channel 的 noData 充当隐式共享 mask;Kotlin/JNI 最小修改为从单 `byte[]` 改为三个 nullable byte[](或等价 native struct),Core parser/adapter 接收 plane bundle。若实施时 JNI 参数数量/ABI 风险高,可在 Kotlin 读三文件后封装为有明确 offsets/lengths 的 **v2 binary container**;不能复用 v1 的单 payload 产生歧义。
|
||||||
|
|
||||||
|
## v1/v2 版本决策
|
||||||
|
|
||||||
|
选择 **v2**,而非 v1 additive:v1 单 `vector`、单 payload、`velocity` snapshot 和 WIND/GLOBAL validation 都是结构性约束;在同 schema 内重载 payload 会令 scalar-only 与 scalar+vector 的 bytes、validity、noData、compatibility ambiguous。
|
||||||
|
|
||||||
|
- v1 vector-only 继续由旧 parser/adapter 可读,作为 compatibility ingress;最薄兼容层将 v1 decode 规范化成 v2 内部 snapshot(vector=present、shared validity 从 v1 U/V noData 派生、semantics=VELOCITY)。
|
||||||
|
- Runtime 内只使用一个 V3 snapshot/timeline 实现;允许 v1 首帧与 v2 vector-only 共同进入同 Runtime,前提是 normalize 后 descriptor/semantics/channel presence 完全 compatible。否则 fail-closed,不维护两套 runtime。
|
||||||
|
- v1 不增加 scalar 字段;发布 `.3` 才公开 v2 `payloadRefs`,旧 `.2` ABI 可继续作为 legacy ingress,Native/AAR coordinate 独立推进。
|
||||||
|
|
||||||
|
## Renderer Contract(与数据分离)
|
||||||
|
|
||||||
|
`FieldSnapshot` 只回答“有什么”;renderer config 显式回答“怎样画”:
|
||||||
|
|
||||||
|
```text
|
||||||
|
FieldRendererConfig.valueSource = SCALAR | VECTOR_MAGNITUDE
|
||||||
|
ParticleRendererConfig.vectorSemantics = VELOCITY | DIRECTION
|
||||||
|
ParticleRendererConfig.advectionScale (finite, >0; display/config policy)
|
||||||
|
```
|
||||||
|
|
||||||
|
`SCALAR` 要求 scalar present;`VECTOR_MAGNITUDE` 要求 vector present;不满足即该 renderer unavailable/reject,绝不猜。`FieldColorStop.value` 保持 generic public semantics。Native 内部 `speedMps`、shader default 0..30m/s 和 speed-color 命名将在 V3-03 统一改为 generic `fieldValue`/`value`; legacy JNI ABI 可薄转换,不能改变 RN `.2`。
|
||||||
|
|
||||||
|
`SnapshotGpuResources` 为每一个 T0/T1 快照包含 `scalarTexture?`、`vectorTexture?`、`validityTexture` 与各 plane scale/format。rollover 把整个 bundle(而不是单 texture)从 OLD_T1 原子复用为 NEW_T0;只上传 NEW_T1 bundle。style reload/context loss 清空 renderer-owned resource,Source timeline 保留;下一帧从当前 immutable window 重建。Field ON/Particle OFF、Field OFF/Particle ON、双 ON 都继续合法;无 vector 时 Particle 不可用,不让 scalar 伪装 U/V。
|
||||||
|
|
||||||
|
## Vector semantics
|
||||||
|
|
||||||
|
`VELOCITY` 保持当前 Wind/Tide 行为:T0/T1 component-wise mix,m/s 地理推进,`advectionScale=1` 默认。
|
||||||
|
|
||||||
|
`DIRECTION` 用 unit U/V 表示方向,不代表速度。先 component-wise mix,再 normalize;若长度非 finite 或小于冻结阈值(建议 `1e-5`)则 cell invalid、粒子不移动/respawn,fail-closed。反向邻帧在 mix 约 0.5 归零亦遵此规则,不能任选方向。实际视觉推进量只由 particle renderer 的 `advectionScale`(及现有 visual clock)控制,必须明确由宿主/renderer 配置提供,不能从 Wave height/period 或业务名称推算。本任务未定义 Wave physics。
|
||||||
|
|
||||||
|
## WeatherV2→NavSea→V3 映射
|
||||||
|
|
||||||
|
|业务输入|NavSea canonical V3|Field renderer|Particle|
|
||||||
|
|---|---|---|---|
|
||||||
|
|Wind|vector U/V, `VELOCITY`|`VECTOR_MAGNITUDE`|`VELOCITY`|
|
||||||
|
|Tide/Current|vector U/V, `VELOCITY`|`VECTOR_MAGNITUDE`|`VELOCITY`|
|
||||||
|
|Rain|scalar rainRate + shared valid|`SCALAR`|unavailable|
|
||||||
|
|Wave|scalar height + unit direction-to U/V, `DIRECTION`|`SCALAR`|`DIRECTION`|
|
||||||
|
|
||||||
|
Wave period 留在 NavSea model/cache,不增加第二 scalar;Rain precipitation type 未写入 NSFT,V3 不推断或补造。NavSea 在拼接 257 tile 时保持 `(tiles*256)+1`,Core 不理解 tile。
|
||||||
|
|
||||||
|
## 受影响文件矩阵与风险
|
||||||
|
|
||||||
|
|区域|预计修改|
|
||||||
|
|---|---|
|
||||||
|
|Core|`field_data_adapter.*`、`local_particle_field_feed.cpp`、`raster_array_source.*`、fixtures/tests|
|
||||||
|
|shader/renderer|`render_weather_color_layer.cpp`、`render_particle_validation_layer.cpp`、`particle_gpu_validation.fragment.glsl`、UBO/manifest/generator 后端产物|
|
||||||
|
|Android|`WeatherRuntime.java`、`MapLibreMap.java`、`native_map_view.*`、JNI registration|
|
||||||
|
|RN|`types.ts`、`FieldRuntime.ts`、`NativeWeatherRuntimeBridge.ts`、Kotlin TurboModule/codegen、package version|
|
||||||
|
|Test Host|style fragment、v1 regression 与 Wind/Rain/Wave/synthetic fixtures;不引入业务逻辑到 package|
|
||||||
|
|
||||||
|
风险:plane 增加 GPU memory、3 URI read 后 Java/C++ copy 峰值、texture precision/scale、v1/v2 ABI 共存、direction opposite interpolation、style/context lifecycle 和 timeline compatibility。缓解是 bounded total bytes、明确 channel-presence compatibility、bundle-level reuse、fail-closed 和先 Linux/Core verification。
|
||||||
|
|
||||||
|
## 实施批次与门槛
|
||||||
|
|
||||||
|
|批次|修改范围 / 不修改范围|完成门槛 / 下一入口|
|
||||||
|
|---|---|---|
|
||||||
|
|V3-02 Core Contract + Scalar Adapter|v2 descriptor/snapshot/parser、v1 normalize;不改 GPU/JNI|单元/fixture覆盖三组合、invalid/compatibility;进入 V3-03|
|
||||||
|
|V3-03 GPU + Generic Field|scalar/vector/validity bundle、`FieldValueSource`;不改 particle semantics/JNI|OpenGL Core fixture:scalar 与 magnitude 不混读;进入 V3-04|
|
||||||
|
|V3-04 Vector Semantics|DIRECTION normalize、advectionScale;不改 Wave physics|VELOCITY Wind 回归、opposite/zero fail-closed;进入 V3-05|
|
||||||
|
|V3-05 Android/JNI/RN ingress|v2 plane refs、新 `.3`,保留 v1 thin ingress|payload limits/ownership/static checks;进入 V3-06|
|
||||||
|
|V3-06 fixtures|真实 Wind/Rain/Wave,Tide 回归、wave synthetic|T0/T1/MIX/reuse/style/visibility/gap coverage;进入 V3-07|
|
||||||
|
|V3-07 Linux freeze|Core/OpenGL 精确验证与文档|双 renderer/lifecycle/perf evidence;进入唯一 Android 周期|
|
||||||
|
|V3-08 Android 综合验收|一次 AAR/APK/device;不分散小周期|Wind/Rain/Wave/Tide、三 visibility 组合、seek/play/pause/rollover/style reload、hard-gate log 与 memory/perf evidence|
|
||||||
|
|
||||||
|
Wave synthetic fixture 固定:scalar west-low→east-high、vector 全场 north;色场必须只东西渐变,粒子必须只向北。所有 fixture还覆盖 T0/T1 scalar、T0/T1 vector、共同 MIX、OLD_T1→NEW_T0、only NEW_T1 upload、invalid/gap fail-closed。
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# MapLibreParticle QBIN V3 Decoder 与 Block Geometry 实施决策 v1
|
||||||
|
|
||||||
|
## 1. 结论
|
||||||
|
|
||||||
|
STEP-086 将 `QBV3` / version 3 作为新的压缩天气场容器格式接入 MapLibre Native。生产 Decoder 固定使用 **16×16 Block Geometry**,不在运行时尝试 16×16 / 32×32 双解码,也不通过启发式猜测块尺寸。
|
||||||
|
|
||||||
|
既有 `QBV2` 解码链继续保留。`EncodedFieldIngress` 依据 magic 显式分流:`QBV2` → `QbinV2Decoder`,`QBV3` → `QbinV3Decoder`;version 由对应 Decoder 严格校验。解码完成后继续统一进入 `FieldProfileValidator`,不改变既有 profile contract 分层。
|
||||||
|
|
||||||
|
## 2. Block Geometry 决策依据
|
||||||
|
|
||||||
|
使用仓库既有 Current / Rain / Wave / Wind 四类真实 Golden 数据,共 T0/T1 8 帧,按 v2.2 冻结稿规则实现确定性的 C++ benchmark encoder/decoder,对 16×16 与 32×32 做相同输入对比。
|
||||||
|
|
||||||
|
| 指标 | 16×16 | 32×32 | 结论 |
|
||||||
|
|---|---:|---:|---|
|
||||||
|
| 数值 payload 总字节 | 2,470,419 | 2,514,288 | 32×32 增加 43,869 字节,约 +1.7758% |
|
||||||
|
| 总 block 数 | 22,704 | 5,984 | 32×32 block 更少 |
|
||||||
|
| 非空 block 数 | 19,120 | 5,190 | 32×32 block 更少 |
|
||||||
|
| C++ encode 总时间 | 218.713 ms | 200.101 ms | 32×32 约快 8.5% |
|
||||||
|
| C++ decode 总时间 | 104.121 ms | 101.451 ms | 32×32 约快 2.6% |
|
||||||
|
|
||||||
|
8 帧合计 decode 差异约 2.67 ms,平均每帧约 0.33 ms;相比之下,32×32 对 payload 尺寸有稳定负收益,Rain 两帧的增幅尤其明显。QBIN V3 的核心目标之一是降低网络、缓存和跨 RN/Native 传输成本,因此选择 **16×16**。
|
||||||
|
|
||||||
|
此决定同时冻结:
|
||||||
|
|
||||||
|
- `QbinV3Decoder::BlockWidth = 16`;
|
||||||
|
- `QbinV3Decoder::BlockHeight = 16`;
|
||||||
|
- Golden Fixture manifest 中 blockWidth/blockHeight 均为 16;
|
||||||
|
- 后续生产 Writer 必须输出相同 ABI,不得自行改为 32×32。
|
||||||
|
|
||||||
|
## 3. Decoder 责任边界
|
||||||
|
|
||||||
|
`QbinV3Decoder` 只负责把已取得的 QBV3 二进制解码成现有 `DecodedFieldFrame`。它不负责 Weather provider、HTTP、下载、重试、缓存、manifest 获取、路线调度或 React 业务逻辑。
|
||||||
|
|
||||||
|
实现包含:
|
||||||
|
|
||||||
|
- 固定 Header / Descriptor 布局与 little-endian 读取;
|
||||||
|
- file size、plane count、cell count、decoded bytes 等硬上限;
|
||||||
|
- CRC32 校验;
|
||||||
|
- canonical descriptor 排序、唯一键、16-byte packing 与 padding 校验;
|
||||||
|
- VALIDITY 唯一性、长度、unused bits 校验;
|
||||||
|
- Numeric dtype 约束与 all-invalid 零长度编码;
|
||||||
|
- 16×16 block 的 Predictor + ZigZag + Rice 解码;
|
||||||
|
- Direction 的 canonical scale、环形预测与 half-circle tie 规则;
|
||||||
|
- 非法 Rice-K、截断、额外字节、非零 bit padding、残差越界等 fail-closed;
|
||||||
|
- 解码后构造既有 `DecodedFieldFrame`,继续使用既有 FieldProfileValidator。
|
||||||
|
|
||||||
|
## 4. Golden Fixtures
|
||||||
|
|
||||||
|
正式 QBIN V3 Golden Fixture 位于:
|
||||||
|
|
||||||
|
`integration/react-native/MapLibreParticleRNTestHost/assets/qbin-v3-golden/`
|
||||||
|
|
||||||
|
覆盖:
|
||||||
|
|
||||||
|
- current:T0 / T1,513×513;
|
||||||
|
- rain:T0 / T1,513×513;
|
||||||
|
- wave:T0 / T1,513×769;
|
||||||
|
- wind:T0 / T1,513×769。
|
||||||
|
|
||||||
|
`manifest.json` 固定每个文件的 bytes、SHA-256、width、height 以及 16×16 Block Geometry。校验脚本为:
|
||||||
|
|
||||||
|
`integration/react-native/MapLibreParticleRNTestHost/scripts/verify-qbin-v3-golden-fixtures.py`
|
||||||
|
|
||||||
|
8 个 V3 fixture 已与对应 V2 canonical raw 解码结果做逐 plane / validity byte-for-byte 对比,结果一致。
|
||||||
|
|
||||||
|
## 5. 兼容性与版本
|
||||||
|
|
||||||
|
本次不移除 QBV2,不改变 RN `submitEncodedField` 公共调用形态,不把 V3 压缩细节暴露给 JS/TS。Native / RN 发行版本统一升级为 `13.4.1-particle.9`。
|
||||||
27
docs/30_设计/MapLibre粒子Android发布与ReactNative包设计_v1.md
Normal file
27
docs/30_设计/MapLibre粒子Android发布与ReactNative包设计_v1.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# MapLibre 粒子 Android 发布与 React Native 包设计 v1
|
||||||
|
|
||||||
|
## 版本与坐标
|
||||||
|
|
||||||
|
真实基础为 MapLibre Native Android `13.4.1`,共同 core commit 为 `18545ca10d296704b2cba675b182ff2c8da24588`,本发行只准备 Android OpenGL。正式规则为 `<上游版本>-particle.<修订号>`;首发为 `13.4.1-particle.1`。正式 npm 包为 `@navsea/maplibre-particle-rn@13.4.1-particle.1`,正式 Maven 坐标为 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1`。
|
||||||
|
|
||||||
|
`step061` 是已经完成验证的开发阶段制品编号,不是正式发行版本。release manifest 是 `release/maplibre-particle/release-manifest.json`。Android Maven 正式仓库已确定为 `https://pbf.jpcn.ccwu.cc/maven`;npm registry 仍暂不决定。
|
||||||
|
|
||||||
|
## Consumer 依赖关系
|
||||||
|
|
||||||
|
```text
|
||||||
|
Consumer
|
||||||
|
↓ 直接安装
|
||||||
|
@navsea/maplibre-particle-rn
|
||||||
|
↓ 唯一 MapLibre RN native module
|
||||||
|
@maplibre/maplibre-react-native 11.3.6
|
||||||
|
↓ Gradle dependency substitution
|
||||||
|
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
正式包 re-export 已有 MLRN `Map`、`Camera` 等公共 API,并包含 WeatherRuntime TurboModule。它不是 MLRN 的完整 fork;`@maplibre/maplibre-react-native@11.3.6` 是正式包的 peer dependency;现代 npm 在安装正式包时自动解析到 App 根依赖层。Consumer 不应再直接声明或安装第二份 MLRN。package plugin 把 MLRN 唯一的 `org.maplibre.gl:android-sdk-opengl` 请求替换为正式 NavSea AAR,避免官方 AAR 与 Particle AAR 同时进入 APK。
|
||||||
|
|
||||||
|
WeatherColorLayer 与 RasterParticleLayer 是 Core-only style layer,不是 React 组件;包公开 `createParticleWeatherStyle()` 和稳定 IDs 以声明样式。field 仍只经既有 `metadataJson + local file:// payload → WeatherRuntime → RasterArraySource::submitHostField()` 进入 Native/Core;不创建第二套 ingress、Timeline 或 Runtime Window。
|
||||||
|
|
||||||
|
## 发布与升级边界
|
||||||
|
|
||||||
|
Consumer 不编译 MapLibre Native 源码,也不使用 workspace、relative source path、CMake path 或软链接。Android Native 统一从 `https://pbf.jpcn.ccwu.cc/maven` 解析;npm registry 未决定时,`.tgz` 只用于正式 npm 包的预发布 Consumer 验证。未来升级到新的上游版本时,按 `13.5.0-particle.1` 等新发行线重新完成 Native、tarball Consumer 和 APK 验证;通过后才单独创建 release tag。
|
||||||
383
docs/30_设计/MapLibre粒子ReactNative使用说明_v1.md
Normal file
383
docs/30_设计/MapLibre粒子ReactNative使用说明_v1.md
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
# MapLibre 粒子 React Native 使用说明 v1
|
||||||
|
|
||||||
|
本文面向 NavSea / React Native 调用侧,说明如何通过正式包 `@navsea/maplibre-particle-rn` 使用 MapLibre Particle Android 能力。本文描述的是当前已验证的 `13.4.1-particle.1` 契约,不是 Weather Provider、下载服务或天气业务层设计。
|
||||||
|
|
||||||
|
## 1. 使用模型
|
||||||
|
|
||||||
|
React Native App 只直接依赖正式包:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@navsea/maplibre-particle-rn@13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Android Native 由该包的 Expo/Gradle 接线从正式 Maven 仓库解析:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
正式 Maven 仓库:`https://pbf.jpcn.ccwu.cc/maven`。
|
||||||
|
|
||||||
|
包会复用唯一的 `@maplibre/maplibre-react-native@11.3.6` peer dependency,并把其 OpenGL Native SDK 请求替换为 Particle AAR。App 不应再并行引入第二份 MapLibre Native AAR、Maven Local AAR、源码路径 bridge 或 MapLibre Native 源码树。
|
||||||
|
|
||||||
|
以后升级粒子版本的目标流程是:升级 `@navsea/maplibre-particle-rn` 版本 → npm 安装 → Gradle 取得对应正式 AAR → 重新编译 App。
|
||||||
|
|
||||||
|
## 2. Expo / React Native 配置
|
||||||
|
|
||||||
|
`app.json` / app config 中加入正式 plugin:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expo": {
|
||||||
|
"plugins": ["@navsea/maplibre-particle-rn/app.plugin"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
React 侧统一从正式包导入,不直接从官方 MLRN 或旧 bridge 导入:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import {
|
||||||
|
Camera,
|
||||||
|
Map,
|
||||||
|
WeatherRuntime,
|
||||||
|
createParticleWeatherStyle,
|
||||||
|
particleWeatherIds,
|
||||||
|
} from "@navsea/maplibre-particle-rn";
|
||||||
|
```
|
||||||
|
|
||||||
|
`WeatherColor` 与 `RasterParticle` 当前是 MapLibre Core style layer,不是 React 组件。测试或独立页面可直接使用 `createParticleWeatherStyle()`;NavSea 有自己的正式 style 时,应把同样的 `raster-array` source、`weather-color` layer 和 `raster-particle` layer 合入宿主 style,而不是在 MapView 外再画一层 Canvas/WebView。
|
||||||
|
|
||||||
|
## 3. Map 生命周期与 WeatherRuntime
|
||||||
|
|
||||||
|
必须在 Map style 已完成加载后 attach Native Runtime,并在页面销毁时 detach:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const [reactTag, setReactTag] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const onStyleReady = useCallback(async (event: unknown) => {
|
||||||
|
const tag = findNodeHandle((event as { target?: unknown }).target as never);
|
||||||
|
if (tag == null) return;
|
||||||
|
await WeatherRuntime.attach(tag);
|
||||||
|
setReactTag(tag);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => () => {
|
||||||
|
if (reactTag != null) void WeatherRuntime.detach(reactTag);
|
||||||
|
}, [reactTag]);
|
||||||
|
```
|
||||||
|
|
||||||
|
典型 Map:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Map mapStyle={createParticleWeatherStyle()} onDidFinishLoadingStyle={onStyleReady}>
|
||||||
|
<Camera initialViewState={{ center: [135, 32], zoom: 3.9 }} />
|
||||||
|
</Map>
|
||||||
|
```
|
||||||
|
|
||||||
|
`WeatherRuntime.getState(tag)` 返回 Native/Core 权威状态。至少两帧兼容的合法 Field 建立 initial window 后,Runtime 才进入 `READY`;React 不自行维护第二套 T0/T1/MIX Runtime Window。
|
||||||
|
|
||||||
|
## 4. 显示数据的总体结构
|
||||||
|
|
||||||
|
这是本接口最重要的部分。每一个时间片不是图片,也不是粒子坐标列表,而是一张**规则二维风矢量场**。一个 Field 由两部分组成:
|
||||||
|
|
||||||
|
```text
|
||||||
|
metadataJson
|
||||||
|
+
|
||||||
|
local file:// binary payload
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 metadata 描述时间、范围、网格和编码;binary payload 只保存每个网格点的 U/V 风矢量。
|
||||||
|
|
||||||
|
当前正式 schema:
|
||||||
|
|
||||||
|
```text
|
||||||
|
maplibre-particle-field/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
固定语义:
|
||||||
|
|
||||||
|
```text
|
||||||
|
二维规则 grid
|
||||||
|
origin = north-west
|
||||||
|
row order = north-to-south
|
||||||
|
column order = west-to-east
|
||||||
|
storage = row-major
|
||||||
|
layout = interleaved-uv
|
||||||
|
encoding = float32 little-endian
|
||||||
|
U = eastward-positive,单位 m/s
|
||||||
|
V = northward-positive,单位 m/s
|
||||||
|
每 cell = 2 × float32 = 8 bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
MapLibre 接收的是这张向量场;WeatherColor 根据 U/V 得到色场,RasterParticle 从同一 U/V Field 采样并推进 GPU 粒子。React 不需要提前生成每个粒子的经纬度或轨迹。
|
||||||
|
|
||||||
|
## 5. metadataJson 结构
|
||||||
|
|
||||||
|
当前真实 WeatherV2 T0 metadata 的核心结构如下:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema": "maplibre-particle-field/v1",
|
||||||
|
"fieldId": "ecmwf-hres-wind-2026080700",
|
||||||
|
"fieldType": "WIND",
|
||||||
|
"displayDomain": "GLOBAL",
|
||||||
|
"generation": 1786060800000,
|
||||||
|
"validTime": 1786060800000,
|
||||||
|
"dataVersion": 1786060800000,
|
||||||
|
"bounds": { "west": 112.5, "south": 0.0, "east": 157.5, "north": 55.77657301866769 },
|
||||||
|
"grid": { "width": 513, "height": 769 },
|
||||||
|
"vector": {
|
||||||
|
"layout": "interleaved-uv",
|
||||||
|
"encoding": "float32-le",
|
||||||
|
"noData": -9999.0,
|
||||||
|
"unit": "m/s",
|
||||||
|
"origin": "north-west",
|
||||||
|
"rowOrder": "north-to-south",
|
||||||
|
"columnOrder": "west-to-east",
|
||||||
|
"uPositive": "east",
|
||||||
|
"vPositive": "north"
|
||||||
|
},
|
||||||
|
"payload": { "byteLength": 3155976 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 含义 / 当前约束 |
|
||||||
|
| --- | --- |
|
||||||
|
| `schema` | 必须为 `maplibre-particle-field/v1` |
|
||||||
|
| `fieldId` | Producer 提供的非空 Field 标识 |
|
||||||
|
| `fieldType` | 当前实现必须为 `WIND` |
|
||||||
|
| `displayDomain` | 当前实现必须为 `GLOBAL` |
|
||||||
|
| `generation` | 非 0 `uint64`;Producer 负责赋值,Core 不猜版本 |
|
||||||
|
| `validTime` | 非 0 `uint64`;同一序列使用同一时间基准并严格递增;当前 WeatherV2 使用 Unix 毫秒 |
|
||||||
|
| `dataVersion` | 可选 `uint64`,用于数据版本 / identity;当前真实 fixture 提供 |
|
||||||
|
| `bounds` | `west < east`、`south < north`,描述规则网格覆盖范围 |
|
||||||
|
| `grid.width/height` | 大于 0;总 cell 数=`width × height` |
|
||||||
|
| `vector.layout` | 只支持 `interleaved-uv` |
|
||||||
|
| `vector.encoding` | 只支持 `float32-le` |
|
||||||
|
| `vector.noData` | 必须为有限数值;当前 fixture 为 `-9999.0` |
|
||||||
|
| `payload.byteLength` | 必须严格等于 `width × height × 8` |
|
||||||
|
| `stride` | 可省略;若提供,当前 interleaved UV 必须为 8 bytes |
|
||||||
|
|
||||||
|
真实 fixture 还带 `unit/origin/rowOrder/columnOrder/uPositive/vPositive`、`payload.sha256`、`source` 等审计字段。当前 Native parser 强制读取的是 schema、identity、时间、bounds、grid、layout、encoding、noData 与 byteLength;额外审计字段不会替代上述 canonical 语义。
|
||||||
|
|
||||||
|
注意:当前 `payload.sha256` 可用于文件生产/发布审计,但 `LocalParticleFieldFeed` 目前只读取 `payload.byteLength`,不要把 `sha256` 写成 Core 已强制校验的安全门槛。
|
||||||
|
|
||||||
|
## 6. binary payload 的精确排列
|
||||||
|
|
||||||
|
二维索引:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cell = y * width + x
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 `x=0` 是最西列,`y=0` 是最北行。payload 按 row-major 连续存储:先完整保存最北一行,再向南逐行保存。
|
||||||
|
|
||||||
|
每个 cell 占 8 bytes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
byteOffset = cell * 8
|
||||||
|
[byteOffset + 0 .. +3] = U,float32 little-endian
|
||||||
|
[byteOffset + 4 .. +7] = V,float32 little-endian
|
||||||
|
```
|
||||||
|
|
||||||
|
等价的 float 序列是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
U0, V0, U1, V1, U2, V2, ...
|
||||||
|
```
|
||||||
|
|
||||||
|
也就是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
velocity[2 * cell] = U
|
||||||
|
velocity[2 * cell + 1] = V
|
||||||
|
```
|
||||||
|
|
||||||
|
U>0 表示向东,U<0 表示向西;V>0 表示向北,V<0 表示向南。`noData` 只要出现在某 cell 的 U 或 V 任一分量,该 cell 就标记为无效。NaN/Infinity 不是 noData:当前 Adapter 会直接拒绝整张 Field。
|
||||||
|
|
||||||
|
真实 WeatherV2 fixture:
|
||||||
|
|
||||||
|
```text
|
||||||
|
width = 513
|
||||||
|
height = 769
|
||||||
|
cells = 513 × 769 = 394,497
|
||||||
|
bytes = 394,497 × 8 = 3,155,976
|
||||||
|
```
|
||||||
|
|
||||||
|
所以 `payload.byteLength` 必须正好是 `3155976`;少 1 byte、多 1 byte 或 metadata 声明与实际文件不一致都会 fail-closed。
|
||||||
|
|
||||||
|
一个 2×2 示例:
|
||||||
|
|
||||||
|
```text
|
||||||
|
北行: NW(U=5,V=1) NE(U=6,V=1)
|
||||||
|
南行: SW(U=4,V=0) SE(U=5,V=0)
|
||||||
|
```
|
||||||
|
|
||||||
|
payload 中的 float 顺序必须是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
5,1, 6,1, 4,0, 5,0
|
||||||
|
```
|
||||||
|
|
||||||
|
总长度为 `2 × 2 × 8 = 32 bytes`。
|
||||||
|
|
||||||
|
React/Node 侧若自己生成 canonical payload,可用 `DataView.setFloat32(offset, value, true)`;最后一个参数必须为 `true`,明确写成 little-endian。业务层也可以从服务器取得已经生成好的 `.f32` 文件,不要求 JS 重新编码。
|
||||||
|
|
||||||
|
## 7. React 侧如何提交一帧
|
||||||
|
|
||||||
|
当前正式 bridge 不把大块 U/V 数据变成 JS `number[]`。React 侧准备好 metadata JSON 和本地文件 URI 后直接提交:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const result = await WeatherRuntime.submitWindField(
|
||||||
|
reactTag,
|
||||||
|
JSON.stringify(metadata),
|
||||||
|
payloadFileUri, // 必须是 local file:// URI
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
测试 Host 使用 `expo-asset` 把随 App 打包的 `.f32` materialize 到本地 cache:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const asset = Asset.fromModule(require("./field.f32"));
|
||||||
|
await asset.downloadAsync();
|
||||||
|
const payloadFileUri = asset.localUri;
|
||||||
|
```
|
||||||
|
|
||||||
|
正式 NavSea 不要求数据必须来自 Expo Asset。可以由天气业务层下载、转换或合成 canonical U/V Field,然后保存到 App 可读的本地文件,再把 `file://` URI 交给 `WeatherRuntime`。Native 读取 bytes 后进入唯一的 `RasterArraySource::submitHostField()` / Adapter / Snapshot / Timeline 路径。
|
||||||
|
|
||||||
|
不要使用以下正式 transport:
|
||||||
|
|
||||||
|
```text
|
||||||
|
number[]
|
||||||
|
JSON float array
|
||||||
|
base64
|
||||||
|
hex string
|
||||||
|
每 cell 一个 {lat,lon,u,v} 对象
|
||||||
|
React 每帧传粒子位置
|
||||||
|
```
|
||||||
|
|
||||||
|
这些都会扩大 JS/Native 开销并破坏当前已冻结的 binary Field 边界。
|
||||||
|
|
||||||
|
## 8. 多时间片与 Timeline
|
||||||
|
|
||||||
|
至少提交两个彼此兼容、时间递增的 Field,Core 才能建立 initial window:
|
||||||
|
|
||||||
|
```text
|
||||||
|
T0 → ACCEPTED_PENDING_INITIAL_WINDOW
|
||||||
|
T1 → INITIAL_WINDOW_READY
|
||||||
|
T2 → ACCEPTED_NEXT_SNAPSHOT
|
||||||
|
T3 → ACCEPTED_NEXT_SNAPSHOT
|
||||||
|
```
|
||||||
|
|
||||||
|
随后:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const state = await WeatherRuntime.getState(reactTag);
|
||||||
|
// 预期 state.state === "READY"
|
||||||
|
```
|
||||||
|
|
||||||
|
同一时间序列必须保持相同的 grid 尺寸、bounds、noData、fieldType 和 displayDomain,`validTime` 必须使用同一时间基准并严格递增。当前真实 WeatherV2 验收使用 2026-08-07 00/06/12/18 四帧,每帧相隔 6 小时。
|
||||||
|
|
||||||
|
React 可以用:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await WeatherRuntime.setForecastTime(reactTag, targetTimestampMs);
|
||||||
|
await WeatherRuntime.setPlaybackState(reactTag, "PLAY");
|
||||||
|
await WeatherRuntime.setPlaybackSpeed(reactTag, "NORMAL");
|
||||||
|
```
|
||||||
|
|
||||||
|
但 Timeline cursor、T0/T1 选择、时间插值、Runtime Field Window 和 GPU Field 切换仍由 Core 管理;React 只发低频控制命令,不在 JS 中复制一套播放状态机。
|
||||||
|
|
||||||
|
## 9. 显示与视觉控制
|
||||||
|
|
||||||
|
当前公开控制包括:
|
||||||
|
|
||||||
|
```text
|
||||||
|
setWeatherEnabled
|
||||||
|
setWeatherColorVisible
|
||||||
|
setParticleVisible
|
||||||
|
setTargetFps
|
||||||
|
setWeatherColorStops
|
||||||
|
setParticleColor
|
||||||
|
setTrailColor
|
||||||
|
setParticleCount
|
||||||
|
setParticleLineWidth
|
||||||
|
setParticleLength
|
||||||
|
setParticleOpacity
|
||||||
|
setTrailOpacity
|
||||||
|
setTrailFade
|
||||||
|
setWeatherColorOpacity
|
||||||
|
```
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await WeatherRuntime.setWeatherColorVisible(reactTag, true);
|
||||||
|
await WeatherRuntime.setParticleVisible(reactTag, true);
|
||||||
|
await WeatherRuntime.setParticleCount(reactTag, 4096);
|
||||||
|
await WeatherRuntime.setParticleLineWidth(reactTag, 1.5);
|
||||||
|
await WeatherRuntime.setParticleOpacity(reactTag, 0.9);
|
||||||
|
await WeatherRuntime.setTrailOpacity(reactTag, 0.8);
|
||||||
|
await WeatherRuntime.setTrailFade(reactTag, 0.94);
|
||||||
|
await WeatherRuntime.setWeatherColorOpacity(reactTag, 0.62);
|
||||||
|
```
|
||||||
|
|
||||||
|
这些参数只控制显示,不改变 U/V canonical 数据结构。天气业务数据和渲染视觉参数必须分开管理。
|
||||||
|
|
||||||
|
## 10. 当前 Native 会拒绝哪些数据
|
||||||
|
|
||||||
|
当前 Adapter 采用 fail-closed。以下情况会拒绝 Field,而不是尽量猜测:
|
||||||
|
|
||||||
|
- schema 不是 `maplibre-particle-field/v1`;
|
||||||
|
- `fieldId/fieldType/displayDomain` 缺失或类型错误;
|
||||||
|
- `generation` 或 `validTime` 为 0;
|
||||||
|
- width/height 为 0;
|
||||||
|
- `west >= east` 或 `south >= north`;
|
||||||
|
- `noData` 不是有限数值;
|
||||||
|
- layout 不是 `interleaved-uv`;
|
||||||
|
- encoding 不是 `float32-le`;
|
||||||
|
- stride 提供但不是 8;
|
||||||
|
- metadata `byteLength` 不等于 `width × height × 8`;
|
||||||
|
- 实际文件长度与 expected byte length 不一致;
|
||||||
|
- payload 中包含 NaN/Infinity;
|
||||||
|
- 当前 fieldType 不是 `WIND`;
|
||||||
|
- 当前 displayDomain 不是 `GLOBAL`;
|
||||||
|
- 时间序列不满足既有 compatibility / monotonicity 规则。
|
||||||
|
|
||||||
|
因此 React 侧不要通过容错转换掩盖 producer 错误。Producer/天气业务层应在提交前完成 canonical 化并保持 Field identity、时间和二进制长度稳定。
|
||||||
|
|
||||||
|
## 11. 当前已验证的真实数据基线
|
||||||
|
|
||||||
|
Package Consumer 已使用 ECMWF HRES WeatherV2 真实数据完成 Android 实机验证:4 帧 `2026080700 / 06 / 12 / 18`,每帧 `513×769`、`3,155,976 bytes`,bounds=`112.5,0.0 → 157.5,55.77657301866769`,transport=`FILE_REF / FLOAT32_LE / INTERLEAVED_UV`。用户确认正式 package Consumer APK 正常。
|
||||||
|
|
||||||
|
## 12. NavSea 侧责任边界
|
||||||
|
|
||||||
|
推荐保持以下分工:
|
||||||
|
|
||||||
|
```text
|
||||||
|
React Native / NavSea 天气业务层
|
||||||
|
负责:取数、Route/ETA、多地点/多时间合成、canonical U/V 生成、文件缓存、提交时机
|
||||||
|
↓ metadataJson + local file:// field
|
||||||
|
@navsea/maplibre-particle-rn
|
||||||
|
↓
|
||||||
|
MapLibre Native / Core
|
||||||
|
负责:校验、Snapshot、Timeline、时间插值、空间采样、WeatherColor、Particle、GPU 资源与渲染
|
||||||
|
```
|
||||||
|
|
||||||
|
MapLibre Native 不理解 provider、HTTP、WeatherV2、Route、ETA 或业务缓存;React 不接管 GPU 粒子模拟和 Core Timeline。
|
||||||
|
|
||||||
|
## 13. 版本升级检查
|
||||||
|
|
||||||
|
每次升级 `@navsea/maplibre-particle-rn` 后,至少确认:
|
||||||
|
|
||||||
|
1. npm 根层只有一份 `@maplibre/maplibre-react-native`,没有嵌套 native module;
|
||||||
|
2. Gradle `dependencyInsight` 最终解析到对应 `cc.navsea.maplibre:android-sdk-particle-opengl:<version>`;
|
||||||
|
3. 不出现旧 `step061`、Maven Local 或源码路径;
|
||||||
|
4. Package Consumer 用真实 T0–T3 Field 达到 Runtime `READY`;
|
||||||
|
5. WeatherColor 与 Particle 均正常显示,地图交互无异常;
|
||||||
|
6. Consumer 通过后再升级 NavSea 的包版本。
|
||||||
|
|
||||||
|
正式参考实现:`integration/react-native/MapLibreParticlePackageConsumerTestHost/`。它的意义不是演示 UI,而是验证“正式 npm package → 正式 Maven AAR → 真实 canonical WeatherV2 → Core/Renderer”的完整消费链。
|
||||||
965
docs/30_设计/MapLibre粒子ReactNative使用说明_v2.md
Normal file
965
docs/30_设计/MapLibre粒子ReactNative使用说明_v2.md
Normal file
@@ -0,0 +1,965 @@
|
|||||||
|
# MapLibre 粒子 React Native 使用说明 v2
|
||||||
|
|
||||||
|
本文面向使用 `@navsea/maplibre-particle-rn` 的 React Native / Expo 调用方,描述当前 `13.4.1-particle.1` 的真实公共接口、参数结构、安装方式与调用顺序。
|
||||||
|
|
||||||
|
本文只说明已经存在的公开 API。`@navsea/maplibre-particle-rn` 会完整 re-export `@maplibre/maplibre-react-native@11.3.6`;上游 MapLibre React Native 的全部通用 Map API 不在本文重复展开,本文重点说明 NavSea Particle 新增能力。
|
||||||
|
|
||||||
|
## 1. 当前发布物
|
||||||
|
|
||||||
|
React Native 包名:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@navsea/maplibre-particle-rn
|
||||||
|
```
|
||||||
|
|
||||||
|
当前版本:
|
||||||
|
|
||||||
|
```text
|
||||||
|
13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
当前采用静态 HTTPS tarball 发布,不使用 npm Registry,也不需要 `.npmrc`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.1.tgz
|
||||||
|
```
|
||||||
|
|
||||||
|
Android Native AAR 独立位于 Maven 仓库:
|
||||||
|
```text
|
||||||
|
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1
|
||||||
|
https://pbf.jpcn.ccwu.cc/maven/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 安装
|
||||||
|
|
||||||
|
新 React Native / Expo 项目建议一次安装正式 tarball 和固定的 MapLibre React Native peer dependency:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.1.tgz @maplibre/maplibre-react-native@11.3.6
|
||||||
|
```
|
||||||
|
|
||||||
|
安装后,`package.json` 中 `@navsea/maplibre-particle-rn` 可能记录为完整 HTTPS URL,这是当前预期行为。代码中仍然使用正式包名导入:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Map, Camera, WeatherRuntime } from "@navsea/maplibre-particle-rn";
|
||||||
|
```
|
||||||
|
|
||||||
|
当前 peer dependency 契约:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@maplibre/maplibre-react-native = 11.3.6
|
||||||
|
react >= 19.1.0
|
||||||
|
react-native >= 0.80.0
|
||||||
|
```
|
||||||
|
|
||||||
|
不要同时再安装另一份自定义 MapLibre Native AAR、旧 STEP AAR、源码路径 bridge 或第二份 MLRN native module。
|
||||||
|
## 3. Expo 配置
|
||||||
|
|
||||||
|
Expo / Expo Prebuild 项目必须在 `app.json` 或 app config 中加入:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expo": {
|
||||||
|
"plugins": ["@navsea/maplibre-particle-rn/app.plugin"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
这个 plugin 会完成两件事:
|
||||||
|
|
||||||
|
1. 向 Android Gradle 加入 `https://pbf.jpcn.ccwu.cc/maven`;
|
||||||
|
2. 把官方 `org.maplibre.gl:android-sdk-opengl` 替换为 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1`,避免 APK 中同时进入官方 AAR 与 Particle AAR。
|
||||||
|
|
||||||
|
首次接入、plugin/AAR 版本变化或 Android Native 代码变化后,需要重新生成/编译 Android 原生工程。仅 JS/TS 逻辑变化时,已有包含正确 Native bridge/AAR 的 Debug APK 可以继续通过 Metro 验证新 JS。
|
||||||
|
|
||||||
|
Bare React Native 如果不使用 Expo config plugin,必须在宿主 Android Gradle 中等价完成 Maven repository 与 dependency substitution;不能只依赖 autolinking。
|
||||||
|
|
||||||
|
## 4. 公共导入入口
|
||||||
|
|
||||||
|
包根入口当前导出:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export * from "@maplibre/maplibre-react-native";
|
||||||
|
export { default as WeatherRuntime } from "./NativeWeatherRuntimeBridge";
|
||||||
|
export * from "./weatherV2";
|
||||||
|
```
|
||||||
|
|
||||||
|
因此调用方统一从一个入口导入即可:
|
||||||
|
```ts
|
||||||
|
import {
|
||||||
|
Map,
|
||||||
|
Camera,
|
||||||
|
WeatherRuntime,
|
||||||
|
WeatherV2HttpClient,
|
||||||
|
loadWeatherV2WindIntoRuntime,
|
||||||
|
decodeWeatherV2WindFrame,
|
||||||
|
createParticleWeatherStyle,
|
||||||
|
particleWeatherIds,
|
||||||
|
type ColorStop,
|
||||||
|
type RuntimeSnapshot,
|
||||||
|
type WeatherV2WindContext,
|
||||||
|
type LoadWeatherV2WindResult,
|
||||||
|
} from "@navsea/maplibre-particle-rn";
|
||||||
|
```
|
||||||
|
|
||||||
|
`Map`、`Camera` 以及其他通用 MapLibre React Native API 都来自上游 `@maplibre/maplibre-react-native@11.3.6`,只是由本包重新导出。本文不重新定义这些上游组件的全部 Props。
|
||||||
|
|
||||||
|
## 5. 固定 Weather 对象 ID
|
||||||
|
|
||||||
|
```ts
|
||||||
|
particleWeatherIds = {
|
||||||
|
source: "rn-weather-fields",
|
||||||
|
weatherColorLayer: "rn-weather-color",
|
||||||
|
rasterParticleLayer: "rn-weather-particles",
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`WeatherRuntime.attach()` 会按这三个固定 ID 查找 source/layer。正式宿主 style 如果不用 `createParticleWeatherStyle()`,也必须提供同名对象。
|
||||||
|
## 6. `createParticleWeatherStyle()`
|
||||||
|
|
||||||
|
签名:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
createParticleWeatherStyle(
|
||||||
|
overrides?: Record<string, unknown>,
|
||||||
|
): string
|
||||||
|
```
|
||||||
|
|
||||||
|
返回 MapLibre Style JSON 字符串。默认包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
source: rn-weather-fields / raster-array / host-field-submission=true
|
||||||
|
layer : rn-weather-color / weather-color
|
||||||
|
layer : rn-weather-particles / raster-particle
|
||||||
|
底图 : GSI standard raster
|
||||||
|
```
|
||||||
|
|
||||||
|
最小调用:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Map mapStyle={createParticleWeatherStyle()}>
|
||||||
|
<Camera initialViewState={{ center: [135, 32], zoom: 3.9 }} />
|
||||||
|
</Map>
|
||||||
|
```
|
||||||
|
|
||||||
|
`overrides` 当前是**顶层浅覆盖**,不是深度 merge。若传入 `sources` 或 `layers`,会覆盖 helper 原来的整个顶层字段,因此正式 NavSea style 更推荐显式把 Particle source/layers 合入宿主 style,而不是依赖复杂 overrides。
|
||||||
|
|
||||||
|
## 7. Map 生命周期与 `reactTag`
|
||||||
|
|
||||||
|
`WeatherRuntime` 是 per-MapView Native facade。所有方法第一个参数都是当前 MapView 的正整数 `reactTag`。必须在 Map style 完成加载以后取得 tag 并 attach。
|
||||||
|
```tsx
|
||||||
|
const [reactTag, setReactTag] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const onStyleReady = useCallback(async (event: unknown) => {
|
||||||
|
const tag = findNodeHandle((event as { target?: unknown }).target as never);
|
||||||
|
if (tag == null) return;
|
||||||
|
|
||||||
|
const state = await WeatherRuntime.attach(tag);
|
||||||
|
setReactTag(tag);
|
||||||
|
console.log(state);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (reactTag != null) {
|
||||||
|
void WeatherRuntime.detach(reactTag).catch(() => undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [reactTag]);
|
||||||
|
```
|
||||||
|
|
||||||
|
Map:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Map
|
||||||
|
mapStyle={createParticleWeatherStyle()}
|
||||||
|
onDidFinishLoadingStyle={onStyleReady}
|
||||||
|
>
|
||||||
|
<Camera initialViewState={{ center: [135, 32], zoom: 3.9 }} />
|
||||||
|
</Map>
|
||||||
|
```
|
||||||
|
|
||||||
|
`attach()` 前必须满足 style、`rn-weather-fields`、`rn-weather-color`、`rn-weather-particles` 都已存在,否则 Promise 会 reject。页面销毁时应调用 `detach()`;MapView 自身从 Window detach 时 Native 也有兜底 dispose。
|
||||||
|
## 8. WeatherRuntime 公共类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type RuntimeState =
|
||||||
|
| "DISABLED"
|
||||||
|
| "WAITING_FOR_FIELDS"
|
||||||
|
| "READY"
|
||||||
|
| "ERROR";
|
||||||
|
|
||||||
|
type PlaybackState = "PLAY" | "PAUSE";
|
||||||
|
type TargetFps = "AUTO" | "FPS_30" | "FPS_60";
|
||||||
|
type CommandResult = "ACCEPTED" | "REJECTED";
|
||||||
|
|
||||||
|
type RuntimeSnapshot = Readonly<{
|
||||||
|
state: RuntimeState;
|
||||||
|
playbackState: PlaybackState;
|
||||||
|
targetFps: TargetFps;
|
||||||
|
disposed: boolean;
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Field 提交结果:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type FieldSubmitResult =
|
||||||
|
| "REJECTED_SOURCE_MODE"
|
||||||
|
| "REJECTED_ADAPTER"
|
||||||
|
| "REJECTED_INCOMPATIBLE"
|
||||||
|
| "REJECTED_NON_MONOTONIC"
|
||||||
|
| "REJECTED_CAPACITY"
|
||||||
|
| "ACCEPTED_PENDING_INITIAL_WINDOW"
|
||||||
|
| "INITIAL_WINDOW_READY"
|
||||||
|
| "ACCEPTED_NEXT_SNAPSHOT"
|
||||||
|
| "DUPLICATE"
|
||||||
|
| "UNKNOWN";
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:Native 日志内部可能打印更详细的 C++ reason 名称;React API 应以这里的 `FieldSubmitResult` 字符串为准。
|
||||||
|
## 9. WeatherRuntime 方法总表
|
||||||
|
|
||||||
|
除 `detach()` 外,方法返回值均通过 Promise 返回。Bridge 参数错误、Map/style/runtime 不存在等情况会 Promise reject;Core 正常处理但拒绝业务命令时通常返回 `"REJECTED"` 或对应 `FieldSubmitResult`。
|
||||||
|
|
||||||
|
| 方法 | 参数 | 返回 | 用途 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `attach(reactTag)` | `reactTag:number` | `Promise<RuntimeSnapshot>` | 为当前 MapView 建立 WeatherRuntime |
|
||||||
|
| `detach(reactTag)` | `reactTag:number` | `Promise<void>` | dispose 当前 MapView Runtime |
|
||||||
|
| `getState(reactTag)` | `reactTag:number` | `Promise<RuntimeSnapshot>` | 读取 Native 权威状态 |
|
||||||
|
| `clearWindFields(reactTag)` | `reactTag:number` | `Promise<CommandResult>` | 清空 Host Field/Timeline |
|
||||||
|
| `submitWindField(reactTag, metadataJson, payloadFileUri)` | tag + JSON + 本地文件 | `Promise<FieldSubmitResult>` | 提交一张 canonical 风场 |
|
||||||
|
| `setWeatherEnabled(reactTag, enabled)` | `boolean` | `Promise<CommandResult>` | 总开关 |
|
||||||
|
| `setWeatherColorVisible(reactTag, visible)` | `boolean` | `Promise<CommandResult>` | 场图显示开关 |
|
||||||
|
| `setParticleVisible(reactTag, visible)` | `boolean` | `Promise<CommandResult>` | 粒子显示开关 |
|
||||||
|
| `setTargetFps(reactTag, target)` | `AUTO/FPS_30/FPS_60` | `Promise<CommandResult>` | Runtime 绘制 cadence |
|
||||||
|
| `setForecastTime(reactTag, timestampMs)` | epoch ms | `Promise<CommandResult>` | seek 到目标预报时间 |
|
||||||
|
| `setPlaybackState(reactTag, playback)` | `PLAY/PAUSE` | `Promise<CommandResult>` | 播放/暂停 Timeline |
|
||||||
|
| `setPlaybackSpeed(reactTag, speed)` | `HALF/NORMAL/DOUBLE` | `Promise<CommandResult>` | 0.5x/1x/2x 播放速度 |
|
||||||
|
| `setWeatherColorStops(reactTag, stops)` | `ColorStop[]` | `Promise<CommandResult>` | 风速→场图颜色 |
|
||||||
|
| `setParticleColor(...)` | RGBA | `Promise<CommandResult>` | 粒子颜色 |
|
||||||
|
| `setTrailColor(...)` | RGBA | `Promise<CommandResult>` | 尾迹颜色 |
|
||||||
|
| `setParticleCount(reactTag, count)` | 非负整数 | `Promise<CommandResult>` | 粒子数量 |
|
||||||
|
| `setParticleLineWidth(reactTag, value)` | finite number | `Promise<CommandResult>` | 粒子线宽 |
|
||||||
|
| `setParticleLength(reactTag, value)` | finite number | `Promise<CommandResult>` | 粒子长度 |
|
||||||
|
| `setParticleOpacity(reactTag, value)` | finite number | `Promise<CommandResult>` | 粒子透明度 |
|
||||||
|
| `setTrailOpacity(reactTag, value)` | finite number | `Promise<CommandResult>` | 尾迹透明度 |
|
||||||
|
| `setTrailFade(reactTag, value)` | finite number | `Promise<CommandResult>` | 尾迹衰减 |
|
||||||
|
| `setWeatherColorOpacity(reactTag, value)` | finite number | `Promise<CommandResult>` | 场图整体透明度 |
|
||||||
|
### 9.1 `attach / detach / getState / clearWindFields`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await WeatherRuntime.attach(reactTag);
|
||||||
|
await WeatherRuntime.getState(reactTag);
|
||||||
|
await WeatherRuntime.clearWindFields(reactTag);
|
||||||
|
await WeatherRuntime.detach(reactTag);
|
||||||
|
```
|
||||||
|
|
||||||
|
`reactTag` 必须是正整数并解析到当前 Fabric `MLRNMapView`。`clearWindFields()` 成功后 Runtime 回到 `WAITING_FOR_FIELDS`;已有 Timeline 和当前 Field 会被清空。
|
||||||
|
|
||||||
|
`setWeatherEnabled(false)` 会隐藏 WeatherColor/Particle 并清空 Field,Runtime 进入 `DISABLED`。重新 `setWeatherEnabled(true)` 后进入 `WAITING_FOR_FIELDS`,需要重新提交至少 T0/T1。
|
||||||
|
|
||||||
|
### 9.2 `submitWindField()`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const result = await WeatherRuntime.submitWindField(
|
||||||
|
reactTag,
|
||||||
|
metadataJson,
|
||||||
|
payloadFileUri,
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
- `reactTag`:正整数 MapView tag;
|
||||||
|
- `metadataJson`:非空 UTF-8 JSON,Bridge 上限 1 MiB;
|
||||||
|
- `payloadFileUri`:`file://` URI,或 Android 可读的绝对本地路径;不能使用 HTTP URL;
|
||||||
|
- payload 文件必须可读、非空,Bridge 上限 64 MiB。
|
||||||
|
|
||||||
|
正式数据路径不使用 JS `number[]`、base64 或逐 cell 对象。
|
||||||
|
严格时间序列典型返回:
|
||||||
|
|
||||||
|
```text
|
||||||
|
T0 -> ACCEPTED_PENDING_INITIAL_WINDOW
|
||||||
|
T1 -> INITIAL_WINDOW_READY
|
||||||
|
T2 -> ACCEPTED_NEXT_SNAPSHOT
|
||||||
|
T3 -> ACCEPTED_NEXT_SNAPSHOT
|
||||||
|
```
|
||||||
|
|
||||||
|
至少 T0/T1 两张兼容 Field 建立 initial window 后,Runtime 才进入 `READY`。
|
||||||
|
|
||||||
|
### 9.3 显示开关
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await WeatherRuntime.setWeatherEnabled(reactTag, true);
|
||||||
|
await WeatherRuntime.setWeatherColorVisible(reactTag, true);
|
||||||
|
await WeatherRuntime.setParticleVisible(reactTag, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
语义区别:
|
||||||
|
|
||||||
|
- `setWeatherEnabled`:整个 Weather Runtime 的总开关;关闭时同时清空 Field;
|
||||||
|
- `setWeatherColorVisible`:只控制背景 WeatherColor 场图;
|
||||||
|
- `setParticleVisible`:只控制运动粒子层。
|
||||||
|
|
||||||
|
### 9.4 时间与播放
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await WeatherRuntime.setForecastTime(reactTag, timestampMs);
|
||||||
|
await WeatherRuntime.setPlaybackState(reactTag, "PLAY");
|
||||||
|
await WeatherRuntime.setPlaybackSpeed(reactTag, "NORMAL");
|
||||||
|
await WeatherRuntime.setTargetFps(reactTag, "AUTO");
|
||||||
|
```
|
||||||
|
|
||||||
|
`timestampMs` 必须是非负 Unix epoch 毫秒整数。播放速度允许 `HALF`、`NORMAL`、`DOUBLE`;Target FPS 允许 `AUTO`、`FPS_30`、`FPS_60`。React 只设置目标时间/播放状态,T0/T1 选择、MIX 和 GPU 时间插值由 Native/Core 管理。
|
||||||
|
### 9.5 WeatherColor 色阶
|
||||||
|
|
||||||
|
类型:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ColorStop = Readonly<{
|
||||||
|
speedMps: number;
|
||||||
|
red: number;
|
||||||
|
green: number;
|
||||||
|
blue: number;
|
||||||
|
alpha: number;
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
调用:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await WeatherRuntime.setWeatherColorStops(reactTag, [
|
||||||
|
{ speedMps: 0, red: 0.08, green: 0.20, blue: 0.80, alpha: 0.72 },
|
||||||
|
{ speedMps: 8, red: 0.00, green: 0.85, blue: 0.90, alpha: 0.72 },
|
||||||
|
{ speedMps: 16, red: 1.00, green: 0.85, blue: 0.05, alpha: 0.72 },
|
||||||
|
{ speedMps: 30, red: 0.90, green: 0.08, blue: 0.08, alpha: 0.72 },
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
约束:
|
||||||
|
|
||||||
|
- 当前 Core capacity 为最多 8 个 stop;至少 2 个;
|
||||||
|
- `speedMps` 必须 finite 且严格递增,单位 m/s;
|
||||||
|
- RGBA 必须 finite 且全部在 `[0,1]`;
|
||||||
|
- 两个 stop 之间由 Shader 做线性颜色插值;
|
||||||
|
- 小于第一个 stop 使用第一个颜色,大于最后一个 stop 使用最后一个颜色。
|
||||||
|
|
||||||
|
例如若 0 m/s 与 2 m/s 两个 stop 使用相同 RGBA,则 0~2 m/s 会保持同色。
|
||||||
|
### 9.6 粒子与尾迹视觉参数
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await WeatherRuntime.setParticleColor(reactTag, 1, 1, 1, 0.95);
|
||||||
|
await WeatherRuntime.setTrailColor(reactTag, 0.35, 0.85, 1, 0.8);
|
||||||
|
await WeatherRuntime.setParticleCount(reactTag, 4096);
|
||||||
|
await WeatherRuntime.setParticleLineWidth(reactTag, 1.5);
|
||||||
|
await WeatherRuntime.setParticleLength(reactTag, 0.012);
|
||||||
|
await WeatherRuntime.setParticleOpacity(reactTag, 0.9);
|
||||||
|
await WeatherRuntime.setTrailOpacity(reactTag, 0.8);
|
||||||
|
await WeatherRuntime.setTrailFade(reactTag, 0.94);
|
||||||
|
await WeatherRuntime.setWeatherColorOpacity(reactTag, 0.62);
|
||||||
|
```
|
||||||
|
|
||||||
|
硬性 Bridge 参数约束:
|
||||||
|
|
||||||
|
- `setParticleColor` / `setTrailColor`:RGBA 各分量 `[0,1]`;
|
||||||
|
- `setParticleCount`:0 到 Android `Int.MAX_VALUE` 的整数;
|
||||||
|
- line width / length / opacity / fade / WeatherColor opacity:Bridge 要求 finite number,最终是否接受仍由 Native style property 决定。
|
||||||
|
|
||||||
|
上面的 `4096 / 1.5 / 0.012 / 0.9 / 0.8 / 0.94 / 0.62` 是当前 Consumer Test Host 的验证值,不是所有参数的固定默认契约。
|
||||||
|
|
||||||
|
WeatherColor 色阶、粒子颜色、尾迹颜色是三套独立视觉设置:
|
||||||
|
|
||||||
|
```text
|
||||||
|
setWeatherColorStops -> 背景风速场颜色
|
||||||
|
setParticleColor -> 粒子本体颜色
|
||||||
|
setTrailColor -> 粒子尾迹颜色
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. canonical Wind Field 数据结构
|
||||||
|
|
||||||
|
MapLibre Particle 接收规则二维 U/V 向量场,而不是图片或粒子坐标数组。每个时间片由:
|
||||||
|
|
||||||
|
```text
|
||||||
|
metadataJson + local binary payload file
|
||||||
|
```
|
||||||
|
|
||||||
|
组成。
|
||||||
|
当前 canonical schema:
|
||||||
|
|
||||||
|
```text
|
||||||
|
maplibre-particle-field/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
固定向量语义:
|
||||||
|
|
||||||
|
```text
|
||||||
|
origin = north-west
|
||||||
|
rowOrder = north-to-south
|
||||||
|
columnOrder = west-to-east
|
||||||
|
storage = row-major
|
||||||
|
layout = interleaved-uv
|
||||||
|
encoding = float32-le
|
||||||
|
U = eastward-positive, m/s
|
||||||
|
V = northward-positive, m/s
|
||||||
|
noData = -9999(当前 WeatherV2 converter)
|
||||||
|
每 cell = U float32 + V float32 = 8 bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
metadata 示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema": "maplibre-particle-field/v1",
|
||||||
|
"fieldId": "ecmwf-hres-wind-2026080700",
|
||||||
|
"fieldType": "WIND",
|
||||||
|
"displayDomain": "GLOBAL",
|
||||||
|
"generation": 1786060800000,
|
||||||
|
"validTime": 1786060800000,
|
||||||
|
"dataVersion": 1786060800000,
|
||||||
|
"bounds": { "west": 112.5, "south": 0, "east": 157.5, "north": 55.77657301866769 },
|
||||||
|
"grid": { "width": 513, "height": 769 },
|
||||||
|
"vector": {
|
||||||
|
"layout": "interleaved-uv",
|
||||||
|
"encoding": "float32-le",
|
||||||
|
"noData": -9999,
|
||||||
|
"unit": "m/s",
|
||||||
|
"origin": "north-west",
|
||||||
|
"rowOrder": "north-to-south",
|
||||||
|
"columnOrder": "west-to-east",
|
||||||
|
"uPositive": "east",
|
||||||
|
"vPositive": "north"
|
||||||
|
},
|
||||||
|
"payload": { "byteLength": 3155976 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
二进制排列:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cell = y * width + x
|
||||||
|
byteOffset = cell * 8
|
||||||
|
[offset + 0..3] = U float32 little-endian
|
||||||
|
[offset + 4..7] = V float32 little-endian
|
||||||
|
|
||||||
|
float sequence = U0,V0,U1,V1,U2,V2,...
|
||||||
|
```
|
||||||
|
|
||||||
|
`payload.byteLength` 必须严格等于 `width × height × 8`。有效 U/V 不能是 NaN/Infinity;当前 WeatherV2 converter 对 invalid cell 把 U/V 写为 `-9999`。
|
||||||
|
|
||||||
|
同一 Timeline 内,Field 必须保持兼容 grid/bounds/语义,并且 `validTime`、`generation` 按 Native monotonicity 契约严格向前。React 不直接构造 T0/T1/MIX window。
|
||||||
|
|
||||||
|
## 11. `WeatherV2HttpClient`
|
||||||
|
|
||||||
|
WeatherV2 HTTP 层负责读取服务器 manifest/time index、选择预报帧、下载 `WVECBIN1` 并转换成 canonical U/V。Native/Core 不理解 WeatherV2/provider/HTTP。
|
||||||
|
|
||||||
|
### 11.1 构造函数
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type WeatherV2HttpClientOptions = Readonly<{
|
||||||
|
baseUrl: string;
|
||||||
|
headers?: Readonly<Record<string, string>>;
|
||||||
|
fetchImpl?: WeatherV2Fetch;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const client = new WeatherV2HttpClient({
|
||||||
|
baseUrl: "https://weather.jpcn.ccwu.cc",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`baseUrl` 必须是绝对 `http://` 或 `https://` URL。`headers` 可增加/覆盖请求 header;默认会带 `Accept: application/json, application/octet-stream`。`fetchImpl` 用于宿主注入兼容 fetch,实现测试、代理或特殊网络层。
|
||||||
|
### 11.2 `getWindContext()`
|
||||||
|
|
||||||
|
签名:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
client.getWindContext(options?: {
|
||||||
|
datasetId?: string;
|
||||||
|
referenceTime?: string;
|
||||||
|
}): Promise<WeatherV2WindContext>
|
||||||
|
```
|
||||||
|
|
||||||
|
返回:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type WeatherV2WindContext = Readonly<{
|
||||||
|
datasetId: string;
|
||||||
|
model: string;
|
||||||
|
referenceTime: string;
|
||||||
|
targetTimes: readonly string[];
|
||||||
|
particleTemplate: string;
|
||||||
|
bounds?: {
|
||||||
|
west: number;
|
||||||
|
south: number;
|
||||||
|
east: number;
|
||||||
|
north: number;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
调用示例:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const context = await client.getWindContext({
|
||||||
|
datasetId: "ecmwf-hres",
|
||||||
|
referenceTime: "2026080600",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
两个参数都省略时,从 manifest 的 wind dataset 列表选择第一个匹配项。`referenceTime`/`targetTimes` 当前服务协议使用 `YYYYMMDDHH` 字符串。
|
||||||
|
### 11.3 `selectWindFrames()`
|
||||||
|
|
||||||
|
签名:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
client.selectWindFrames(
|
||||||
|
context,
|
||||||
|
options?: {
|
||||||
|
targetTimes?: readonly string[];
|
||||||
|
count?: number;
|
||||||
|
},
|
||||||
|
): readonly WeatherV2WindFrameRef[]
|
||||||
|
```
|
||||||
|
|
||||||
|
返回元素:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type WeatherV2WindFrameRef = Readonly<{
|
||||||
|
datasetId: string;
|
||||||
|
model: string;
|
||||||
|
referenceTime: string;
|
||||||
|
targetTime: string;
|
||||||
|
url: string;
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
若不传 `targetTimes`,默认从 `context.targetTimes` 开头取 `count ?? 4` 帧;`count` 必须是 `>=2` 的整数。若显式传 `targetTimes`,这些时间必须都存在于 context,并严格递增,且至少两帧。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const refs = client.selectWindFrames(context, {
|
||||||
|
targetTimes: ["2026080700", "2026080706", "2026080712", "2026080718"],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
正式 NavSea 应根据当前时间、Route/ETA 或业务策略选择 targetTimes;固定四帧只是当前回归样例。
|
||||||
|
### 11.4 `fetchCanonicalWindFrame()`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const field = await client.fetchCanonicalWindFrame(ref);
|
||||||
|
```
|
||||||
|
|
||||||
|
返回:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type CanonicalWindField = Readonly<{
|
||||||
|
metadataJson: string;
|
||||||
|
payload: Uint8Array;
|
||||||
|
fieldId: string;
|
||||||
|
validTime: number;
|
||||||
|
generation: number;
|
||||||
|
bounds: WeatherV2Bounds;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
sourceUrl: string;
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
该方法会:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET frame.url
|
||||||
|
-> 校验 WVECBIN1 magic/header/fields/length
|
||||||
|
-> 读取 U/V/speed/valid planes
|
||||||
|
-> 只保留 canonical U/V
|
||||||
|
-> invalid cell 写 noData=-9999
|
||||||
|
-> 生成 maplibre-particle-field/v1 metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
`payload` 此时仍在 JS 内存中,是 `Uint8Array`;提交 Native 前应通过 `WeatherV2BinaryFileStore` 写成临时本地文件。
|
||||||
|
|
||||||
|
## 12. `decodeWeatherV2WindFrame()`
|
||||||
|
|
||||||
|
这是公开的底层 converter,适合已经自行取得 WVECBIN1 `ArrayBuffer` 的调用方:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const canonical = decodeWeatherV2WindFrame(arrayBuffer, frameRef);
|
||||||
|
```
|
||||||
|
|
||||||
|
签名等价于:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
decodeWeatherV2WindFrame(
|
||||||
|
source: ArrayBuffer,
|
||||||
|
frame: WeatherV2WindFrameRef,
|
||||||
|
): CanonicalWindField
|
||||||
|
```
|
||||||
|
|
||||||
|
一般业务代码优先使用 `WeatherV2HttpClient.fetchCanonicalWindFrame()`,不需要直接调用 decoder。
|
||||||
|
## 13. `WeatherV2BinaryFileStore`
|
||||||
|
|
||||||
|
WeatherV2 helper 不绑定 Expo FileSystem。宿主只需提供:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type WeatherV2BinaryFileStore = Readonly<{
|
||||||
|
writeTempFile(
|
||||||
|
fileName: string,
|
||||||
|
bytes: Uint8Array,
|
||||||
|
): Promise<string>;
|
||||||
|
|
||||||
|
removeTempFile?(
|
||||||
|
fileUri: string,
|
||||||
|
): Promise<void>;
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
`writeTempFile()` 必须返回 Native 可读的 `file://` URI或绝对本地路径。`removeTempFile()` 可选;helper 在每帧提交完成后 best-effort 清理。
|
||||||
|
|
||||||
|
Expo FileSystem 示例:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { File, Paths } from "expo-file-system";
|
||||||
|
|
||||||
|
const fileStore = {
|
||||||
|
async writeTempFile(fileName: string, bytes: Uint8Array) {
|
||||||
|
const file = new File(Paths.cache, fileName);
|
||||||
|
file.write(bytes);
|
||||||
|
return file.uri;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
## 14. `loadWeatherV2WindIntoRuntime()`
|
||||||
|
|
||||||
|
这是 WeatherV2 最常用的高层入口。它把 HTTP 取数、转换、临时文件和 Native submission 串成一条流程。
|
||||||
|
|
||||||
|
参数类型:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type LoadWeatherV2WindOptions = Readonly<{
|
||||||
|
reactTag: number;
|
||||||
|
client: WeatherV2WindClient;
|
||||||
|
fileStore: WeatherV2BinaryFileStore;
|
||||||
|
datasetId?: string;
|
||||||
|
referenceTime?: string;
|
||||||
|
targetTimes?: readonly string[];
|
||||||
|
count?: number;
|
||||||
|
weatherColorVisible?: boolean;
|
||||||
|
particleVisible?: boolean;
|
||||||
|
playbackState?: "PLAY" | "PAUSE";
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
调用:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const loaded = await loadWeatherV2WindIntoRuntime({
|
||||||
|
reactTag,
|
||||||
|
client,
|
||||||
|
fileStore,
|
||||||
|
datasetId: "ecmwf-hres",
|
||||||
|
referenceTime: "2026080600",
|
||||||
|
targetTimes: ["2026080700", "2026080706", "2026080712", "2026080718"],
|
||||||
|
weatherColorVisible: true,
|
||||||
|
particleVisible: true,
|
||||||
|
playbackState: "PAUSE",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
内部顺序固定为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
clearWindFields
|
||||||
|
-> setWeatherEnabled(true)
|
||||||
|
-> 设置 WeatherColor / Particle visibility
|
||||||
|
-> getWindContext
|
||||||
|
-> selectWindFrames
|
||||||
|
-> 逐帧 fetch + WVECBIN1 decode
|
||||||
|
-> writeTempFile
|
||||||
|
-> submitWindField
|
||||||
|
-> best-effort removeTempFile
|
||||||
|
-> setForecastTime(第一帧 validTime)
|
||||||
|
-> 可选 setPlaybackState
|
||||||
|
-> getState,要求 READY
|
||||||
|
```
|
||||||
|
|
||||||
|
当前实现按帧**串行**下载/转换/提交,避免同时在 JS 内存保留多张大 Field。
|
||||||
|
|
||||||
|
同一个 `reactTag` 上,该函数带 single-flight:前一次完整加载尚未结束时再次调用,会直接返回同一个 in-flight Promise,不会再次 `clearWindFields()` 或交叉提交另一套 T0/T1/T2/T3。调用方仍建议在 UI 上把加载按钮置为 disabled/loading。
|
||||||
|
|
||||||
|
注意当前调用前提:Runtime 已 `attach()`,且不要处于 `DISABLED`。如果此前调用过 `setWeatherEnabled(false)`,应先:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await WeatherRuntime.setWeatherEnabled(reactTag, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
再调用完整 WeatherV2 loader。
|
||||||
|
|
||||||
|
返回类型:
|
||||||
|
```ts
|
||||||
|
type LoadedWeatherV2WindFrame = Readonly<{
|
||||||
|
fieldId: string;
|
||||||
|
validTime: number;
|
||||||
|
generation: number;
|
||||||
|
bounds: WeatherV2Bounds;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
sourceUrl: string;
|
||||||
|
submitResult: FieldSubmitResult;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type LoadWeatherV2WindResult = Readonly<{
|
||||||
|
context: WeatherV2WindContext;
|
||||||
|
frames: readonly LoadedWeatherV2WindFrame[];
|
||||||
|
runtime: RuntimeSnapshot;
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
成功返回时 `runtime.state` 必须已经是 `READY`,否则 helper 自己 throw。
|
||||||
|
|
||||||
|
## 15. `WeatherV2WindClient` 接口
|
||||||
|
|
||||||
|
`loadWeatherV2WindIntoRuntime()` 不强制 `client` 必须是内置 `WeatherV2HttpClient`,也可以注入自定义实现,只要满足:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type WeatherV2WindClient = Readonly<{
|
||||||
|
getWindContext(options?: {
|
||||||
|
datasetId?: string;
|
||||||
|
referenceTime?: string;
|
||||||
|
}): Promise<WeatherV2WindContext>;
|
||||||
|
|
||||||
|
selectWindFrames(
|
||||||
|
context: WeatherV2WindContext,
|
||||||
|
options?: {
|
||||||
|
targetTimes?: readonly string[];
|
||||||
|
count?: number;
|
||||||
|
},
|
||||||
|
): readonly WeatherV2WindFrameRef[];
|
||||||
|
|
||||||
|
fetchCanonicalWindFrame(
|
||||||
|
frame: WeatherV2WindFrameRef,
|
||||||
|
): Promise<CanonicalWindField>;
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
因此以后 NavSea 可以保留统一的 Runtime loader,同时替换 HTTP/cache/provider 层。
|
||||||
|
## 16. 最小完整 WeatherV2 React Native 示例
|
||||||
|
|
||||||
|
以下示例演示:Map style ready → attach → 从 HTTP 获取 WeatherV2 → canonical U/V → Native Runtime → WeatherColor + Particle。
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { View, findNodeHandle } from "react-native";
|
||||||
|
import { File, Paths } from "expo-file-system";
|
||||||
|
import {
|
||||||
|
Map,
|
||||||
|
Camera,
|
||||||
|
WeatherRuntime,
|
||||||
|
WeatherV2HttpClient,
|
||||||
|
createParticleWeatherStyle,
|
||||||
|
loadWeatherV2WindIntoRuntime,
|
||||||
|
} from "@navsea/maplibre-particle-rn";
|
||||||
|
|
||||||
|
const client = new WeatherV2HttpClient({
|
||||||
|
baseUrl: "https://weather.jpcn.ccwu.cc",
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileStore = {
|
||||||
|
async writeTempFile(fileName: string, bytes: Uint8Array) {
|
||||||
|
const file = new File(Paths.cache, fileName);
|
||||||
|
file.write(bytes);
|
||||||
|
return file.uri;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function WeatherMap() {
|
||||||
|
const [reactTag, setReactTag] = useState<number | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const onStyleReady = useCallback(async (event: unknown) => {
|
||||||
|
const tag = findNodeHandle((event as { target?: unknown }).target as never);
|
||||||
|
if (tag == null) return;
|
||||||
|
await WeatherRuntime.attach(tag);
|
||||||
|
setReactTag(tag);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadWeather = useCallback(async () => {
|
||||||
|
if (reactTag == null || loading) return;
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const context = await client.getWindContext({
|
||||||
|
datasetId: "ecmwf-hres",
|
||||||
|
});
|
||||||
|
|
||||||
|
const targetTimes = context.targetTimes.slice(0, 4);
|
||||||
|
|
||||||
|
const result = await loadWeatherV2WindIntoRuntime({
|
||||||
|
reactTag,
|
||||||
|
client,
|
||||||
|
fileStore,
|
||||||
|
datasetId: context.datasetId,
|
||||||
|
referenceTime: context.referenceTime,
|
||||||
|
targetTimes,
|
||||||
|
weatherColorVisible: true,
|
||||||
|
particleVisible: true,
|
||||||
|
playbackState: "PAUSE",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(result.runtime);
|
||||||
|
console.log(result.frames);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [reactTag, loading]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (reactTag == null) return;
|
||||||
|
void loadWeather();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
void WeatherRuntime.detach(reactTag).catch(() => undefined);
|
||||||
|
};
|
||||||
|
}, [reactTag, loadWeather]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Map
|
||||||
|
mapStyle={createParticleWeatherStyle()}
|
||||||
|
onDidFinishLoadingStyle={onStyleReady}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<Camera
|
||||||
|
initialViewState={{
|
||||||
|
center: [135, 32],
|
||||||
|
zoom: 3.9,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Map>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
实际产品中不要简单使用 `context.targetTimes.slice(0, 4)` 作为长期业务策略。应由 React/NavSea 天气业务层根据当前时间、船位、Route/ETA、缓存和预测有效期决定要提交哪些 canonical Field。
|
||||||
|
|
||||||
|
## 17. 错误处理
|
||||||
|
|
||||||
|
需要区分两类失败:
|
||||||
|
|
||||||
|
1. Promise reject:参数格式错误、MapView 找不到、style 未 ready、Runtime 已 dispose、本地文件不可读等 Bridge/生命周期错误;
|
||||||
|
2. Promise resolve 但返回 `REJECTED` / `REJECTED_*`:请求到达 Native/Core,但被 Runtime、Adapter、Timeline 或 capacity 契约拒绝。
|
||||||
|
建议所有 command 都检查结果:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const commandResult = await WeatherRuntime.setParticleVisible(reactTag, true);
|
||||||
|
if (commandResult !== "ACCEPTED") {
|
||||||
|
throw new Error(`setParticleVisible failed: ${commandResult}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
直接提交 Field 时同样不要忽略返回值。`loadWeatherV2WindIntoRuntime()` 已经内置严格的 T0/T1/T2... expected result 检查,因此高层 WeatherV2 路径优先使用该 helper。
|
||||||
|
|
||||||
|
常见 Field 拒绝原因包括:schema/metadata 不合法、payload 长度不匹配、NaN/Infinity、时间不递增、Field 不兼容、Timeline capacity 已满或 source 不是 Host submission 模式。
|
||||||
|
|
||||||
|
## 18. React 与 Native 的责任边界
|
||||||
|
|
||||||
|
```text
|
||||||
|
React Native / NavSea JS/TS
|
||||||
|
provider / HTTP
|
||||||
|
cache / retry / prefetch
|
||||||
|
当前时间与 forecast time 选择
|
||||||
|
Route / ETA
|
||||||
|
WeatherV2 解析或其他 provider -> canonical U/V
|
||||||
|
临时文件管理
|
||||||
|
低频 Runtime 控制
|
||||||
|
↓
|
||||||
|
metadataJson + local file
|
||||||
|
↓
|
||||||
|
@navsea/maplibre-particle-rn
|
||||||
|
↓
|
||||||
|
MapLibre Native / C++
|
||||||
|
Field 校验
|
||||||
|
Snapshot / Timeline
|
||||||
|
T0/T1 Runtime Field Window
|
||||||
|
GPU 空间采样 + 时间插值
|
||||||
|
WeatherColor
|
||||||
|
Particle / Trail
|
||||||
|
GPU rendering
|
||||||
|
```
|
||||||
|
|
||||||
|
React 不向 Native 传 provider 业务对象,也不每帧驱动粒子位置;Native 不负责 HTTP、Route、ETA 或业务缓存。
|
||||||
|
## 19. Bare React Native Android 手工配置
|
||||||
|
|
||||||
|
如果宿主不使用 Expo config plugin,需要在 Android 根工程中完成与 `app.plugin.js` 等价的配置。核心要求不是“再加一份 AAR”,而是让 MLRN 的 OpenGL Native SDK 请求统一解析到 NavSea Particle AAR。
|
||||||
|
|
||||||
|
当前等价 Groovy 逻辑为:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
allprojects { p ->
|
||||||
|
p.repositories.maven {
|
||||||
|
url = p.uri("https://pbf.jpcn.ccwu.cc/maven")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.configurations.configureEach { configuration ->
|
||||||
|
configuration.resolutionStrategy.dependencySubstitution {
|
||||||
|
substitute module("org.maplibre.gl:android-sdk-opengl") using module(
|
||||||
|
"cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.4.1")
|
||||||
|
rootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")
|
||||||
|
```
|
||||||
|
|
||||||
|
包自身 `react-native.config.js` 已声明 Android `sourceDir=./android`,正常 React Native autolinking 应自动接入 `WeatherRuntimeBridgePackage`。
|
||||||
|
|
||||||
|
## 20. 版本与升级
|
||||||
|
|
||||||
|
当前静态包 URL 包含完整版本号,因此项目升级必须显式修改 URL:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.../navsea-maplibre-particle-rn-13.4.1-particle.1.tgz
|
||||||
|
.../navsea-maplibre-particle-rn-13.4.1-particle.2.tgz
|
||||||
|
```
|
||||||
|
|
||||||
|
已经发布的 tarball 不覆盖。版本有修改时创建新的 `particle.N`,重新 `npm pack`、上传、Consumer 验证后再给 NavSea 使用。
|
||||||
|
## 21. WeatherV2 低层网络类型
|
||||||
|
|
||||||
|
只有需要替换默认 `globalThis.fetch` 时才需要这些类型:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type WeatherV2FetchResponse = Readonly<{
|
||||||
|
ok: boolean;
|
||||||
|
status: number;
|
||||||
|
json(): Promise<unknown>;
|
||||||
|
arrayBuffer(): Promise<ArrayBuffer>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type WeatherV2Fetch = (
|
||||||
|
url: string,
|
||||||
|
init?: Readonly<{
|
||||||
|
headers?: Readonly<Record<string, string>>;
|
||||||
|
}>,
|
||||||
|
) => Promise<WeatherV2FetchResponse>;
|
||||||
|
```
|
||||||
|
|
||||||
|
例如可以用自定义 fetch 包装日志、认证 header 或测试 mock,但返回对象必须至少实现上述四项契约。
|
||||||
|
|
||||||
|
## 22. 当前推荐的 React 调用层级
|
||||||
|
|
||||||
|
普通业务优先级:
|
||||||
|
|
||||||
|
```text
|
||||||
|
第一选择:loadWeatherV2WindIntoRuntime()
|
||||||
|
-> 完整 WeatherV2 HTTP + canonical + submit 流程
|
||||||
|
|
||||||
|
需要自定义选帧:WeatherV2HttpClient.getWindContext()
|
||||||
|
+ selectWindFrames()
|
||||||
|
+ loadWeatherV2WindIntoRuntime()
|
||||||
|
|
||||||
|
需要自定义缓存/Provider:实现 WeatherV2WindClient
|
||||||
|
+ WeatherV2BinaryFileStore
|
||||||
|
+ loadWeatherV2WindIntoRuntime()
|
||||||
|
|
||||||
|
已有 canonical 本地文件:WeatherRuntime.submitWindField()
|
||||||
|
|
||||||
|
已有 WVECBIN1 ArrayBuffer:decodeWeatherV2WindFrame()
|
||||||
|
```
|
||||||
|
|
||||||
|
不要为了普通显示直接绕过高层 helper 操作 Core T0/T1/MIX;这些不是 React 公共控制面。
|
||||||
79
docs/30_设计/MapLibre粒子ReactNative使用说明_v3.md
Normal file
79
docs/30_设计/MapLibre粒子ReactNative使用说明_v3.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# MapLibre 粒子 React Native 使用说明 v3
|
||||||
|
|
||||||
|
本文对应 `@navsea/maplibre-particle-rn@13.4.1-particle.2`。v2 保留为 `.1` 历史接口说明;不要将其中的旧接口用于本版本。
|
||||||
|
|
||||||
|
## 安装与版本
|
||||||
|
|
||||||
|
Consumer 直接安装 `@navsea/maplibre-particle-rn@13.4.1-particle.2` 和 `@maplibre/maplibre-react-native@11.3.6`。RN 包 `.2` 继续使用 Android Native artifact `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1`;本版本没有新的 AAR。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Camera, Map } from "@maplibre/maplibre-react-native";
|
||||||
|
import { FieldRuntime } from "@navsea/maplibre-particle-rn";
|
||||||
|
```
|
||||||
|
|
||||||
|
Map 与 Camera 必须来自官方 MLRN。NavSea 包仅为 peer dependency 对接 MLRN,不转导出地图组件。
|
||||||
|
|
||||||
|
## Style 合入
|
||||||
|
|
||||||
|
宿主保有自己的底图、source 和 layer。将纯 renderer fragment 合入既有 style:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createParticleFieldStyleFragment } from "@navsea/maplibre-particle-rn";
|
||||||
|
|
||||||
|
const fragment = createParticleFieldStyleFragment();
|
||||||
|
const style = {
|
||||||
|
version: 8,
|
||||||
|
sources: { ...hostSources, ...fragment.sources },
|
||||||
|
layers: [...hostLayers, ...fragment.layers],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
fragment 只有 field source、field layer 和 particle layer;不含 GSI 或任何业务底图。使用 `particleFieldIds` 引用 renderer ID,不要自行硬编码其历史内部字符串。
|
||||||
|
|
||||||
|
## Canonical Field 提交
|
||||||
|
|
||||||
|
React/宿主选择业务数据、下载/解码、校验、缓存和本地文件写入;SDK 不理解 provider、network、WeatherV2 或数据集。宿主将既有 Native 数据契约的 `metadataJson` 与本地 `file://` payload 提交一次:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await FieldRuntime.attach(reactTag);
|
||||||
|
await FieldRuntime.clearFields(reactTag);
|
||||||
|
await FieldRuntime.submitField(reactTag, metadataJson, localFileUri);
|
||||||
|
```
|
||||||
|
|
||||||
|
契约保持 `maplibre-particle-field/v1`、`float32-le`、`interleaved-uv`。同一 canonical Field 同时供 field renderer 和 particle renderer 使用,不需要重复提交。
|
||||||
|
|
||||||
|
## FieldRuntime 方法
|
||||||
|
|
||||||
|
所有方法均以 `reactTag: number` 为首参,异步返回现有 bridge 的结果;无 Native bridge 时调用会明确失败,import 本身不会失败。
|
||||||
|
|
||||||
|
| 方法 | 其余参数 | 返回值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `attach` | 无 | `Promise<RuntimeSnapshot>` |
|
||||||
|
| `detach` | 无 | `Promise<void>` |
|
||||||
|
| `getState` | 无 | `Promise<RuntimeSnapshot>` |
|
||||||
|
| `clearFields` | 无 | `Promise<CommandResult>` |
|
||||||
|
| `submitField` | `metadataJson: string`, `payloadFileUri: string` | `Promise<FieldSubmitResult>` |
|
||||||
|
| `setEnabled` | `enabled: boolean` | `Promise<CommandResult>` |
|
||||||
|
| `setFieldVisible` | `visible: boolean` | `Promise<CommandResult>` |
|
||||||
|
| `setParticleVisible` | `visible: boolean` | `Promise<CommandResult>` |
|
||||||
|
| `setTargetFps` | `target: "AUTO" | "FPS_30" | "FPS_60"` | `Promise<CommandResult>` |
|
||||||
|
| `setTargetTime` | `timestampMs: number` | `Promise<CommandResult>` |
|
||||||
|
| `setPlaybackState` | `state: "PLAY" | "PAUSE"` | `Promise<CommandResult>` |
|
||||||
|
| `setPlaybackSpeed` | `speed: "HALF" | "NORMAL" | "DOUBLE"` | `Promise<CommandResult>` |
|
||||||
|
| `setFieldColorStops` | `stops: readonly FieldColorStop[]` | `Promise<CommandResult>` |
|
||||||
|
| `setParticleColor` / `setTrailColor` | `red, green, blue, alpha: number` | `Promise<CommandResult>` |
|
||||||
|
| `setParticleCount` | `count: number` | `Promise<CommandResult>` |
|
||||||
|
| `setParticleLineWidth` / `setParticleLength` | `value: number` | `Promise<CommandResult>` |
|
||||||
|
| `setParticleOpacity` / `setTrailOpacity` / `setTrailFade` / `setFieldOpacity` | `value: number` | `Promise<CommandResult>` |
|
||||||
|
|
||||||
|
`FieldColorStop` 为 `{ value, red, green, blue, alpha }`。`RuntimeSnapshot` 包含 `state`、`playbackState`、`targetFps`、`disposed`;`CommandResult` 为 `"ACCEPTED" | "REJECTED"`。
|
||||||
|
|
||||||
|
`setFieldColorStops()` 继续遵循既有 Native 色阶约束:至少 2 个 stop,`value` 必须为 finite 且严格递增,RGBA 分量位于 `[0,1]`。
|
||||||
|
|
||||||
|
## 独立显示
|
||||||
|
|
||||||
|
`setFieldVisible()` 与 `setParticleVisible()` 可独立切换同一 Field 的两种 renderer。`setEnabled()` 控制 runtime 总开关。SDK 不根据业务类型自动选择显示组合;显示策略由宿主决定。
|
||||||
|
|
||||||
|
## 边界
|
||||||
|
|
||||||
|
本包不提供业务数据类型、HTTP、provider、重试、缓存、路线或 ETA 策略。Native/Core、JNI、shader 和 Android AAR 均保持 `.1` 已验证实现不变。Android 实机验证应在 Consumer 安装 `.2` tarball 后单独执行。
|
||||||
95
docs/30_设计/NavSea_PBF自动识别与轻量混淆跨平台方案设计_v1.md
Normal file
95
docs/30_设计/NavSea_PBF自动识别与轻量混淆跨平台方案设计_v1.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# NavSea PBF 自动识别与轻量混淆跨平台方案设计 v1
|
||||||
|
|
||||||
|
日期:2026-09-27
|
||||||
|
状态:**规划 / DESIGN ONLY;未实现、未测试、未发布。**
|
||||||
|
适用范围:NavSea 自有矢量海图 PBF,MapLibre Native Android / iOS 与 MapLibre GL JS Web。
|
||||||
|
归属:MapLibre 资源加载适配层;**不是粒子、Weather Field 或 QBIN 格式变更**。
|
||||||
|
|
||||||
|
## 1. 背景与目标
|
||||||
|
|
||||||
|
NavSea 计划对自己分发的 PBF 进行低成本字节混淆,避免直接下载后被普通 PBF/MBTiles 工具读取;客户端收到瓦片后恢复标准 PBF,继续使用原有 MapLibre 解析和渲染链。Web 版本也要消费同一份服务器资源。
|
||||||
|
本方案明确只是增加直接复用数据的门槛,不承诺数字版权保护、防逆向或防止明文提取。客户端可被调试,解码后的 PBF 可在内存或缓存环节被截获;公开 Web 尤其如此。
|
||||||
|
|
||||||
|
**核心规则:检测到 NavSea 专用 Magic Header → 按版本解码;未检测到 → 维持 MapLibre 原有标准资源流程。** 不依据文件后缀决定是否解码,不修改标准 PBF 解析器,不建立第二套地图引擎。
|
||||||
|
|
||||||
|
## 2. 范围与不变项
|
||||||
|
|
||||||
|
- 只处理自有矢量瓦片资源;普通 PBF、第三方瓦片、Style JSON、Sprite、Glyph、图片以及 Weather/QBIN 不受影响。
|
||||||
|
- 原有 URL 路径、Source/Layer、Style Z-order、粒子绘制、GPU、地图交互与正常 HTTP 重试行为不变。
|
||||||
|
- 服务器可同时提供普通 PBF 与封装 PBF;三端遵守同一二进制契约,各自实现资源加载适配。
|
||||||
|
- 只在有效响应体上进行格式识别;404、500、304、取消、空响应等继续走既有错误/缓存语义,不把错误页当 PBF 解码。
|
||||||
|
- 本文是未来工作的设计草案;不指定当前源码已有某个具体 Hook,进入实施前须按当前 fork 做只读调查。
|
||||||
|
|
||||||
|
## 3. 封装结构草案
|
||||||
|
|
||||||
|
建议文件名仍为 `.pbf`,但编码后内容已不是标准 PBF,必须先解封装。以下 **16 字节头仅为待验证候选格式**,实施前冻结字节序、长度和兼容矩阵。
|
||||||
|
|
||||||
|
| 偏移 | 长度 | 字段 | 候选语义 |
|
||||||
|
| --- | ---: | --- | --- |
|
||||||
|
| 0 | 8 | Magic | ASCII `NSVTILE\0`,固定 8 字节 |
|
||||||
|
| 8 | 1 | Version | `1` |
|
||||||
|
| 9 | 1 | Codec | `1 = XOR-v1`;其他值拒绝 |
|
||||||
|
| 10 | 1 | Flags | bit0 表示内部 GZIP,其余保留位须校验 |
|
||||||
|
| 11 | 1 | HeaderLength | v1 固定 16,给后续扩展留入口 |
|
||||||
|
| 12 | 4 | CRC32 | 解封装且内部解压后,标准 PBF 明文的 CRC32;约定字节序 |
|
||||||
|
| 16… | 可变 | Payload | 经 XOR 混淆的原始 PBF 或内部 GZIP 字节 |
|
||||||
|
|
||||||
|
CRC32 只检测意外损坏或密钥不匹配的常见现象,**不是抗恶意篡改认证**。Magic 是类型标识,不是秘密,也不是加密强度来源。头部存在但格式、版本、长度、Flags、Codec 或校验不合法时,必须拒绝,不能退回普通 PBF。
|
||||||
|
|
||||||
|
## 4. 编码、识别、解码
|
||||||
|
|
||||||
|
服务器预处理一次:标准 PBF → 计算明文 CRC32 → 可选内部 GZIP → XOR → 添加 Header → 存储/CDN。对于已经是 GZIP 存储的原始瓦片,应先核对实际字节语义,再决定保留内部压缩还是重新封装,避免重复压缩。
|
||||||
|
客户端:收到已按 HTTP 传输层规则处理的响应体 → 检查 Magic → 读取并验证头部 → XOR 还原 Payload → 按 Flags 内部解压 → 校验长度/CRC32 → 把标准 PBF 交给现有解析器。普通资源无 Magic 时完全返回既有处理链,不新增一次盲目 GZIP 解压。
|
||||||
|
|
||||||
|
**HTTP `Content-Encoding` 与封装内部 GZIP 必须分开。** XOR 后的压缩 Payload 不得冒充 HTTP gzip,否则网络库可能在 XOR 前尝试解压。封装可用 `Content-Type: application/octet-stream`,自定义 `X-NavSea-Tile-Encoding` 仅作辅助诊断;格式识别以响应体 Header 为准。HTTP 层若另行压缩整个封装,须确认客户端收到的是解开传输编码后的封装字节。
|
||||||
|
|
||||||
|
已声明为“必须受保护”的自有 Source 可另设严格策略:缺少 NavSea Header 也报错(fail-closed),防止服务端误发明文;普通/混合 Source 则执行“无头=原路径”。Source 保护策略与字节自动识别是不同层面,不得混为一谈。
|
||||||
|
|
||||||
|
## 5. 三个平台的接入边界
|
||||||
|
|
||||||
|
| 平台 | 未来接入方向 | 重要约束 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Android | 在当前 fork 的 MapLibre HTTP 资源响应交给解析器前解封装 | 核对 OkHttp/Native 缓存与 Response 回调实际位置,避免双重解压或额外整份复制 |
|
||||||
|
| iOS | 在当前 MapLibre Native 的对应资源响应路径解封装 | 以实际 iOS 网络资源实现为准,不假设 Android Java 修改会覆盖 iOS |
|
||||||
|
| Web | MapLibre GL JS `addProtocol()` 负责 fetch、校验、解封装并返回标准 PBF `ArrayBuffer` | 自有瓦片 URL 经自定义 scheme 或 `transformRequest` 导向注册的协议;后者本身不能修改响应体 |
|
||||||
|
|
||||||
|
Web 必须核对 GL JS 版本及协议回调契约、Style/TileJSON 的 URL 重写、Worker/主线程负载、CORS 和取消信号;服务端需暴露必要响应头。第三方请求不接入解码器。三端共享格式测试向量,不共享平台相关 HTTP 实现。
|
||||||
|
|
||||||
|
## 6. 缓存、错误与日志
|
||||||
|
|
||||||
|
- CDN/HTTP 缓存以**封装字节**为对象;ETag、Content-Length、缓存键、版本及更新失效策略要与实际封装内容一致。
|
||||||
|
- HTTP 304 没有新响应体;由现有缓存/重验证机制恢复对应实体,不能把空体交给解码器。按当前 Native/Web 路径调查 MapLibre 自有缓存究竟保存封装还是明文,不作默认安全保证。
|
||||||
|
- 若业务要求离线缓存也不可直接导出标准 PBF,需单独设计落盘边界;仅 HTTP 层 XOR 不保证 MapLibre 离线库/内存缓存保存密文。
|
||||||
|
- 解码前限制输入大小,解压后限制最大明文大小与压缩比,避免异常瓦片造成内存放大。数据损坏、版本不支持、密钥不匹配归类为独立 Codec 错误。
|
||||||
|
- 将前述 HTTP URL 诊断与 Codec 诊断分开:`INVALID_URL`、`HTTP_ERROR`、`NETWORK_ERROR`、`UNSUPPORTED_TILE_CODEC`、`TILE_DECODE_FAILED`;只记录必要的 URL(敏感查询参数脱敏)、状态码、版本与长度,不输出密钥或明文 PBF;正常 200 不逐张刷日志,可限频。
|
||||||
|
- 404/500、取消、超时、304、第三方瓦片和正常 200 行为均不因本功能改变。
|
||||||
|
|
||||||
|
## 7. 性能与安全预期
|
||||||
|
|
||||||
|
XOR 是 O(n) 的逐字节操作,优先服务器离线预编码、客户端原地处理,避免 React/JS Bridge 往返和多次完整 Buffer 拷贝。封装额外增加 Header 字节;“XOR 不改变 Payload 长度”不等于整体零额外字节。压缩应在 XOR 前进行,否则压缩收益可能明显恶化。
|
||||||
|
Web 先评估主线程处理时长与地图交互,再决定是否使用 Worker;Android/iOS 关注实际缓存策略、内存峰值与线程阻塞。性能影响只能经真实 PBF 样本、冷/热缓存及拖动缩放实测后定论。
|
||||||
|
|
||||||
|
固定或循环 XOR 密钥可以被分析,已知格式和客户端代码会进一步降低逆向门槛;把算法放到 C++/WASM 或换成 AES 也无法阻止客户端截获已解码的明文。本方案定位为**轻量混淆**。若未来目标提高到防批量下载,应另行规划 HTTPS、授权、短期访问凭据、配额/限速、异常下载监测与必要的数据分发范围;不要把 XOR 描述成加密认证。
|
||||||
|
|
||||||
|
## 8. 未来实施阶段(本次均不执行)
|
||||||
|
|
||||||
|
| 阶段 | 工作 | 产出/闸门 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| A 只读调查 | 当前 fork Android/iOS HTTP、资源与磁盘缓存;GL JS 版本;服务器 PBF/GZIP、CDN、TileJSON/Style 路径 | 确认真实接入点、压缩语义和数据所有权 |
|
||||||
|
| B 协议冻结 | 冻结 Header 字节序/CRC 覆盖、Codec、Flags、版本策略、普通/受保护 Source、Golden vectors | 三端可交叉解码同一 fixture |
|
||||||
|
| C 服务端 | 离线编码工具、标准/封装样本、响应头及缓存策略 | 普通与封装资源同时可分发 |
|
||||||
|
| D Android/iOS | 各自最小资源适配,保持解析器与业务接口不变 | 各平台原生样本验证;构建/实机由用户手工执行 |
|
||||||
|
| E Web | 注册自定义协议、URL 路由、解码与取消/CORS | 自有/第三方混合地图正常 |
|
||||||
|
| F 综合验收 | 性能、离线缓存、故障、日志、安全边界与回退 | 三端统一验收通过后再决定发布 |
|
||||||
|
|
||||||
|
验收矩阵至少覆盖:原始 PBF、标准 HTTP gzip、封装 RAW、封装内部 gzip、无头普通资源、受保护 Source 缺头、短头/错误 Magic/版本/Flags、错误 CRC/密钥、空瓦片、404/500/304、网络失败/取消、第三方瓦片、冷/热/离线缓存、跨平台 Golden Fixture、Web CORS 与长时间拖动缩放;不得以单张瓦片可显示作为最终通过。
|
||||||
|
|
||||||
|
## 9. 待实施前决策
|
||||||
|
|
||||||
|
1. 是否采用 XOR-v1 作为正式首版,还是只做试验;客户端密钥静态分发的风险是否接受。
|
||||||
|
2. Magic/Header 的精确二进制布局、CRC 字节序、最大明文长度、Codec ID 和压缩 Flags 是否按本草案冻结。
|
||||||
|
3. 哪些 NavSea Source 必须受保护;普通/混合资源的无头旁路范围。
|
||||||
|
4. 离线缓存是否必须保存封装字节,以及 MapLibre 内建缓存如何满足该要求。
|
||||||
|
5. Web URL 路由和跨域部署方案;第三方瓦片是否保持完全独立。
|
||||||
|
|
||||||
|
**本次结论:仅保留规划文档。没有新增 Codec、改动服务器资源、修改 MapLibre HTTP、引入密钥、触发构建/测试或宣布任何平台支持完成。**
|
||||||
3
docs/30_设计/README.md
Normal file
3
docs/30_设计/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 设计目录
|
||||||
|
|
||||||
|
调查后再写设计:ParticleState、VelocityField、Trail、RasterParticleLayer接口、LandOceanMask。
|
||||||
27
docs/30_设计/STEP-057_AndroidHost天气运行时接口契约_v1.md
Normal file
27
docs/30_设计/STEP-057_AndroidHost天气运行时接口契约_v1.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# STEP-057 Android Host 天气运行时接口契约
|
||||||
|
|
||||||
|
## 入口与线程
|
||||||
|
|
||||||
|
公开入口为 `org.maplibre.android.maps.WeatherRuntime`,由 Android 主线程创建和调用。`MapLibreMap` 同样要求主线程访问;本 facade 不创建 Timer、线程或 per-frame Java 回调。Core 继续在其既有 Map/Render 线程完成 Timeline、窗口和 GPU 工作。
|
||||||
|
|
||||||
|
`WeatherRuntime.Config(sourceId, weatherColorLayerId, particleLayerId)` 绑定一个已存在的 style runtime;ID 不可为空,未硬编码 Test App ID。
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
- `submitWindField(metadataJson, byte[]) -> FieldSubmitResult`:复用 canonical ingress。结果逐一映射 `REJECTED_*`、`ACCEPTED_PENDING_INITIAL_WINDOW`、`INITIAL_WINDOW_READY`、`ACCEPTED_NEXT_SNAPSHOT`、`DUPLICATE`。
|
||||||
|
- `clearWindFields() -> CommandResult`:暂停 Timeline,清除 pending T0、Timeline entries、authoritative T0/T1 和 retained snapshots;Source/Layer 可复用。
|
||||||
|
- `setWeatherEnabled(boolean)`:OFF 暂停、隐藏两层并 clear fields;Render 线程发现空场时释放粒子 State/Trail/draw、field texture 和 WeatherColor 资源。ON 只恢复 visibility,状态为 `WAITING_FOR_FIELDS`,必须重新提交 T0/T1。
|
||||||
|
- `setWeatherColorVisible` / `setParticleVisible`:仅 visibility,`hide != unload`。
|
||||||
|
- `setForecastTime(timestampMs)`:Core 按 timestamp 选相邻 T0/T1 并计算 MIX。恰好命中末快照为 MIX=1;中间线性插值;超范围、未 READY、Gap/Invalid 返回 `REJECTED`,不会沿用错误旧时间。正常 seek 不 submit field、不 reset particle、不 clear trail;仅跨窗口时沿用既有 OLD_T1→NEW_T0 reuse。
|
||||||
|
- `setPlaybackState(PLAY/PAUSE)`、`setPlaybackSpeed(HALF/NORMAL/DOUBLE)`:复用 Source Timeline,不创建 Android Timer。
|
||||||
|
- `setWeatherColorOpacity`、`setParticleCount`、`setParticleLineWidth`、`setParticleLength`、`setParticleOpacity`、`setTrailOpacity`、`setTrailFade`、`setParticleSpeedColorEnabled`:明确 typed 的低频 visual 更新;正式 Host API 不接收 property string。
|
||||||
|
- `setWeatherColorStops(List<ColorStop>)`:2 至 Core `MaxWeatherColorStops` 个 stop(v1 为 8,Java 经 JNI `getWeatherColorStopCapacity()` 获取),speed finite 严格递增,RGBA 均为有限 [0,1]。低于/高于边界分别钳到首/末颜色,中间线性插值。无效输入 reject,并保留上一套有效 ramp。
|
||||||
|
- `setParticleColors(...)`:独立 particle/trail RGBA;不依赖 WeatherColor ramp。legacy `speed-color-enabled=true` 时仅当前 segment 可使用旧 speed 色,不改变 WeatherColor stops。
|
||||||
|
|
||||||
|
状态为 `DISABLED`、`WAITING_FOR_FIELDS`、`READY`、`ERROR`,仅由低频命令和 submit result 更新。v1 提供同步 typed result/state query;没有 listener,未来 RN 薄层可在这些调用完成后读取结果。
|
||||||
|
|
||||||
|
## JNI 与边界
|
||||||
|
|
||||||
|
Java facade 经 `MapLibreMap` / `NativeMapView` 到 JNI:field submit、clear、timestamp seek、Timeline command、layer visibility、particle/trail colour、WeatherColor stops。C++ Renderer 不接触 provider、HTTP、route、ETA 或预测目录;没有向 Host 暴露 T0/T1、MIX、State A/B、Trail A/B、资源代际或 texture 格式。
|
||||||
|
|
||||||
|
Shader-visible ramp 是 UBO 的 8 个 stop/颜色槽;改 ramp 不上传 Field,也不重建粒子 simulation。粒子/尾迹颜色也只更新 UBO。
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# STEP-058 Weather-local Target FPS 与 Repaint Gate 设计
|
||||||
|
|
||||||
|
## Host contract
|
||||||
|
|
||||||
|
`WeatherRuntime.TargetFps` 只提供 `AUTO`、`FPS_30`、`FPS_60`,默认 `AUTO`。`WeatherRuntime.setTargetFps()` 返回 typed `CommandResult`,`getTargetFps()` 返回当前 Host 选择。Host 不接触 RenderLayer、deadline、`needsRepaint()` 或 renderer `requestRender()` 细节;未来 RN 只包裹该 facade 方法。
|
||||||
|
|
||||||
|
## Shared cadence authority
|
||||||
|
|
||||||
|
天气 cadence 归 `RasterArrayTimelineRuntime` 所有,与 source 的 T0/T1、MIX、playback 共存。它保存 target mode、单一 monotonic last cadence tick、deadline 判定和低频 tick 计数。Particle 与 WeatherColor renderer 都读取同一 timeline authority;没有各自 timer 或 gate。AUTO 始终 due,30/60 使用约 33.33/16.67ms 间隔,且 target 切换重置 cadence deadline,不改变 Timeline/field/window。
|
||||||
|
|
||||||
|
`tick()` 仅在 deadline 到达时推进真实 `steady_clock` elapsed;不把 1/30 或 1/60 写入 simulation delta。这样 particle lifecycle、Trail fade、Timeline playback 和速度仍使用真实 wall time。
|
||||||
|
|
||||||
|
## Repaint 与 delayed wake-up
|
||||||
|
|
||||||
|
`RenderRasterParticleLayer::needsRepaint()` 与 `RenderWeatherColorLayer::needsRepaint()` 共享 source timeline 的 due 判定。Android `WeatherRuntime` 使用已有 `MapLibreMap.triggerRepaint()` 作为最小 delayed wake-up seam,在 READY 且至少一层需要天气持续动画时按目标间隔唤醒;AUTO 不安装天气 wake-up。Runtime disable/clear、两层 hidden 或错误状态会停止 wake-up。未使用 `MapView#setMaximumFps()`,因此 camera/其他 layer 仍可保持地图自身帧率。
|
||||||
|
|
||||||
|
地图高频 camera frame 在未到 deadline 时只重用现有粒子/Trail composite,不推进 State、Trail、Timeline 或 field upload;天气到 deadline 后两层再次消费同一 cadence。WeatherColor visibility 不依赖 particle visibility:Particle hidden + WeatherColor PLAY 仍可唤醒;两层 hidden、WeatherColor PAUSE 且 Particle hidden 时不持续唤醒。
|
||||||
|
|
||||||
|
## 动态修改与边界
|
||||||
|
|
||||||
|
target 切换仅更新 source/timeline cadence,不 reload Style、不重提交 field、不 reset particle State、不 clear Trail、不重建 field texture。clear/disabled 后 timeline 被清空,cadence naturally 无 due frame。Core 保留 canonical U/V、T0/T1/MIX、Runtime Field Window、OLD_T1→NEW_T0 reuse 与 only NEW_T1 upload。
|
||||||
|
|
||||||
|
## 测试策略
|
||||||
|
|
||||||
|
cadence deadline 通过显式 `steady_clock::time_point`/duration 的 deterministic 测试验证,不使用 sleep:AUTO always due、30/60 deadline 前后、target switch 和 single-authority 行为。Android popup 仅作为 Host 模拟器提供 FPS AUTO/30/60 与 HUD 显示;Android Gradle/设备验收另行执行。
|
||||||
|
|
||||||
|
## STEP-058A shared advance 与 Runtime 生命周期修正
|
||||||
|
|
||||||
|
`RasterArrayTimelineRuntime::advanceIfDue(now)` 在同一 mutex 内完成 due 判定、cadence revision、真实 wall-time Timeline 推进和 frame 返回。Particle 与 WeatherColor 均可作为 consumer;先到者推进,后到者收到相同 `cadenceRevision` 且不会再次推进。Particle 用 revision 判断是否运行 State/Trail,因而 WeatherColor-only PLAY 不再依赖 Particle pass,也不会发生双 tick。
|
||||||
|
|
||||||
|
WeatherColor 在 PLAY 时参与 shared advance;PAUSE 时仅显示当前 frame,`needsRepaint()` 不主动保持连续刷新。Particle 保持 active 时,即使 Timeline PAUSE,仍按 cadence 推进自身 simulation。两层 hidden 时 facade 不再安装 delayed wake-up。
|
||||||
|
|
||||||
|
`WeatherRuntime.dispose()` 是 map/style-scoped、幂等的 ownership 结束接口:取消 Handler callback,旧 Runnable 被执行时也不会 repaint 或再次 schedule,后续 control API 拒绝或 fail-fast。Test App 在 style reload、离开 REAL WEATHER 与 Activity destroy 前都先 dispose 旧 Runtime。
|
||||||
53
docs/30_设计/STEP-081_iOS_Metal_Renderer与真机Smoke入口设计_v1.md
Normal file
53
docs/30_设计/STEP-081_iOS_Metal_Renderer与真机Smoke入口设计_v1.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# STEP-081 iOS Metal Renderer 与真机 Smoke 入口设计 v1
|
||||||
|
|
||||||
|
状态:CLOSED / PASS。已在 Pleiades(iPad mini 5 / iPadOS 26.5.2)完成签名安装、Metal runtime、deterministic fixtures 与 QBIN V4 T0/MID/T1/Clear 真机验收。
|
||||||
|
|
||||||
|
## 目标和边界
|
||||||
|
|
||||||
|
Metal 路径消费既有 `RasterArraySource` V4 renderer projection、`WeatherColorLayer` 与 `RasterParticleLayer`。没有 iOS Field model、QBIN decoder 分支或公开 iOS SDK API。Smoke 代码仅在 `platform/ios/app/MBXViewController.mm`,RN iOS bridge 仍是明确非目标。
|
||||||
|
|
||||||
|
## OpenGL → Metal pass 对照
|
||||||
|
|
||||||
|
| `u_pass.x` | OpenGL 既有语义 | Metal `ParticleGpuValidationShader` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `< 0.5` | State A/B update / seed / respawn | 同一 T0/T1 decode、timeMix、VELOCITY/DIRECTION advection 与 fail-closed state write |
|
||||||
|
| `< 1.5` | Trail fade | 同一 retention fade;legacy validation marker 保留 |
|
||||||
|
| `1.5–3.5` | Composite / FrozenComposite | 同一 trail/particle 色彩、mask、speed-color 和 legacy diagnostic 分支 |
|
||||||
|
| `> 3.5` | Trail particle line draw | State read/write displacement line,respawn alpha 抑制跨屏线 |
|
||||||
|
| `> 4.5` | WeatherColor | 同一 scalar/vector magnitude、stops/ramp 和 invalid discard |
|
||||||
|
| `> 5.5` | 无 | Metal-only current-endpoint 1 px quad coverage;只补 raster coverage,不改变 State、速度、时间步或 previous→current 物理长度 |
|
||||||
|
|
||||||
|
MSL 使用 `ParticleGpuValidationUBO` 的同字段 vec4/float4x4 布局;声明与 C++ 的 `alignas(16)` 相同,并在 MSL 中静态断言 `31 * 16` 字节。此 Step 未改动 C++ UBO、QBIN V4 contract 或 OpenGL source。
|
||||||
|
|
||||||
|
## Metal binding
|
||||||
|
|
||||||
|
| 类别 | Metal index | Stable ID |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
| vertex attribute `short2 a_pos` | attribute 0 | `idParticleGpuValidationPosVertexAttribute` |
|
||||||
|
| UBO | buffer `drawableReservedUBOCount` | `idParticleGpuValidationUBO` |
|
||||||
|
| previous | texture/sampler 0 | `idParticleGpuValidationPreviousTexture` |
|
||||||
|
| mask | texture/sampler 1 | `idParticleGpuValidationMaskTexture` |
|
||||||
|
| field T0 | texture/sampler 2 | `idParticleGpuValidationFieldTexture` |
|
||||||
|
| field T1 | texture/sampler 3 | `idParticleGpuValidationNextFieldTexture` |
|
||||||
|
| state | texture/sampler 4 | `idParticleGpuValidationStateTexture` |
|
||||||
|
|
||||||
|
这些 index 来自 Metal `ShaderSource` 的 `attributes`/`textures` 表以及 existing shader IDs,而非 OpenGL location 推测。注册位于 `src/mbgl/mtl/renderer_backend.cpp`,CMake source/include list 位于 `cmake/metal.cmake`。
|
||||||
|
|
||||||
|
## Smoke 数据链
|
||||||
|
|
||||||
|
Test App Runtime Styling 菜单新增 `Particle / QBIN V4 Smoke (T0)`。点击后可选 T0、midpoint、T1 或 clear/release。它从 app bundle `ParticleSmokeFixtures/T0.qbin`、`T1.qbin` 读取现有 RN Wind Golden fixtures;`platform/ios/app/CMakeLists.txt` 在构建后从唯一 fixture 来源复制,不创建第二种数据格式。
|
||||||
|
|
||||||
|
private `MLNMapView` seam 提供 `mbglMap`。Smoke 直接创建 `RasterArraySource`,提交 `fixture.wind` T0/T1、Wind profile contract、ColorBinding 与 VectorComponents ParticleBinding,再添加 `WeatherColorLayer` 与 `RasterParticleLayer`。T0=1789239600000、midpoint=1789241400000、T1=1789243200000,全部通过 source-owned requested forecast time 选择窗口/mix。clear 先移除 layers 后移除 source,允许重复创建。Wind Golden fixture 实际范围为 112.5E..157.5E、0..55.776573N,Test App smoke 会把相机置于约 27.8883N / 135E、zoom 3,保证 FIELD_READY 后画面可观察;这只是 Test App 可视化入口,不改变 Core 数据范围。
|
||||||
|
|
||||||
|
## Metal 真机收口结论
|
||||||
|
|
||||||
|
真机调试确认两个 Metal 特有问题并形成正式修复:
|
||||||
|
|
||||||
|
1. `ShaderProgram::getRenderPipelineState()` 的缓存复用 key 原先只包含调用方 reuse hash,没有包含 color/depth/stencil attachment pixel format。粒子 State 使用 RGBA32Float、Trail 使用 RGBA8Unorm、主 renderable 常见 BGRA8Unorm,同一 shader 因此可能复用到不兼容的 pipeline。修复后 cache key 显式合并 color/depth/stencil format;物理 iPad 上 Trail RGBA8 写入从全零恢复为有效像素。
|
||||||
|
2. deterministic Vortex 实测真实 previous→current 线段平均约 0.065 px、最大约 0.24 px,全部小于 0.5 px。Metal `MTLPrimitiveTypeLine` 对该亚像素长度没有稳定可见覆盖。正式 previous→current line 保持不变,Metal 额外在真实 currentPosition 绘制 1 px quad coverage;不拉长轨迹、不改变 State、advection、时间步或物理速度。
|
||||||
|
|
||||||
|
同时确认 Metal vertex-stage texture/sampler binding、State A/B RGBA32Float、Trail A/B RGBA8、矩阵投影与 Composite 均可工作。
|
||||||
|
|
||||||
|
## 后续边界
|
||||||
|
|
||||||
|
STEP-081 已关闭。下一轮如需 iOS RN,必须另行设计桥接;不得把本 App 的 private Core seam 提升为 SDK 或 NavSea API。当前 Metal vertex+fragment 双阶段纹理绑定可在后续独立技术债中收窄为 stage-specific binding,但不得在 STEP-081 收尾中继续重构。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# STEP-082 iOS Native FieldRuntime Facade 与 XCFramework 发行设计 v1
|
||||||
|
|
||||||
|
状态:IMPLEMENTED / WAITING_MAC_XCFRAMEWORK_VALIDATION
|
||||||
|
|
||||||
|
## 公开边界
|
||||||
|
|
||||||
|
新增 `MLNParticleFieldRuntimeConfiguration`、`MLNParticleFieldRuntime` 和 `MLNMapView (ParticleFieldRuntime)`。公开头只依赖 Foundation 与已有 public Objective-C 前置类型;QBIN 以 `NSData` 传入,不暴露 `mbgl`、STL、Core 指针或 `MLNMapView_Private.h`。
|
||||||
|
|
||||||
|
Configuration 保存 Host-neutral 的 `sourceIdentifier`、`weatherColorLayerIdentifier`、`particleLayerIdentifier`。它不硬编码 RN ID;后续 RN 可继续传 `rn-weather-fields`、`rn-weather-color`、`rn-weather-particles`。
|
||||||
|
|
||||||
|
## 生命周期与 fail-closed
|
||||||
|
|
||||||
|
Runtime 被 `MLNMapView` 的 associated object 持有,Runtime 只弱引用 MapView。replace 先 dispose 并移除旧实例,attach 仅在当前 style 同时存在正确 Core 类型的 `RasterArraySource`、`WeatherColorLayer`、`RasterParticleLayer` 且两个 layer 都引用该 source 时成功。任何不满足条件的 style-ready/source/type/layer 错误都以 `NSError` 返回,不创建 source/layer,不替换 style。
|
||||||
|
|
||||||
|
每条命令重新从 current MapView/current style 查找并验证三对象;不缓存 `RasterArraySource*` 或任何跨 style 生命周期 Core pointer。因此 style reload 后旧 runtime 不会操作悬空对象,而是 fail-closed,Host 应在 style ready 后 replace/attach。
|
||||||
|
|
||||||
|
## V4 转发
|
||||||
|
|
||||||
|
`submitEncodedField` 先守住与 Android 相同的 UTF-8 ID 1..64、contract 1..256 KiB、QBIN 1..64 MiB、generation>0 边界,再调用 Core `parseResolvedFieldProfileContractJson(...)` 和 `RasterArraySource::submitEncodedField(...)`。十个 Core stable result 逐值映射为 ObjC enum;facade 生命周期错误不会伪装成 submit result。
|
||||||
|
|
||||||
|
Color binding 传递 fieldKey、semantic/group、SCALAR/VELOCITY_COMPONENT/DIRECTION_ANGLE 和 expected unit。Particle binding 保留 VECTOR_COMPONENTS 与 DIRECTION_ANGLE,inactive selector 严格为 `(0, 0)`。forecast time/play/pause/speed 和 clear 都只转发给 Core;T0/T1/mix、boundary reuse、QBIN decoder 与 renderer 不在 ObjC 实现重写。
|
||||||
|
|
||||||
|
## 导出与 distribution
|
||||||
|
|
||||||
|
`MLNParticleFieldRuntime.h` 列入 `MLN_IOS_SDK_HEADERS`,由现有 `public_hdrs` 进入 `MapLibre.dynamic`,并由 `Mapbox.h` 导入。`MLNMapView_Private.h` 继续仅列于 private list。
|
||||||
|
|
||||||
|
`release/maplibre-particle/ios/` 建立 module 名不变的 SwiftPM binary template。后续 STEP-083 的链路固定为:`reactTag -> React View Registry -> Fabric ComponentView -> contentView -> MLNMapView -> public MLNParticleFieldRuntime`。当前 peer dependency 是 MLRN 11.3.6;STEP-083 不建立第二个 Map registry、不把 MLRN private header 作为长期 API,并用 `$MLRN_SPM_SPEC` 令 MLRN 与 Bridge 消费同一 MapLibre binary。
|
||||||
45
docs/30_设计/STEP-085_V4_Renderer性能修复设计_v1.md
Normal file
45
docs/30_设计/STEP-085_V4_Renderer性能修复设计_v1.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# STEP-085 V4 Renderer 性能修复设计 v1
|
||||||
|
|
||||||
|
状态:`IMPLEMENTED / WAITING_FOR_RDC_NATIVE_BUILD`
|
||||||
|
|
||||||
|
## 目标与根因
|
||||||
|
|
||||||
|
V4 的 `RasterArraySource` 通过独立的 field-keyed window 产出已经 materialize 的 T0/T1/MIX。旧 renderer 将 V4 误当作“没有 timeline”,从而产生两项错误:WeatherColor 的 `needsRepaint()` 永远为 true;Particle 也永远请求下一帧,并在 `prepare()` 将包含大 vector/scalar/valid 数组的 snapshot 按值复制。
|
||||||
|
|
||||||
|
本设计保持 V3 timeline、V4 Runtime Field Window、QBIN 和所有 RN/Host 公共接口不变,只在 Source 内部和共用 renderer seam 修复 cadence/lifetime。
|
||||||
|
|
||||||
|
## 共用 cadence 语义
|
||||||
|
|
||||||
|
新增 Source-owned `RasterArrayRendererCadence`。它只负责 visual simulation 的 deadline,不携带 field、不会变更 T0/T1/MIX,也不会调用 ForecastClock。
|
||||||
|
|
||||||
|
| targetFps | Core cadence | platform wake |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| AUTO | 每个 MapLibre animation frame 可消费一次 | 沿用平台 animation-frame 语义 |
|
||||||
|
| 30 | 约 33.3ms 才允许一次 simulation | Android Handler / iOS NSTimer 约 34ms 请求一次 repaint |
|
||||||
|
| 60 | 约 16.7ms 才允许一次 simulation | Android Handler / iOS NSTimer 约 17ms 请求一次 repaint |
|
||||||
|
|
||||||
|
`RenderRasterParticleLayer::needsRepaint()` 仅对 active、visible、field-ready 的 V4 Particle 在 deadline 到期时返回 true。因 camera 而来的额外 frame 仍可 composite 当前 trail,但 `consumeIfDue()` 未成功时不建 State/Trail pass、不推进 ping-pong、不衰减 trail。Forecast PAUSE 不影响该 visual cadence。
|
||||||
|
|
||||||
|
Android/iOS runtime timer 仅是已有 facade 的“下一帧 wake”薄层;deadline 与是否推进由共用 C++ Source cadence 判定。hidden、clear、disable 和 dispose 都停止 V4 Particle wake。
|
||||||
|
|
||||||
|
## WeatherColor idle 与 drawable
|
||||||
|
|
||||||
|
V4 WeatherColor 在 PAUSE、field/window/stops/opacity/transform 不变时 `needsRepaint()==false`。它不再以 `sourceTimeline == nullptr` 作为“永远动画”的代理。
|
||||||
|
|
||||||
|
稳定状态下 WeatherColor drawable 复用既有 LayerGroup drawable。T0/T1 texture、mix、opacity、color stops 与 value source 由 Resources/Tweaker 在 render 时更新 UBO;仅 wrap 集合变化才 `clearDrawables()` 后重建。该方案保留 gfx abstraction,未引入 OpenGL/Metal 直接调用。
|
||||||
|
|
||||||
|
## V4 snapshot lifetime
|
||||||
|
|
||||||
|
materialize 仍发生在 Source 的 binding/window 真正变化时,但 `Materialized*Projection` 与 renderer `sourceField/sourceNextField` 全部改为 `shared_ptr<const ParticleFieldSnapshot>`。Source Impl 的不可变副本持有 projection,RenderLayer 只复制 shared handle;稳定 window 的 `prepare()` 不再深复制 scalar/vector/valid arrays。旧 V3 optional snapshot 路径仍在 Source 变化时构造独立 handle,未改变其 API。
|
||||||
|
|
||||||
|
## Debug/Validation observability
|
||||||
|
|
||||||
|
`include/mbgl/particle/renderer_debug_counters.hpp` 在非 release 建立低成本 relaxed atomic 累计器,release 编译为 no-op。覆盖:两 layer 的 needsRepaint/update、particle simulation advance、WeatherColor/Particle drawable rebuild、field texture upload、GPU resource create/release-reset、trail create/resize、V4 projection materialize 与 field-window revision。它不逐帧写普通日志,也不暴露给 NavSea。
|
||||||
|
|
||||||
|
## ForecastClock 审查
|
||||||
|
|
||||||
|
全仓确认 `RasterArraySource::advanceForecastClock(elapsedMs)` 只有定义,当前 Android WeatherRuntime、iOS `MLNParticleFieldRuntime` 与 RN bridge 均没有 production caller。故 V4 PLAY 自动推进尚无 owner;本 Step 不将 cadence timer 偷接成第二个 forecast clock。Host 显式 `setEncodedForecastTime` 继续触发 window refresh;自动 forecast playback 应单独设计 owner 后处理。
|
||||||
|
|
||||||
|
## 生命周期边界
|
||||||
|
|
||||||
|
本 Step 不改变 `detach()`、dispose、Host clear/destroy contract,也不对 retained allocation/Native Heap 归因。计数器和减少 repaint/copy/rebuild 仅为后续 RDC 采样提供证据。
|
||||||
141
docs/30_设计/STEP-094_通用RenderSegment模型与Style分类Diagnostics设计_v1.md
Normal file
141
docs/30_设计/STEP-094_通用RenderSegment模型与Style分类Diagnostics设计_v1.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# STEP-094 通用 Render Segment 模型与 Style 分类 Diagnostics 设计 v1
|
||||||
|
|
||||||
|
## 1. 目标与边界
|
||||||
|
|
||||||
|
Renderer 只接收 Style 顺序和一组连续 Segment 定义。每个 Segment 的执行策略只有 `RETAINED` 或 `CONTINUOUS`;不引入 LOW/MEDIUM/HIGH,不识别 AIS、Route、Weather 等业务语义,不改变 Style Z-order,也不增加 Anchor/Marker Layer。
|
||||||
|
|
||||||
|
V12 的 Bottom Retained / Continuous / Top Retained 是本模型的兼容特例。新实现只有一套 resolved topology;旧 `setContinuousLayerRange()` 先转换为通用 Segment Plan,再进入同一 Renderer 路径。
|
||||||
|
|
||||||
|
## 2. 公共数据模型
|
||||||
|
|
||||||
|
Core 公共类型位于 `include/mbgl/renderer/render_segment.hpp`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RenderSegmentPlan
|
||||||
|
schemaVersion = 1
|
||||||
|
segments[1..N]
|
||||||
|
strategy = RETAINED | CONTINUOUS
|
||||||
|
startLayerId = 第 1 段省略;后续段必填
|
||||||
|
```
|
||||||
|
|
||||||
|
RN 公共类型位于 `packages/maplibre-particle-rn/src/types.ts`,`RendererRuntime.setRenderSegmentPlan(reactTag, plan)` 接收 typed object。TurboModule、Android/iOS package bridge 与 platform API 内部使用 schema-versioned JSON,JS 调用方不接触 raw JSON。
|
||||||
|
|
||||||
|
## 3. Config / Resolve 生命周期
|
||||||
|
|
||||||
|
1. map-scoped runtime state 保存 active config、`configSource` 和单调递增 `configRevision`。
|
||||||
|
2. `setRenderSegmentPlan()` 将 active source 切到 `explicit-render-segment-plan`。
|
||||||
|
3. `setContinuousLayerRange()` 将 active source 切到 `continuous-range-compat`,不会再并存第二套 Renderer topology。
|
||||||
|
4. 没有显式配置时,既有 `startsRetainedDynamicRange()` 仅通过 `unconfigured/legacy` adapter 生成兼容 plan。
|
||||||
|
5. RenderOrchestrator 只在 config、Style ready、Layer add/remove/change 时重新解析 boundary ID 和 diagnostics metadata;Particle 高频 Fast Path 不扫描/复制 Style 字符串,也不 stringify JSON。
|
||||||
|
6. `topologyRevision` 只在 resolved strategy/begin/end 签名变化时递增。Visibility 变化更新 `visible`,不改变 topology revision。
|
||||||
|
|
||||||
|
显式 plan 合法条件:definitions 非空;第 0 段无 boundary;第 1..N-1 段 boundary 存在、非空且 index 严格递增;每段非空;完整覆盖 `[0, layerCount)`。失败时 segments 为空、`valid=false`、释放旧 cache 并走 full renderer;ordered `styleLayers` 仍可读取。
|
||||||
|
|
||||||
|
## 4. V12 Compatibility Mapping
|
||||||
|
|
||||||
|
旧闭区间 `[startLayerId,endLayerId]` 解析为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
startIndex > 0 -> RETAINED [0,startIndex)
|
||||||
|
CONTINUOUS [startIndex,endIndex+1)
|
||||||
|
endIndex+1 < styleLayerCount -> RETAINED [endIndex+1,layerCount)
|
||||||
|
```
|
||||||
|
|
||||||
|
因此自然退化为 R-C-R、R-C、C-R 或 C。`configSource=continuous-range-compat`。旧 `getRetainedUnderlayDiagnostics()` 继续保留摘要;多段真实事实以新 API 为准。
|
||||||
|
|
||||||
|
## 5. Renderer 执行与 Cache Ownership
|
||||||
|
|
||||||
|
Resolved Segment 保留 Style 全局 `layerIndex` 和 LayerGroup 原 ordinal。Retained 构建继续使用 opaque reverse / translucent forward;Continuous 直接绘制同一全局 index 范围。`parameters.currentLayer` 和 `depthRangeSize` 不按 Segment 重新编号。
|
||||||
|
|
||||||
|
Renderer 按 resolved topology 动态创建 `retainedSegmentCaches[segmentIndex]`:
|
||||||
|
|
||||||
|
- RETAINED:独立 `RetainedUnderlayCache`、key、revision、texture、compositor 和 counters;
|
||||||
|
- CONTINUOUS:对应 slot 为空,不创建 retained texture/FBO/compositor;
|
||||||
|
- topology 改变:旧 cache 在 Renderer 线程释放,不能迁移或 composite stale resource;
|
||||||
|
- GPU resource 仍由 `prepareResources()` lazy 创建,稳定 viewport 复用。
|
||||||
|
|
||||||
|
主 buffer 按 Segment 顺序执行:Retained composite 或 Continuous direct render。相邻同策略不会合并。
|
||||||
|
|
||||||
|
## 6. Invalidation
|
||||||
|
|
||||||
|
Layer immutable Impl identity 与全局 Style index 组成 retained Segment content fingerprint。Layer property/visibility 在某 retained Segment 内变化时,只推进该 Segment 的 content revision;其他 retained Segment 可继续 reuse。
|
||||||
|
|
||||||
|
以下仍保守 invalidate 全部 retained Segment:camera/viewport/pixel ratio、source/tile、image/sprite/glyph、placement/transition、context reset、debug/3D、style reload、resource/compositor failure。原因是当前 Core 没有足够稳定的通用 source-to-render-segment dependency graph;本 Step 不扩建该图。
|
||||||
|
|
||||||
|
## 7. Particle Fast Path
|
||||||
|
|
||||||
|
恰好一个 Continuous Segment、其内容满足既有 Particle-only 支持条件、所有实际 Retained cache 有效时,沿用 V12 Fast Path。缺少某个不存在的 retained cache 不构成 miss。
|
||||||
|
|
||||||
|
多个 Continuous Segment 暂不做高级 Fast Path:gate 以 `unsupported-continuous-layer` fail closed,进入正常 full CPU frame;Segment topology、cache 与 Z-order 不变。
|
||||||
|
|
||||||
|
## 8. Android / iOS / RN API
|
||||||
|
|
||||||
|
```text
|
||||||
|
RN RendererRuntime
|
||||||
|
setRenderSegmentPlan(reactTag, typedPlan)
|
||||||
|
getRenderSegmentDiagnostics(reactTag)
|
||||||
|
↓ schemaVersion=1 JSON transport
|
||||||
|
WeatherRuntimeBridge(既有唯一 bridge)
|
||||||
|
↓
|
||||||
|
Android MapLibreMap / iOS MLNMapView
|
||||||
|
↓
|
||||||
|
mbgl::Map typed RenderSegmentPlan / RenderSegmentDiagnostics
|
||||||
|
↓
|
||||||
|
RetainedUnderlayRuntimeState + RenderOrchestrator + RendererImpl
|
||||||
|
```
|
||||||
|
|
||||||
|
Android platform 内部 API 为 `setRenderSegmentPlan(String)` / `getRenderSegmentDiagnostics()`;iOS 为 `setRenderSegmentPlanJSON:` / `renderSegmentDiagnosticsJSON`。Core API 保持 typed。JSON/schema/strategy 非法时返回 REJECTED,同时把 active topology 置 invalid,避免继续复用旧 cache。
|
||||||
|
|
||||||
|
## 9. Diagnostics Schema
|
||||||
|
|
||||||
|
Snapshot 为 pull-based。顶层包含 enabled/configured/valid/configSource/configRevision/topologyRevision/counts/coverage/fallback/reason/Fast Path counters;`styleLayers[]` 始终按 Style 顺序;`segments[]` 包含 begin/end、实际 Layer 成员。Retained 的 `cache` 为 object;Continuous 的 `cache=null`,不会伪装为 disabled retained cache。
|
||||||
|
|
||||||
|
完整示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"enabled": true,
|
||||||
|
"configured": true,
|
||||||
|
"valid": true,
|
||||||
|
"configSource": "explicit-render-segment-plan",
|
||||||
|
"configRevision": 7,
|
||||||
|
"topologyRevision": 4,
|
||||||
|
"styleLayerCount": 6,
|
||||||
|
"segmentCount": 4,
|
||||||
|
"retainedSegmentCount": 3,
|
||||||
|
"continuousSegmentCount": 1,
|
||||||
|
"coverageComplete": true,
|
||||||
|
"fallback": false,
|
||||||
|
"lastReason": "none",
|
||||||
|
"fastPathEligible": true,
|
||||||
|
"fastPathActive": true,
|
||||||
|
"fastPathFallbackReason": "none",
|
||||||
|
"fastPathHit": 120,
|
||||||
|
"fastPathMiss": 3,
|
||||||
|
"fullCpuFrame": 4,
|
||||||
|
"styleLayers": [
|
||||||
|
{"layerIndex":0,"layerId":"background","layerType":"background","visible":true,"sourceId":null,"sourceLayer":null},
|
||||||
|
{"layerIndex":1,"layerId":"water","layerType":"fill","visible":true,"sourceId":"chart","sourceLayer":"water"},
|
||||||
|
{"layerIndex":2,"layerId":"rn-weather-color","layerType":"weather-color","visible":true,"sourceId":"rn-weather-fields","sourceLayer":null},
|
||||||
|
{"layerIndex":3,"layerId":"rn-weather-particles","layerType":"raster-particle","visible":true,"sourceId":"rn-weather-fields","sourceLayer":null},
|
||||||
|
{"layerIndex":4,"layerId":"land-area","layerType":"fill","visible":true,"sourceId":"chart","sourceLayer":"land"},
|
||||||
|
{"layerIndex":5,"layerId":"ais-layer","layerType":"symbol","visible":true,"sourceId":"ais","sourceLayer":null}
|
||||||
|
],
|
||||||
|
"segments": [
|
||||||
|
{"segmentIndex":0,"strategy":"RETAINED","configuredStartLayerId":null,"beginIndex":0,"endExclusive":2,"resolvedStartLayerId":"background","resolvedEndLayerId":"water","layerCount":2,"layers":[{"layerIndex":0,"layerId":"background","layerType":"background","visible":true,"sourceId":null,"sourceLayer":null},{"layerIndex":1,"layerId":"water","layerType":"fill","visible":true,"sourceId":"chart","sourceLayer":"water"}],"cache":{"cacheState":"reuse","contentRevision":19,"build":1,"reuse":120,"invalidation":0,"lastInvalidationReason":"none","textureCreate":1,"compositeCreate":1,"resourceRelease":0}},
|
||||||
|
{"segmentIndex":1,"strategy":"CONTINUOUS","configuredStartLayerId":"rn-weather-color","beginIndex":2,"endExclusive":4,"resolvedStartLayerId":"rn-weather-color","resolvedEndLayerId":"rn-weather-particles","layerCount":2,"layers":[{"layerIndex":2,"layerId":"rn-weather-color","layerType":"weather-color","visible":true,"sourceId":"rn-weather-fields","sourceLayer":null},{"layerIndex":3,"layerId":"rn-weather-particles","layerType":"raster-particle","visible":true,"sourceId":"rn-weather-fields","sourceLayer":null}],"cache":null},
|
||||||
|
{"segmentIndex":2,"strategy":"RETAINED","configuredStartLayerId":"land-area","beginIndex":4,"endExclusive":5,"resolvedStartLayerId":"land-area","resolvedEndLayerId":"land-area","layerCount":1,"layers":[{"layerIndex":4,"layerId":"land-area","layerType":"fill","visible":true,"sourceId":"chart","sourceLayer":"land"}],"cache":{"cacheState":"ready","contentRevision":22,"build":2,"reuse":80,"invalidation":1,"lastInvalidationReason":"layer-mutation","textureCreate":2,"compositeCreate":2,"resourceRelease":1}},
|
||||||
|
{"segmentIndex":3,"strategy":"RETAINED","configuredStartLayerId":"ais-layer","beginIndex":5,"endExclusive":6,"resolvedStartLayerId":"ais-layer","resolvedEndLayerId":"ais-layer","layerCount":1,"layers":[{"layerIndex":5,"layerId":"ais-layer","layerType":"symbol","visible":true,"sourceId":"ais","sourceLayer":null}],"cache":{"cacheState":"reuse","contentRevision":11,"build":1,"reuse":40,"invalidation":0,"lastInvalidationReason":"none","textureCreate":1,"compositeCreate":1,"resourceRelease":0}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
有效 snapshot 保证 Segment layers 拼接等于 styleLayers,Layer 不重号、不遗漏、不重复。RN Test Host 的按钮只在用户操作时配置/拉取并显示一次完整 JSON,不轮询、不逐帧打印。
|
||||||
|
|
||||||
|
## 10. 当前限制
|
||||||
|
|
||||||
|
- 多 Continuous 只保证正确 full renderer,不提供高级 Particle Fast Path。
|
||||||
|
- Source/Tile mutation 尚无通用 dependency graph,仍全 retained invalidation。
|
||||||
|
- Legacy diagnostics 只是兼容摘要,不能表达任意 N Segment。
|
||||||
|
- OpenGL/Metal 编译、Native tests、Android codegen/Gradle 与设备运行由 RDC 独立验证后才能形成 PASS 事实。
|
||||||
76
docs/30_设计/STEP-097_V13三轮问题去重与修复矩阵_v1.md
Normal file
76
docs/30_设计/STEP-097_V13三轮问题去重与修复矩阵_v1.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# STEP-097 V13 三轮问题去重与修复矩阵 v1
|
||||||
|
|
||||||
|
基线:Mac-mini-tei.lan;`particle/main`;起始 `720d309364628f85049cf9969d71c71e8dfacce9`,与查询时 `origin/particle/main` 一致;工作区干净。冻结 `.13` tag 指向 `ee0b89e71`。该 HEAD 相对任务书的 `b5c1c4879` 只新增 STEP-097 任务文档。Integration 本地 C-001/RFC-001 已只读;其远端是否最新尚未由 RDC 证实。所有构建、测试和运行验证:**NOT RUN**。
|
||||||
|
|
||||||
|
## 公共不变量
|
||||||
|
|
||||||
|
`fieldId/generation/validTime`、东向 U/北向 V、T0/T1/MIX、旧 T1→新 T0 复用、Style 全局 Z 顺序、每个 R 独立缓存、WeatherColor 与 Particle 分离、Host 拥有天气获取。无权改变 C-001 的公开生命周期、错误或数据所有权语义。
|
||||||
|
|
||||||
|
以下“入口”均为待执行回归,不能当作运行结果。源码状态随阶段更新;`WAITING_RDC_BUILD_AND_RUNTIME` 表示仅完成定位,尚未实现。
|
||||||
|
|
||||||
|
| # / 原编号 | 当前源码定位与最短触发 | 反例/安全约束 | 拟修文件与回归入口 | 状态 / commit / 平台待测 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| 01 N01/B01 | `paint_parameters.cpp:185` tile 集合相同跨 R-R target 复用 CPU mask | 同一未清 stencil target 可复用 | PaintParameters、Renderer、RenderTarget 已在新目标重置;同 tiles R-R/R-C-R 像素入口尚缺 | WAITING_RDC_BUILD_AND_RUNTIME / `e67e4961b` / GL、Metal |
|
||||||
|
| 02 N02/B02 | `mtl/render_pass.cpp:21` 仅 color clear;`offscreen_texture.cpp:67` 附件创建 | 无 depth/stencil 的 target 不可盲设附件 | Metal descriptor/combined 附件已修改;专用附件故障测试源码尚缺 | WAITING_RDC_BUILD_AND_RUNTIME / `e67e4961b` / Metal |
|
||||||
|
| 03 N03/B12 | `render_orchestrator.hpp:156` catch;`renderer_impl.cpp:824` 仍 mark success | 失败后 full fallback、下帧重试 | visitor 错误标志与 fallback 已接入;异常注入测试源码尚缺 | WAITING_RDC_BUILD_AND_RUNTIME / `e67e4961b` / GL、Metal |
|
||||||
|
| 04 N04/B06 | `Map::Impl::onUpdate()` 在 Continuous update 驱动每个 RasterArraySource;Source 使用 monotonic tick,并用 `forecastTickInProgress` 阻止同步 SourceObserver 重入二次推进 | 显式 setTime/PLAY/PAUSE/rate 由一个 Core clock owner 推进;平台 timer 只唤醒 | `map_impl.cpp`、RasterArraySource、Android/iOS WeatherRuntime;`RasterArrayV4ProductionClockTicksOnceForBothBindingsAndResetsOnCommands`、`RasterArrayV4ClockSourceNotificationReentryDoesNotAdvanceTwice` | CODE_IMPLEMENTED_STATIC_CHECKED / A2 源码提交 `f49ffa68a` / OpenGL 与 Metal host A2 定向 3/3 PASS;FZG1 V4 Golden PLAY/PAUSE/速率/clear/reload PASS;无 iOS runtime(用户豁免) |
|
||||||
|
| 05 B13 | `sameFieldGpuContent()` 保持 revision/version 无关的 payload 相等;renderer GPU 槽位更新使用严格 `sameFieldGpuEndpoint()`;Source mix-only cache 使用 projection identity、binding、immutable frame 和 plane/validity storage pointer identity | mix-only 复用同一 T0/T1 snapshot;frame/revision、grid、validity、U/V、encoding 或 binding 改变则重物化并失效;invalid/clear fail-closed | `field_data_adapter.hpp`、projection materialization、RasterArraySource 与 renderer;Source cache 测试 + `ParticleGpuField.*` | CODE_IMPLEMENTED_STATIC_CHECKED / A2 源码提交 `f49ffa68a` / broad OpenGL 与 Metal host 各 121/121 PASS,A2 定向各 3/3 PASS;FZG1 stable mix 时 upload/reset 计数稳定;clear/reload 后合法增加 |
|
||||||
|
| 06 B07 | 新增内部 signed RGBA8 编码,byte 128 明确表示零;GL/Metal shader 以 high precision 还原 byte | 不改公开 QBIN/FieldProfile encoding;量化误差与 sub-half-quantum 微速边界明确测试 | `particle_gpu_math.hpp`、GL/Metal shader 源及生成头、`particle_gpu_math.test.cpp` | CODE_IMPLEMENTED_STATIC_CHECKED / A2 源码提交 `f49ffa68a` / broad OpenGL 与 Metal host 各 121/121 PASS,含 signed zero、取消、NaN/invalid 与微速边界;两次 shader generator 幂等;无 iOS runtime |
|
||||||
|
| 07 B08 | 统一 helper 与 GL/Metal 粒子位移路径都为 Mercator 东向 U、北向 V 应用相同 cos(latitude) metric,并夹紧极区分母 | 保持 U/V canonical m/s、V 北向为正、既有视觉时间倍率 | `particle_gpu_math.hpp`、renderer 与 GL/Metal shader 源及生成头;0°/45°/60° 四方向和极区测试 | CODE_IMPLEMENTED_STATIC_CHECKED / A2 源码提交 `f49ffa68a` / broad OpenGL 与 Metal host 各 121/121 PASS,含数学定向测试;Android FZG1 场景闭环通过;无 iOS runtime |
|
||||||
|
| 08 N05 | 长度感知 JSON 解析,拒绝 NUL/重复键/非法 schema,invalid fail-closed | 清旧计划、合法恢复 | `render_segment_json.hpp`、双平台 bridge 与 GTest | CODE_IMPLEMENTED / B Core 定向 6/6、扩展 139/139 PASS;待 B Git,iOS runtime 未测 |
|
||||||
|
| 09 N06 | FieldProfileValidator 在 Store commit 前检验有效数值及 scale/offset | validity=0/noData 合法;坏高 revision 不覆盖好帧 | FieldProfileValidator/Source GTest,修正旧 16-bit fixture | CODE_IMPLEMENTED / B Core 定向 6/6、扩展 139/139 PASS;待 B Git |
|
||||||
|
| 10 N07 | View owner、generation guard、map destroy observer 源码已写 | 旧 map 回调不得影响新世代,监听须释放 | Kotlin/Java JUnit,真实 MapLibreMap.onDestroy | Android Native JUnit 2/2、Java 编译与 owner 隔离 JVM JUnit 4/4 PASS;BLOCKED_NATIVE_AAR_COMPATIBILITY:正式 `.13` 缺新 Map 方法,默认 RN consumer 不可配对编译,B 不判 PASS |
|
||||||
|
| 11 F01/C01 | `renderer_impl.cpp:505` full diagnostics 快照 | 外部 pull schema 不变 | runtime/renderer;快照计数 | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Core |
|
||||||
|
| 12 F02/C02/B03 | Metal offscreen `swap()` 同步等待 | 不删不明依赖 wait | Metal;GPU capture | DEFERRED_DESIGN / 无 / Metal |
|
||||||
|
| 13 F03/C03/B04 | cache revision 变化释放 texture | target clear 必须先正确 | cache;同尺寸重绘/resize | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / GL、Metal |
|
||||||
|
| 14 F04/C04 | R 数 × viewport 独立附件内存 | 保留独立 R 与失败 fallback | cache;资源阶梯、失败注入 | DEFERRED_DESIGN / 无 / GL、Metal |
|
||||||
|
| 15 C04/B11 | encoded Store 无总 key/byte 预算 | 不静默驱逐绑定窗口 | Store;容量/旧读者 | DEFERRED_DESIGN / 无 / Core |
|
||||||
|
| 16 F07/C05 | `render_orchestrator.hpp:156` 每段前缀 distance | 全局 ordinal 与 Z 顺序不变 | orchestrator;重排/正反遍历 | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Core |
|
||||||
|
| 17 C06/B05/B10 | A2 已处理相同 immutable projection 的 mix-only 重物化;其余 diagnostics pull、ordinal 与 UI owner 世代发布仍属后续范围 | A2 mix-only 不再复制/比较整张浮点快照;不据此宣称 C 阶段整体完成 | A2 部分:RasterArraySource cache 定向测试及 FZG1;C 部分仍按原任务另行实施 | A2 mix-only 子项已验证 / 当前工作树 `5bb1a0e` / OpenGL、Metal host 定向 3/3 PASS;C 其余项目未开始 |
|
||||||
|
| 18 C07 | drawable/tweaker tick 重建、端点复制 | 不保留 stale GPU 引用 | Particle renderer;tick/resize | DEFERRED_DESIGN / 无 / GL、Metal |
|
||||||
|
| 19 F09/O01 | `runtime_state.hpp:230` 新 topology;旧 cache 析构按 index 计数 | 总量与当前 topology 分离 | runtime/cache 按 revision 过滤;新增 `OldCacheReleaseCannotChargeReplacementAtSameIndex` GTest | CODE_IMPLEMENTED_STATIC_CHECKED / `e67e4961b` / 2026-09-26 Core GTest 1/1 PASS,诊断组 5/5 PASS;GPU 资源验收仍待测 |
|
||||||
|
| 20 F05/E01 | Test Host package/lock 已对齐正式 `.13`,SHA-256/SRI 核对一致 | RN/Native 必须精确同版,不改 `.13` 产物 | package/lock、release manifest、默认 RN 编译 | STATIC_VERSION_LOCK_PASS;B10 新 Native method 不在正式 `.13`,默认消费者仍阻塞 |
|
||||||
|
| 21 F06 | `renderer_impl.cpp` multi-C 走 full CPU | 保留安全 fallback | 后续 A/B 设计 | DEFERRED_DESIGN / 无 / GL、Metal |
|
||||||
|
| 22 F08 | source/tile 等全局失效 | 无依赖图前不缩窄 | 后续依赖设计 | DEFERRED_DESIGN / 无 / Core |
|
||||||
|
| 23 F10/E02 | Integration status/compatibility 仍写 `.5` | 发布≠NavSea 支持 | 负责人协调证据请求 | DEFERRED_DESIGN / 无 / 跨项目 |
|
||||||
|
| 24 STEP-095 §7/B09 | synthetic mask 非共享地理 mask | 不新建公开 mask API | C-001/RFC/ADR 协调 | DEFERRED_DESIGN / 无 / 跨项目 |
|
||||||
|
|
||||||
|
## 操作次数模型(静态,非性能实测)
|
||||||
|
|
||||||
|
- A1:跨 target 清除 CPU mask 为每次目标切换一次 O(1) 状态清理;当前同 tiles 可能零次重绘但产生错误像素。Metal clear 每个 pass 最多各一次附件 load 设置。异常后额外一次 full fallback,避免错误缓存跨帧复用。
|
||||||
|
- A2:PLAY 由唯一 Map/Source clock 每次 update 推进一次;V4 mix-only 的投影重物化计数为 0(50 次 Source tick 定向测试),decoded endpoint 变化时 color/particle projection 各物化一次。FZG1 日志在单次 stable mix-only epoch 内 upload/reset 计数不变;clear/reload 会合法增加计数。以上是源码测试和该次设备记录,不构成普遍 GPU 性能结论。
|
||||||
|
- B:JSON 严格校验 O(输入字节长度),有效数值验证 O(有效 cell × plane 数),View/map 每世代各至多一活跃监听;源码已实施,但正式 .13 Native ABI 不含新方法。无 GPU 性能实测。
|
||||||
|
- C:预期 full diagnostics 深复制从每 render 次降到每外部 pull 次;ordinal 前缀从每段 O(G) 降到 topology 更新 O(G);同时间物化从 O(cell) 降为 O(1)。尚未实施。
|
||||||
|
|
||||||
|
## 2026-09-26 第 19 项实测更新
|
||||||
|
|
||||||
|
- RDC 使用 `0d13552be` 重编译并执行 GTest,第 19 项 1/1 PASS、RenderSegmentDiagnostics 全组 5/5 PASS;详见 `docs/报告/STEP-097_A1_第19项GTest运行验证报告_v1.md`。其他问题状态不因此变化,A1 仍为 PARTIAL。
|
||||||
|
|
||||||
|
## 2026-09-26 第 19 项 FZG1 设备辅助证据
|
||||||
|
|
||||||
|
- 基于相同生产源码的 APK 在 FZG1-2 通过覆盖安装和设备 SHA-256 校验。两轮拓扑循环、重排、缓存 OFF/ON、缺失边界回退与恢复的 20 张截图及 UI 诊断已保存;有效场景的 B/T 粗粒度 textureCreate-resourceRelease 均为 0 或 1。此证据只支持 Android 运行态冒烟,不替代此前旧 revision 归属的 GTest 1/1 PASS,也不能证明 GPU 无泄漏。详见 `docs/报告/STEP-097_A1_第19项FZG1运行验证报告_v1.md`。A1 仍因 01/02/03 未闭合保持 PARTIAL。
|
||||||
|
|
||||||
|
## 2026-09-26 A1 专用入口补充(待 RDC 执行)
|
||||||
|
|
||||||
|
- 01:Android Validation Activity 的 STEP-093 菜单新增 STEP-097 same-source stencil 子菜单;`Step097StencilFixture` 用一个本地 GeoJSON source 与四个 fill layer 生成 R-R、R-C-R、C-R-R,菜单可重载与读 segment 诊断,既有 CACHE ON/OFF 可切换。RDC 仍需首帧/重建/reuse 的实际像素截图与 cache 计数;此处仅是可操作 fixture,未实测。
|
||||||
|
- 02:`mtl_retained_attachments.test.cpp` 检查实际离屏资源的 color/depth/stencil 绑定、simulator/x86 combined attachment、无 depth/stencil 时跳过 clear;生产 `RenderPass` 共用的 clear 辅助函数可检查 load/clear/store。设备资源创建失败与 encoder 失败的运行注入仍待 RDC 验证。
|
||||||
|
- 03:`render_orchestrator_error.test.cpp` 注入正/反向范围 visitor 异常,核对 observer/failed 标志,并用 cache 状态机核对失败后继续 Build、成功重试后 Reuse。真实 GPU 绘制故障与完整 renderer fallback 仍待 RDC 注入验证。
|
||||||
|
- 01/02/03 均保持 `WAITING_RDC_BUILD_AND_RUNTIME`;源码入口准备不等于编译或运行 PASS。19 的既有实测状态不变。
|
||||||
|
|
||||||
|
## 2026-09-26 A1 Android/Core 实测收口
|
||||||
|
|
||||||
|
- 01:same-source tiled fixture RR/RCR/CRR 两轮、rebuild/cache OFF/ON 共 18 点,屏幕中心与区域外采样两组像素均 18/18 一致;状态 valid=true/fallback=false,Android FZG1 实测通过。源码/测试提交 `d0ced7235`,编译修正 `8c006ac83`。
|
||||||
|
- 02:Metal host runner 已编译,GTest 两个测试均因没有 Metal device SKIP;用户决定不执行 iOS 运行测试,记 `USER_ACCEPTED_WITHOUT_IOS_RUNTIME_TEST`,不得称 Metal 实测 PASS。
|
||||||
|
- 03:`RenderOrchestratorError.*` 原生测试 2/2 PASS;真实 Android GPU fault injection 不在本次证据中。
|
||||||
|
- 19:前述 Core 1/1 PASS、诊断组 5/5 PASS、FZG1 辅助测试均保留。A1 在 Android/Core 验收及 iOS 用户豁免口径下进入 A2;详细事实见 `docs/报告/STEP-097_A1_Android专项验收报告_v1.md`。
|
||||||
|
|
||||||
|
## 2026-09-26 A2 RDC 最终验证
|
||||||
|
|
||||||
|
- 04–07 已在同一 dirty 源码工作树完成实现与 RDC 独立验证。旧 `sameFieldGpuContent()` 测试断言修复后原 58 项 58/58 PASS;更新后的 broad filter OpenGL 与 Metal host 各 121/121 PASS,三项 A2 Source 定向测试两平台各 3/3 PASS。
|
||||||
|
- 标准 Android Validation APK 为 OpenGL Debug/x86_64,61,753,803 bytes,SHA-256 `27bf4a6cc191bd86c4d1c10398478d81c2999edd61fe7242e40e5188516b0ca5`;安装到 FZG1-2 后设备 APK hash 相同。V4 Golden Wind test-only T0/T1 场景 17 张 PNG、24 个步骤、runner exit 0、ANR=0;PLAY 30 秒 UI 可响应,PAUSE 稳定,clear/reload 后再次 PLAY/PAUSE。
|
||||||
|
- 日志按资源窗口分别记录:clear/reload 前 mix-only epoch `fieldUploadCount=6/resetCount=2` 稳定;explicit clear/reload 后计数合法增加,达到 `fieldUploadCount=10/resetCount=4`,此后 replay 稳定。不可把 clear/reload 期间的上传与 reset 写成零。
|
||||||
|
- build source diff SHA-256 `18927a5f30a88f5c1c3a00e0017473dc33b04151c227e4baf52250dc4ad2e6a2` 与忽略目录中的 APK source snapshot 完全一致。未运行 iOS device runtime,按用户豁免记 `USER_ACCEPTED_WITHOUT_IOS_RUNTIME_TEST`;不得称 Metal host GTest 为 iOS PASS。A2 Android/Core 完成,B/C 未开始,整个 STEP-097 仍 PARTIAL。
|
||||||
|
|
||||||
|
## 2026-09-26 B 独立验证及阻塞
|
||||||
|
|
||||||
|
- RDC Core 定向 4/4、修正旧 16-bit fixture 后 broad 134/134 PASS;正式 .13 Android SDK 缺 B10 新 map destroy observer,默认 RN consumer 无法与该新源码配对。B PARTIAL,C 不启动;详见 B 独立报告。
|
||||||
76
docs/30_设计/STEP-098_QBIN_V4单帧与八帧滚动缓存设计_v1.md
Normal file
76
docs/30_设计/STEP-098_QBIN_V4单帧与八帧滚动缓存设计_v1.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# STEP-098 QBIN V4 单帧与八帧滚动缓存设计
|
||||||
|
|
||||||
|
日期:2026-09-26
|
||||||
|
状态:**已按接受的设计契约实现源码;Native 构建、定向测试与平台运行验收待 RDC 执行**。
|
||||||
|
|
||||||
|
## 1. 权威契约
|
||||||
|
|
||||||
|
- RFC-004:`/Users/tei/dev/navsea-particle-integration/rfc/RFC-004_QBIN_V4单帧首显与八帧滚动缓存.md`
|
||||||
|
- ADR-001:`/Users/tei/dev/navsea-particle-integration/adr/ADR-001_QBIN_V4单帧与滚动缓存语义.md`
|
||||||
|
- Contract C-002:`/Users/tei/dev/navsea-particle-integration/contracts/C-002_QBIN_V4单帧与滚动缓存.md`
|
||||||
|
- COORD-005:`/Users/tei/dev/navsea-particle-integration/coordination/active/COORD-005/决议.md`
|
||||||
|
- 读取时 Integration `main` / `origin/main` 均为 `1dd576d3304e267e53ed64d31f9711d2350ce10a`。四份文件为 ACCEPTED / DESIGN ONLY 或 IMPLEMENTATION_PENDING,不代表实现、设备或 V15 发布通过。
|
||||||
|
- 原任务 v1 的旧 RFC-003 闸门和 `docs/报告/STEP-098_公共契约阻塞报告_v1.md` 属于此前状态。用户已确认旧 RFC-003/COORD-004 B 作废删除;本次不恢复、不改写任务书或旧报告。
|
||||||
|
|
||||||
|
八帧时间示例按 C-002 为 **13:00–20:00**。提交 21:00 后删除最早端 13:00。等距离时固定删除原最早端。
|
||||||
|
|
||||||
|
## 2. Core 存储与身份
|
||||||
|
|
||||||
|
`EncodedFieldStore` 按 `fieldKey` 保存独立 `FieldSeries`,默认每个序列容量 8。每个序列按 `validTimeMs` 严格升序存储;插入新帧使用 lower-bound 定位,不假定时间间隔。重复时间继续走 Duplicate、revision、stale、identity conflict 现有判定,不触发容量淘汰。
|
||||||
|
|
||||||
|
为同时支持新预报批次单帧首显和防止跨批次混插,分开处理两类兼容性:
|
||||||
|
|
||||||
|
- **同 key 共存兼容性**:grid、validity plane schema、所有 data plane 的数量与 schema 相同。不同 `fieldID`、QBIN `referenceTimeMs` 或解析 profile 的批次可以在序列中共存;否则有效的新日/新批次单帧可能无法进入序列。
|
||||||
|
- **时间插值兼容性**:除共存 schema 相同外,`fieldID`、`referenceTimeMs`、`profileID` 和 `profileVersion` 必须全部相同。resolve 只在相邻时间端点满足这些条件时返回双帧;不同批次之间的时间返回 unavailable。Ingress 会把已验证的 profile 身份保留到解码帧,避免校验后丢失身份。
|
||||||
|
|
||||||
|
满额插入先复制现有有序序列,在候选序列上执行淘汰和新增端点 schema 校验。候选有效后才 swap 到正式序列;grid/plane schema 不兼容时返回既有 `RejectedIncompatible` 路径,不淘汰现有帧。对容量 8 的正常有效插入返回既有 `AcceptedInserted`;`RejectedCapacity` 枚举与 RN 字符串继续保留兼容,不用于正常满额滚动。
|
||||||
|
|
||||||
|
对提交时间 `n` 和原序列最早/最晚端点 `e/l`,淘汰较远端:`|n-e| >= |l-n|` 时淘汰原最早端,否则淘汰原最晚端。距离计算避免无符号时间相减下溢。该规则不读取 forecast clock,所以仍符合 RN 先 submit、后 setTime 的顺序。
|
||||||
|
|
||||||
|
## 3. 单帧、双帧与投影
|
||||||
|
|
||||||
|
- 精确匹配某个 `validTimeMs` 时 resolver 返回同一个不可变 frame 作为 T0/T1,`mix=0`。颜色和有合法 U/V 或方向分量的粒子可从该帧物化;标量帧仍不能通过向量绑定校验。
|
||||||
|
- 非精确时间只在合法的相邻双帧 bracket 内插值,mix 按两端实际时间比例计算。落在窗口外、跨 batch/profile 边界或只有一帧但时间不匹配时返回 unavailable;不回退为最近帧。
|
||||||
|
- 后续帧到达只更新实际变化的投影。新帧位于当前 bracket 外时保留 Source 的不可变物化 projection 与样本时刻;当前端点被淘汰后重新 resolve,原投影不可用则清除。
|
||||||
|
- 单帧 materialization 只创建一个 `ParticleFieldSnapshot` 并让 T0/T1 共用它。Particle 和 WeatherColor 的首次资源安装也共享相同 endpoint 的 GPU texture/image/scale;GL 与 Metal 共用这段 renderer 代码及语义。设备资源实际行为仍须各平台独立验证。
|
||||||
|
- 淘汰仅释放 Store 对已移除 frame 的引用。已发布 projection 或 GPU 更新仍持有共享 snapshot/texture 引用时,按 shared ownership 生命周期释放。
|
||||||
|
|
||||||
|
## 4. 内存观测范围
|
||||||
|
|
||||||
|
新增 `DecodedFieldFrame::decodedByteSize()`、`FieldSeries::decodedByteSize()` 与 `EncodedFieldStore::decodedByteSize()`,返回 validity bitset 加所有非 validity 原始 plane storage 的字节总和;共享 validity plane storage 不重复计数。该计数**不包含** profile/容器元数据、物化 Float32 snapshot、图像、GL/Metal texture、粒子 state 或 trail。
|
||||||
|
|
||||||
|
公式为单帧 `ceil(cellCount / 8) + Σ(plane payload bytes)`;序列与 Store 分别对帧及 key 求和。Native 定向测试代码覆盖滚动时每 key 保持 8 帧及此原始字节计数回落/清空,但本轮没有运行测试,也没有 CPU/GPU 峰值、32 帧总量或长期泄漏实测。8 帧不构成全局内存保证;不新增固定 MiB 上限或公开容量错误码。
|
||||||
|
|
||||||
|
## 5. API、职责与平台
|
||||||
|
|
||||||
|
RN TypeScript、Android Kotlin/JNI、iOS Objective-C++/Facade 保持 `submitEncodedField(...)` 与 `setEncodedForecastTime(...)` 两步调用及原有方法名、参数和结果名称;Codex 静态核对现有传递路径,本轮没有改 bridge。submit 不修改目标时钟,setTime 不下载数据。两调用仍非原子,不承诺无闪烁。
|
||||||
|
|
||||||
|
`DecodedFieldFrame` 增加内部解析 profile 身份字段及 decoded-byte 计数方法,改变该 C++ 数据结构的二进制布局;不改变 RN/Android/iOS bridge 的公开方法签名或结果枚举。Core 与两个平台必须使用同一源码重编;不得把既有 `.13` AAR 或旧发布物当作新 ABI 消费证据。
|
||||||
|
|
||||||
|
NavSea、Server、下载/cache/manifest、日期 UI、QBIN 格式、Rice 解码、V3、Style Z-order、provider、公开 RN API 均未改。
|
||||||
|
|
||||||
|
## 6. 定向测试映射
|
||||||
|
|
||||||
|
以下是本轮新增或扩展的测试代码覆盖,不代表已执行:
|
||||||
|
|
||||||
|
| 项目 | 测试入口 | 本轮代码覆盖 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| T01 | `ParticleFieldBinding.SingleFrameProjectsAtItsExactTimeAndUpgradesToInterpolation`;`Source.RasterArrayV4SingleExactFrameMaterializesAndSecondFrameEnablesInterpolation` | 单帧精确颜色/向量 projection、T0=T1、mix=0、第二帧中点升级;标量-only 无向量绑定 |
|
||||||
|
| T02 | 同上 | 第二帧到达后返回双帧与 mix=0.5 |
|
||||||
|
| T03 | `ParticleFieldRuntime.UnorderedSubmissionsAreSortedAndReferenceCyclesStaySeparate` | 乱序排序;wind/wave 分 key;跨 reference/profile 只可单帧解析 |
|
||||||
|
| T04 | `ParticleFieldRuntime.FullWindowRollsBothDirectionsAndBreaksTiesAtEarliest`;`Source.RasterArrayV4RollingWindowPreservesAnUnchangedActivePair` | 满 8 后向未来提交、淘汰最早端并保持活动 pair |
|
||||||
|
| T05 | `ParticleFieldRuntime.FullWindowRollsBothDirectionsAndBreaksTiesAtEarliest`;Source rolling 测试 | 满额向过去提交并淘汰最晚端 |
|
||||||
|
| T06 | `ParticleFieldRuntime.FullWindowRollsBothDirectionsAndBreaksTiesAtEarliest` | 中部等距插入固定淘汰最早端、有序且 8 帧 |
|
||||||
|
| T07 | `Source.RasterArrayV4StoresNewReferenceAndProfileAsSingleFrameWithoutCrossBatchMix` | 新 reference/profile 批次精确首显、补帧后同批插值、回到早批次单帧 |
|
||||||
|
| T08 | `ParticleFieldRuntime.RevisionIdentityAndIndependentCadence`;既有 Source stale/revision cases | Duplicate、identity conflict、revision;既有 stale 路径保留。满额下 revision 不占槽由同一 Store 分支处理 |
|
||||||
|
| T09 | `Source.RasterArrayV4HostApiRejectsKeepPublicProjectionStable`、`...RejectsNumericOverflowBeforeReplacingAcceptedRevision`、`ParticleFieldRuntime.RejectedFullWindowInsertDoesNotEvictAndKeysRemainIndependent` | 解码/profile/数值和 grid schema 拒绝路径、有效帧/投影/clock 零副作用。数据帧入 Store 前仍由现有 ingress 完成解码与 profile 校验 |
|
||||||
|
| T10 | `ParticleFieldBinding.MixChangesSampleStateButNotContentIdentityOrStorage`、单帧升级 Source 测试 | 合法 bracket 的中间比例;精确端点以单帧语义表示 |
|
||||||
|
| T11 | 既有 binding `WindowUnavailable` assertions;新单帧测试 | 非覆盖时间 unavailable,不用最近帧顶替 |
|
||||||
|
| T12 | `Source.RasterArrayV4RollingWindowPreservesAnUnchangedActivePair` | 外围补帧/双向滚动不改变当前物化 projection 与 requested time |
|
||||||
|
| T13 | `Source.RasterArrayV4EvictedActiveFrameClearsItsMaterializedProjection` | 当前单帧端点淘汰后 fail-closed 清除物化 projection |
|
||||||
|
| T14 | `ParticleFieldRuntime.RejectedFullWindowInsertDoesNotEvictAndKeysRemainIndependent` | 两个 key 各自 8 帧,独立计数与字节 |
|
||||||
|
| T15 | 单帧 binding/materialization 测试;GL/Metal renderer 资源安装源码 | T0/T1 共用物化 snapshot;renderer 首装共用 endpoint texture/image。GPU/context-loss 运行验证未覆盖 |
|
||||||
|
| T16 | `ParticleFieldRuntime.RepeatedBidirectionalRollingAndClearStayBounded` | 120 次双向提交、每 key 8 帧边界、clear/clearAll 和 decoded bytes 归零 |
|
||||||
|
| T17 | 现有 `FieldRuntime.ts`、`NativeWeatherRuntimeBridge.ts`、Android/iOS bridge 源码静态核对 | 旧 API、类型、方法名和两步语义未变;RN 自动化/双平台 consumer build 未运行 |
|
||||||
|
|
||||||
|
所有测试代码的执行状态均为 **NOT RUN**。完整执行状态与 RDC 命令见 STEP-098 完成报告。
|
||||||
File diff suppressed because it is too large
Load Diff
754
docs/40_任务/10_架构调查指令书_v1.md
Normal file
754
docs/40_任务/10_架构调查指令书_v1.md
Normal file
@@ -0,0 +1,754 @@
|
|||||||
|
# MapLibreParticleLab 10_架构调查指令书 v1
|
||||||
|
|
||||||
|
## 1. 任务基本信息
|
||||||
|
|
||||||
|
- 项目:MapLibreParticleLab
|
||||||
|
- 大阶段:`10_架构调查`
|
||||||
|
- 当前基线分支:`particle/main`
|
||||||
|
- 当前前置状态:总则 v1 已提交并推送
|
||||||
|
- 任务性质:只读架构调查
|
||||||
|
- 是否允许修改业务代码:否
|
||||||
|
- 是否允许升级或安装依赖:否
|
||||||
|
- 是否允许顺手修复发现的问题:否
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 任务目标
|
||||||
|
|
||||||
|
对 MapLibreParticleLab 当前真实代码、依赖、原生工程、MapLibre 接入方式和渲染架构进行一次完整调查。
|
||||||
|
|
||||||
|
本阶段的目的不是设计最终粒子系统,而是回答:
|
||||||
|
|
||||||
|
> 当前项目实际上是怎样运行的,MapLibre 实际经过哪些层完成地图渲染,未来粒子系统有哪些真实可用的接入位置,以及当前仍有哪些关键问题无法仅通过现有代码确认。
|
||||||
|
|
||||||
|
最终必须形成一份基于实际仓库源码和配置的中文架构调查报告,为下一阶段 `20_架构设计` 提供事实基础。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 核心原则
|
||||||
|
|
||||||
|
### 3.1 以实际代码为唯一事实来源
|
||||||
|
|
||||||
|
不得根据:
|
||||||
|
|
||||||
|
- 记忆
|
||||||
|
- MapLibre 一般架构
|
||||||
|
- React Native 常见做法
|
||||||
|
- 网上其他项目的实现
|
||||||
|
- 对新版 MapLibre 的印象
|
||||||
|
|
||||||
|
推断当前项目架构。
|
||||||
|
|
||||||
|
所有结论必须优先来自当前 `particle/main` 实际源码、配置、依赖锁定文件和原生工程。
|
||||||
|
|
||||||
|
如果某项无法通过仓库确认,必须明确标记为:
|
||||||
|
|
||||||
|
`未确认`
|
||||||
|
|
||||||
|
不得自行补全。
|
||||||
|
|
||||||
|
### 3.2 本任务只调查,不设计
|
||||||
|
|
||||||
|
本任务不得直接决定:
|
||||||
|
|
||||||
|
- 最终粒子渲染技术路线
|
||||||
|
- CPU 粒子还是 GPU 粒子
|
||||||
|
- CustomLayer 是否作为最终方案
|
||||||
|
- 是否修改 MapLibre Native
|
||||||
|
- 是否自行创建原生 Renderer
|
||||||
|
- 是否使用 Metal / OpenGL / Vulkan
|
||||||
|
- 最终数据纹理格式
|
||||||
|
- 最终 Shader 架构
|
||||||
|
- 最终线程模型
|
||||||
|
|
||||||
|
可以列出候选扩展位置和现有限制,但不得提前作最终技术选型。
|
||||||
|
|
||||||
|
### 3.3 不预设多个调查任务
|
||||||
|
|
||||||
|
本次应尽可能一次完成整个 `10_架构调查`。
|
||||||
|
|
||||||
|
只有在调查后发现某个关键问题同时满足以下条件时,才建议追加新的调查任务:
|
||||||
|
|
||||||
|
1. 无法依靠当前项目源码确认;
|
||||||
|
2. 该问题会直接影响下一阶段技术路线;
|
||||||
|
3. 需要额外读取第三方源码、编译验证、最小实验或平台专项验证。
|
||||||
|
|
||||||
|
不得为了形式完整主动拆出大量 `10-01 / 10-02 / 10-03` 子任务。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 开始前检查
|
||||||
|
|
||||||
|
开始调查后先确认以下信息:
|
||||||
|
|
||||||
|
- [ ] 当前仓库确实为 MapLibreParticleLab
|
||||||
|
- [ ] 当前分支为 `particle/main`
|
||||||
|
- [ ] 读取当前 Git 状态
|
||||||
|
- [ ] 确认工作区是否干净
|
||||||
|
- [ ] 找到并完整阅读当前“总则 v1”
|
||||||
|
- [ ] 确认项目根目录结构
|
||||||
|
- [ ] 确认主要源码目录
|
||||||
|
- [ ] 确认 Android / iOS 原生目录是否存在
|
||||||
|
- [ ] 确认依赖清单及 lock 文件
|
||||||
|
|
||||||
|
如果当前分支或代码状态与上述基线明显不符:
|
||||||
|
|
||||||
|
不要修改仓库状态。
|
||||||
|
|
||||||
|
继续进行能够完成的只读调查,并在报告中明确记录实际状态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 必做调查内容
|
||||||
|
|
||||||
|
### 5.1 项目整体架构
|
||||||
|
|
||||||
|
调查当前项目的真实目录与模块组织。
|
||||||
|
|
||||||
|
至少确认:
|
||||||
|
|
||||||
|
- 项目入口
|
||||||
|
- React Native / Expo / Bare RN 等实际工程形态
|
||||||
|
- TypeScript / JavaScript 使用情况
|
||||||
|
- Android 工程
|
||||||
|
- iOS 工程
|
||||||
|
- 公共源码目录
|
||||||
|
- 地图相关源码目录
|
||||||
|
- 实验代码目录
|
||||||
|
- 数据处理目录
|
||||||
|
- Shader 或图形相关资源目录
|
||||||
|
- assets
|
||||||
|
- scripts
|
||||||
|
- docs
|
||||||
|
- 测试目录
|
||||||
|
- 构建配置
|
||||||
|
- Metro 配置
|
||||||
|
- Babel 配置
|
||||||
|
- TypeScript 配置
|
||||||
|
|
||||||
|
报告中需要给出简化的当前项目结构图。
|
||||||
|
|
||||||
|
只列架构相关目录,不需要机械复制整个文件树。
|
||||||
|
|
||||||
|
### 5.2 当前依赖与版本
|
||||||
|
|
||||||
|
读取实际:
|
||||||
|
|
||||||
|
- `package.json`
|
||||||
|
- lock 文件
|
||||||
|
- Android Gradle 相关配置
|
||||||
|
- iOS Pod 相关配置
|
||||||
|
- 与地图和图形渲染相关的依赖配置
|
||||||
|
|
||||||
|
重点确认:
|
||||||
|
|
||||||
|
- React Native 版本
|
||||||
|
- React 版本
|
||||||
|
- MapLibre React Native 包及实际版本
|
||||||
|
- MapLibre Android / iOS Native 版本,如能够从当前工程确认
|
||||||
|
- Skia 或其他图形库是否存在
|
||||||
|
- Reanimated 等与渲染相关的辅助依赖
|
||||||
|
- OpenGL / Metal / WebGL / GPU 相关第三方库
|
||||||
|
- Native Module / TurboModule / Fabric 相关配置
|
||||||
|
|
||||||
|
必须区分:
|
||||||
|
|
||||||
|
1. 直接依赖
|
||||||
|
2. 间接依赖
|
||||||
|
3. 原生 MapLibre 底层实际版本
|
||||||
|
|
||||||
|
如果底层版本无法确认,明确写“未确认”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. MapLibre 当前接入链调查
|
||||||
|
|
||||||
|
这是本任务重点。
|
||||||
|
|
||||||
|
必须从项目实际地图页面或地图 Host 开始向下追踪。
|
||||||
|
|
||||||
|
调查:
|
||||||
|
|
||||||
|
`业务/页面`
|
||||||
|
↓
|
||||||
|
`React Native Map 组件`
|
||||||
|
↓
|
||||||
|
`MapLibre React Native 封装`
|
||||||
|
↓
|
||||||
|
`Android / iOS Native View`
|
||||||
|
↓
|
||||||
|
`MapLibre Native`
|
||||||
|
|
||||||
|
需要确认当前代码实际使用的:
|
||||||
|
|
||||||
|
- MapView
|
||||||
|
- Camera
|
||||||
|
- Style
|
||||||
|
- Source
|
||||||
|
- Layer
|
||||||
|
- ShapeSource
|
||||||
|
- RasterSource
|
||||||
|
- ImageSource
|
||||||
|
- CustomLayer,如存在
|
||||||
|
- 地图事件
|
||||||
|
- 地图生命周期事件
|
||||||
|
- onPress
|
||||||
|
- camera change
|
||||||
|
- style load
|
||||||
|
- render/frame 相关事件
|
||||||
|
|
||||||
|
报告中必须给出一张文字形式的实际调用关系图。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
`Screen → MapHost → MapView → RN MapLibre Binding → Native Map View`
|
||||||
|
|
||||||
|
但最终结构必须根据当前代码得出,不得照抄该例子。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 地图生命周期调查
|
||||||
|
|
||||||
|
调查当前代码能够观察或控制哪些生命周期阶段。
|
||||||
|
|
||||||
|
至少关注:
|
||||||
|
|
||||||
|
- 地图组件创建
|
||||||
|
- Native Map 创建
|
||||||
|
- Style 加载开始
|
||||||
|
- Style Ready
|
||||||
|
- 地图 Ready
|
||||||
|
- Camera 变化
|
||||||
|
- 地图移动
|
||||||
|
- 地图缩放
|
||||||
|
- 地图旋转
|
||||||
|
- 每帧刷新能力
|
||||||
|
- App 前后台
|
||||||
|
- MapView unmount
|
||||||
|
- Native 资源释放
|
||||||
|
|
||||||
|
明确:
|
||||||
|
|
||||||
|
### 当前 TS/RN 层已经直接获得哪些生命周期事件
|
||||||
|
|
||||||
|
以及:
|
||||||
|
|
||||||
|
### 哪些生命周期只存在 Native 或 MapLibre 内部,目前 JS 层拿不到
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 自定义渲染扩展点调查
|
||||||
|
|
||||||
|
调查当前工程和当前依赖版本实际存在的自定义渲染能力。
|
||||||
|
|
||||||
|
重点寻找:
|
||||||
|
|
||||||
|
- CustomLayer
|
||||||
|
- CustomLayerHost
|
||||||
|
- Native Layer
|
||||||
|
- Render Layer
|
||||||
|
- MapLibre 自定义 layer 接口
|
||||||
|
- 自定义 View Overlay
|
||||||
|
- Surface
|
||||||
|
- Texture
|
||||||
|
- GL Context
|
||||||
|
- Metal Context
|
||||||
|
- Renderer hook
|
||||||
|
- Render pass
|
||||||
|
- Native callback
|
||||||
|
- Frame callback
|
||||||
|
|
||||||
|
每个发现必须分类。
|
||||||
|
|
||||||
|
### A. 当前项目已经直接使用
|
||||||
|
|
||||||
|
### B. 当前依赖已经暴露,但项目尚未使用
|
||||||
|
|
||||||
|
### C. MapLibre Native 可能支持,但 RN 封装当前未暴露
|
||||||
|
|
||||||
|
### D. 当前代码无法确认
|
||||||
|
|
||||||
|
不要把 B/C/D 写成“当前已经支持”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Android 渲染架构调查
|
||||||
|
|
||||||
|
单独调查 Android。
|
||||||
|
|
||||||
|
至少确认:
|
||||||
|
|
||||||
|
- MapLibre Android Native 如何被当前 RN 包引入
|
||||||
|
- MapView 对应原生 View
|
||||||
|
- renderer 所处代码边界
|
||||||
|
- 当前工程是否显式涉及 OpenGL
|
||||||
|
- 是否能够找到 render thread 相关实现或接口
|
||||||
|
- 地图帧刷新由谁驱动
|
||||||
|
- Camera / transform 数据当前在哪一层
|
||||||
|
- Native Module 是否能够接触 MapView 实例
|
||||||
|
- 是否存在可插入自定义渲染的明确入口
|
||||||
|
- JS 与 Native 的当前桥接方式
|
||||||
|
|
||||||
|
如果当前项目使用 New Architecture,也需要说明其实际启用状态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. iOS 渲染架构调查
|
||||||
|
|
||||||
|
Android 与 iOS 不得合并推断。
|
||||||
|
|
||||||
|
单独确认:
|
||||||
|
|
||||||
|
- iOS MapLibre Native 依赖
|
||||||
|
- MapView 原生类型
|
||||||
|
- Metal / OpenGL 实际情况
|
||||||
|
- renderer 生命周期
|
||||||
|
- Camera / transform 数据
|
||||||
|
- RN 到 Native 的连接方式
|
||||||
|
- 是否能够确认自定义渲染接口
|
||||||
|
- 当前 Native 层能够访问的 MapLibre 对象范围
|
||||||
|
|
||||||
|
如果 Android 与 iOS 能力不一致,必须明确列出。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. JS / UI / Native / Render / GPU 边界
|
||||||
|
|
||||||
|
基于当前工程实际情况,整理线程和执行层级。
|
||||||
|
|
||||||
|
至少区分:
|
||||||
|
|
||||||
|
- JavaScript Thread
|
||||||
|
- React Native UI/Main Thread
|
||||||
|
- Android/iOS Native 层
|
||||||
|
- MapLibre 内部 Worker
|
||||||
|
- MapLibre Render Thread
|
||||||
|
- GPU
|
||||||
|
|
||||||
|
如果某个平台实际名称有所不同,使用项目和 MapLibre 当前版本对应的真实名称。
|
||||||
|
|
||||||
|
重点调查:
|
||||||
|
|
||||||
|
- 当前高频数据从 JS 传 Native 会经过什么链路
|
||||||
|
- 地图 Camera 高频变化如何传播
|
||||||
|
- 是否存在 JS 每帧回调
|
||||||
|
- 是否存在 Native 每帧回调
|
||||||
|
- 是否存在直接 GPU 资源更新的位置
|
||||||
|
- 哪些数据如果每帧跨 JS Bridge 会形成明显风险
|
||||||
|
|
||||||
|
这里只描述架构事实和风险,不提前设计最终优化方案。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Camera 与坐标变换能力
|
||||||
|
|
||||||
|
粒子系统未来必须与地图同步,因此重点调查当前代码能够获得哪些:
|
||||||
|
|
||||||
|
- center
|
||||||
|
- zoom
|
||||||
|
- bearing
|
||||||
|
- pitch
|
||||||
|
- bounds
|
||||||
|
- viewport size
|
||||||
|
- screen coordinate
|
||||||
|
- geographic coordinate
|
||||||
|
- projection / unprojection
|
||||||
|
- camera matrix
|
||||||
|
- projection matrix
|
||||||
|
- view matrix
|
||||||
|
- transform matrix
|
||||||
|
|
||||||
|
区分:
|
||||||
|
|
||||||
|
1. JS API 可以直接获取
|
||||||
|
2. Native API 可以获取
|
||||||
|
3. MapLibre 内部存在但当前封装未暴露
|
||||||
|
4. 无法确认
|
||||||
|
|
||||||
|
尤其要明确:
|
||||||
|
|
||||||
|
> 当前是否可以获得足够信息,使自定义粒子渲染与 MapLibre 地图的平移、缩放、旋转、pitch 保持严格同步。
|
||||||
|
|
||||||
|
如果不能确认,不得猜测。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 帧驱动能力
|
||||||
|
|
||||||
|
调查未来粒子动画最关键的问题:
|
||||||
|
|
||||||
|
> 谁负责下一帧?
|
||||||
|
|
||||||
|
确认当前可利用的:
|
||||||
|
|
||||||
|
- requestAnimationFrame
|
||||||
|
- RN animation frame
|
||||||
|
- Reanimated frame callback
|
||||||
|
- Native Choreographer / DisplayLink
|
||||||
|
- MapLibre render loop
|
||||||
|
- CustomLayer render callback
|
||||||
|
- GPU render callback
|
||||||
|
|
||||||
|
需要说明:
|
||||||
|
|
||||||
|
- 哪些帧驱动来自地图 Renderer 本身
|
||||||
|
- 哪些与地图 Renderer 独立
|
||||||
|
- 如果使用独立动画循环,理论上可能出现什么同步问题
|
||||||
|
|
||||||
|
这里只进行架构分析。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. 当前数据与实验代码调查
|
||||||
|
|
||||||
|
搜索当前仓库内已经存在的:
|
||||||
|
|
||||||
|
- particle
|
||||||
|
- wind
|
||||||
|
- current
|
||||||
|
- wave
|
||||||
|
- grid
|
||||||
|
- vector field
|
||||||
|
- raster
|
||||||
|
- interpolation
|
||||||
|
- texture
|
||||||
|
- shader
|
||||||
|
- velocity
|
||||||
|
- animation
|
||||||
|
- GPU
|
||||||
|
- GL
|
||||||
|
- Metal
|
||||||
|
- MapLibre custom rendering
|
||||||
|
|
||||||
|
以及相关实验代码。
|
||||||
|
|
||||||
|
对发现的代码进行分类:
|
||||||
|
|
||||||
|
- 可复用基础代码
|
||||||
|
- 仅实验用途
|
||||||
|
- 与最终架构可能无关
|
||||||
|
- 已弃用
|
||||||
|
- 状态不明确
|
||||||
|
|
||||||
|
不得因为文件名相似就判定“可复用”。
|
||||||
|
|
||||||
|
需要查看真实实现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. 海洋/气象格点数据未来可能经过的现有层
|
||||||
|
|
||||||
|
本任务不设计数据系统,只调查当前项目有哪些已经存在的基础设施。
|
||||||
|
|
||||||
|
关注未来可能涉及:
|
||||||
|
|
||||||
|
`原始格点数据`
|
||||||
|
→ `解析`
|
||||||
|
→ `内存结构`
|
||||||
|
→ `插值`
|
||||||
|
→ `渲染资源`
|
||||||
|
→ `地图显示`
|
||||||
|
|
||||||
|
调查当前是否已经存在:
|
||||||
|
|
||||||
|
- 数据 Fetch 层
|
||||||
|
- ArrayBuffer
|
||||||
|
- TypedArray
|
||||||
|
- 二进制解码
|
||||||
|
- Grid 数据模型
|
||||||
|
- 经纬度格点模型
|
||||||
|
- 缓存
|
||||||
|
- Worker
|
||||||
|
- Native Buffer
|
||||||
|
- Texture Upload
|
||||||
|
- Shader Uniform
|
||||||
|
- 数据生命周期管理
|
||||||
|
|
||||||
|
报告只描述现状。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. 性能风险调查
|
||||||
|
|
||||||
|
不得运行大型性能优化工作。
|
||||||
|
|
||||||
|
仅根据真实架构识别潜在风险。
|
||||||
|
|
||||||
|
至少关注:
|
||||||
|
|
||||||
|
- JS 高频循环
|
||||||
|
- 每帧 JS → Native 调用
|
||||||
|
- 大数组 Bridge 传输
|
||||||
|
- JSON 序列化
|
||||||
|
- ArrayBuffer 复制
|
||||||
|
- Native → GPU 重复复制
|
||||||
|
- Texture 高频重建
|
||||||
|
- 地图 Camera 变化导致的大规模重新计算
|
||||||
|
- React re-render
|
||||||
|
- MapView Overlay 与地图不同步
|
||||||
|
- Android/iOS 实现分叉
|
||||||
|
- GPU context 共享风险
|
||||||
|
- 地图销毁后的资源泄漏
|
||||||
|
|
||||||
|
风险需要标记等级:
|
||||||
|
|
||||||
|
- 高
|
||||||
|
- 中
|
||||||
|
- 低
|
||||||
|
- 尚无法判断
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. 当前架构图
|
||||||
|
|
||||||
|
最终调查报告必须至少包含以下几个简化架构图。
|
||||||
|
|
||||||
|
全部使用 Markdown 文本图即可。
|
||||||
|
|
||||||
|
### 17.1 当前 App / MapLibre 总体调用图
|
||||||
|
|
||||||
|
### 17.2 JS → Native → MapLibre 调用链
|
||||||
|
|
||||||
|
### 17.3 Android 地图渲染相关层级
|
||||||
|
|
||||||
|
### 17.4 iOS 地图渲染相关层级
|
||||||
|
|
||||||
|
### 17.5 当前能够确认的数据与渲染边界
|
||||||
|
|
||||||
|
不得为了图看起来完整而补画未经确认的组件。
|
||||||
|
|
||||||
|
未知位置使用:
|
||||||
|
|
||||||
|
`[未确认]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18. 关键结论分类
|
||||||
|
|
||||||
|
调查报告结尾必须将全部关键结论整理成四类。
|
||||||
|
|
||||||
|
### 18.1 已确认事实
|
||||||
|
|
||||||
|
只能放已有源码或配置证据的事实。
|
||||||
|
|
||||||
|
### 18.2 当前限制
|
||||||
|
|
||||||
|
当前架构真实存在的限制。
|
||||||
|
|
||||||
|
### 18.3 候选扩展点
|
||||||
|
|
||||||
|
当前看来可能承载未来粒子系统的位置。
|
||||||
|
|
||||||
|
这里只列候选,不做最终选择。
|
||||||
|
|
||||||
|
### 18.4 未确认问题
|
||||||
|
|
||||||
|
列出不能通过本次仓库静态调查确认,但会影响后续架构设计的问题。
|
||||||
|
|
||||||
|
每个“未确认问题”注明:
|
||||||
|
|
||||||
|
- 为什么当前无法确认
|
||||||
|
- 是否会阻塞 `20_架构设计`
|
||||||
|
- 如果需要继续调查,建议采用什么最小方式确认
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19. 是否需要追加调查任务的判断
|
||||||
|
|
||||||
|
本次调查结束时必须明确给出:
|
||||||
|
|
||||||
|
### A. 可以直接进入 `20_架构设计`
|
||||||
|
|
||||||
|
或:
|
||||||
|
|
||||||
|
### B. 必须追加一个新的 `10_架构调查` 子任务
|
||||||
|
|
||||||
|
只有关键技术事实无法确认并且会直接影响架构选择时才能选择 B。
|
||||||
|
|
||||||
|
如果选择 B,只提出真正需要的最少调查任务。
|
||||||
|
|
||||||
|
不得为了保险而把所有未知问题拆成新任务。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 20. 禁止事项
|
||||||
|
|
||||||
|
本任务禁止:
|
||||||
|
|
||||||
|
- 修改生产源码
|
||||||
|
- 修改实验源码
|
||||||
|
- 新建粒子实现
|
||||||
|
- 修改 MapLibre 接入
|
||||||
|
- 升级 MapLibre
|
||||||
|
- 升级 React Native
|
||||||
|
- 安装任何新 npm 包
|
||||||
|
- 安装任何新 Pod
|
||||||
|
- 修改 Gradle 依赖
|
||||||
|
- 修改 Podfile
|
||||||
|
- 修改 Metro
|
||||||
|
- 修改 Babel
|
||||||
|
- 修改 TypeScript 配置
|
||||||
|
- 修改 Android 原生代码
|
||||||
|
- 修改 iOS 原生代码
|
||||||
|
- 创建 Shader 实现
|
||||||
|
- 创建 CustomLayer 实现
|
||||||
|
- 创建 Native Module
|
||||||
|
- 进行架构重构
|
||||||
|
- 顺手修复发现的问题
|
||||||
|
|
||||||
|
发现问题只记录。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 21. 允许执行的验证
|
||||||
|
|
||||||
|
允许执行不会改变项目状态的只读命令,例如:
|
||||||
|
|
||||||
|
- Git 状态查询
|
||||||
|
- Git 分支查询
|
||||||
|
- 文件搜索
|
||||||
|
- 文本搜索
|
||||||
|
- 查看 package 配置
|
||||||
|
- 查看 Gradle 配置
|
||||||
|
- 查看 Pod 配置
|
||||||
|
- 查看源码
|
||||||
|
- 查看当前项目已有脚本定义
|
||||||
|
|
||||||
|
如果某个命令可能:
|
||||||
|
|
||||||
|
- 自动安装依赖
|
||||||
|
- 修改 lock 文件
|
||||||
|
- 修改构建产物
|
||||||
|
- 修改 Native 工程
|
||||||
|
- 写入项目配置
|
||||||
|
|
||||||
|
则不要执行。
|
||||||
|
|
||||||
|
本任务主要依赖静态调查。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 22. 必做输出
|
||||||
|
|
||||||
|
### 22.1 架构调查成果
|
||||||
|
|
||||||
|
必须生成中文调查报告:
|
||||||
|
|
||||||
|
`docs/20_调查/10_架构调查报告_v1.md`
|
||||||
|
|
||||||
|
如果同名文件已经存在,不得覆盖,应递增版本号。
|
||||||
|
|
||||||
|
该报告是后续 `20_架构设计` 的主要输入。
|
||||||
|
|
||||||
|
### 22.2 Codex 完成报告
|
||||||
|
|
||||||
|
必须生成中文执行完成报告:
|
||||||
|
|
||||||
|
`docs/报告/10_架构调查_Codex完成报告_v1.md`
|
||||||
|
|
||||||
|
如果 `docs/报告/` 不存在,则创建该目录。
|
||||||
|
|
||||||
|
如果同名报告已经存在,不得覆盖旧报告,应递增版本号。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 23. 架构调查报告必须包含
|
||||||
|
|
||||||
|
`docs/20_调查/10_架构调查报告_v1.md` 至少包含:
|
||||||
|
|
||||||
|
1. 调查环境
|
||||||
|
2. Git / 分支 / 工作区状态
|
||||||
|
3. 总则 v1 读取情况
|
||||||
|
4. 当前项目整体架构
|
||||||
|
5. 核心依赖与版本
|
||||||
|
6. MapLibre 接入链
|
||||||
|
7. MapLibre 生命周期
|
||||||
|
8. 自定义渲染能力
|
||||||
|
9. Android 渲染架构
|
||||||
|
10. iOS 渲染架构
|
||||||
|
11. JS / Native / Render / GPU 边界
|
||||||
|
12. Camera / Matrix / 坐标转换能力
|
||||||
|
13. 帧驱动能力
|
||||||
|
14. 当前数据与粒子相关实验代码
|
||||||
|
15. 当前可复用基础设施
|
||||||
|
16. 性能与线程风险
|
||||||
|
17. 当前架构图
|
||||||
|
18. 已确认事实
|
||||||
|
19. 当前限制
|
||||||
|
20. 候选扩展点
|
||||||
|
21. 未确认问题
|
||||||
|
22. 是否需要追加调查任务
|
||||||
|
23. 是否已经具备进入 `20_架构设计` 的条件
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 24. Codex 完成报告必须包含
|
||||||
|
|
||||||
|
`docs/报告/10_架构调查_Codex完成报告_v1.md` 至少包含:
|
||||||
|
|
||||||
|
1. 本次实际调查范围
|
||||||
|
2. 必做项逐项状态:`已完成 / 未完成 / 无法验证`
|
||||||
|
3. 未完成或无法验证的原因
|
||||||
|
4. 实际生成或修改的文档清单
|
||||||
|
5. 是否修改生产代码
|
||||||
|
6. 是否安装或升级依赖
|
||||||
|
7. 是否存在需要用户关注的阻塞项
|
||||||
|
8. 下一步建议
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 25. 必做项完成检查
|
||||||
|
|
||||||
|
完成前逐项检查:
|
||||||
|
|
||||||
|
- [ ] 已读取总则 v1
|
||||||
|
- [ ] 已确认当前实际 Git 基线
|
||||||
|
- [ ] 已调查整体目录和工程形态
|
||||||
|
- [ ] 已确认核心依赖版本
|
||||||
|
- [ ] 已追踪 MapLibre 实际调用链
|
||||||
|
- [ ] 已调查地图生命周期
|
||||||
|
- [ ] 已调查自定义渲染扩展点
|
||||||
|
- [ ] 已分别调查 Android
|
||||||
|
- [ ] 已分别调查 iOS
|
||||||
|
- [ ] 已调查线程与执行边界
|
||||||
|
- [ ] 已调查 Camera / 坐标 / Matrix 能力
|
||||||
|
- [ ] 已调查帧驱动能力
|
||||||
|
- [ ] 已搜索现有粒子/场数据/Shader 实验代码
|
||||||
|
- [ ] 已调查当前可复用的数据基础设施
|
||||||
|
- [ ] 已整理性能风险
|
||||||
|
- [ ] 已绘制必要架构图
|
||||||
|
- [ ] 已区分事实、限制、候选和未知项
|
||||||
|
- [ ] 已判断是否需要追加调查
|
||||||
|
- [ ] 已判断能否进入 `20_架构设计`
|
||||||
|
- [ ] 未修改生产代码
|
||||||
|
- [ ] 未升级或安装依赖
|
||||||
|
- [ ] 已生成 `docs/20_调查/` 下的正式调查报告
|
||||||
|
- [ ] 已生成 `docs/报告/` 下的 Codex 完成报告
|
||||||
|
|
||||||
|
不得静默跳过任何必做项。
|
||||||
|
|
||||||
|
无法完成的项目必须明确写:
|
||||||
|
|
||||||
|
`未完成`
|
||||||
|
|
||||||
|
或:
|
||||||
|
|
||||||
|
`无法验证`
|
||||||
|
|
||||||
|
并说明原因。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 26. 完成标准
|
||||||
|
|
||||||
|
本任务完成的标准不是“找到一个粒子方案”。
|
||||||
|
|
||||||
|
真正完成标准是:
|
||||||
|
|
||||||
|
> 已经能够基于 MapLibreParticleLab 当前真实代码,准确描述项目、React Native、Native、MapLibre 和 GPU 之间的现有边界,并明确下一阶段进行粒子系统架构设计时,哪些能力已经确认、哪些扩展位置值得考虑、哪些关键事实仍需要额外验证。
|
||||||
|
|
||||||
|
达到这个状态后,`10_架构调查` 才算完成。
|
||||||
|
|
||||||
|
除非存在真正阻塞架构选择的未知项,否则下一步直接进入:
|
||||||
|
|
||||||
|
`20_架构设计`
|
||||||
1092
docs/40_任务/20_架构设计指令书_v1.md
Normal file
1092
docs/40_任务/20_架构设计指令书_v1.md
Normal file
File diff suppressed because it is too large
Load Diff
731
docs/40_任务/30_01_CoreRenderLayer接入验证指令书_v1.md
Normal file
731
docs/40_任务/30_01_CoreRenderLayer接入验证指令书_v1.md
Normal file
@@ -0,0 +1,731 @@
|
|||||||
|
# MapLibreParticleLab 30_最小技术验证-01_Core RenderLayer 接入验证指令书 v1
|
||||||
|
|
||||||
|
## 1. 任务基本信息
|
||||||
|
|
||||||
|
- 项目:MapLibreParticleLab
|
||||||
|
- 阶段:`30_最小技术验证`
|
||||||
|
- 子任务:`30-01 Core RenderLayer 接入验证`
|
||||||
|
- 当前主线:`particle/main`
|
||||||
|
- 前置阶段:
|
||||||
|
- `10_架构调查` 已完成
|
||||||
|
- `20_架构设计` 已完成
|
||||||
|
- 前置正式架构文档:
|
||||||
|
- `docs/10_架构/20_粒子系统总体架构设计_v1.md`
|
||||||
|
- 任务性质:最小运行时技术验证
|
||||||
|
- 本任务不是正式粒子功能实现
|
||||||
|
- 本任务允许最少量验证代码,但必须保持隔离、可识别、可移除
|
||||||
|
- 不允许顺手重构 MapLibre
|
||||||
|
- 不允许扩大到 GPU 粒子、真实场数据、Trail 或 Land/Ocean Mask 实现
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 为什么本阶段先只做 30-01
|
||||||
|
|
||||||
|
当前总体架构已经选择独立 Core 粒子 Layer 作为正式主路线。
|
||||||
|
|
||||||
|
本阶段后续原计划还需要验证:
|
||||||
|
|
||||||
|
1. Core RenderLayer / Style order / 同帧 transform / repaint
|
||||||
|
2. GPU State A/B + Trail A/B ping-pong 与 context recreation
|
||||||
|
3. Camera / world wrap / Land-Ocean Mask
|
||||||
|
4. iOS Core Layer 薄接入
|
||||||
|
|
||||||
|
但第 1 项属于前置门槛。
|
||||||
|
|
||||||
|
如果第 1 项失败,说明当前正式主路线可能需要调整,后续 GPU、Mask 和平台验证的代码位置、生命周期和验证方式都会变化。
|
||||||
|
|
||||||
|
因此本任务只完成 `30-01`。
|
||||||
|
|
||||||
|
只有 `30-01` 得出 PASS 后,才进入下一项。
|
||||||
|
|
||||||
|
不得提前为后续验证大量写代码。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 必须先读取
|
||||||
|
|
||||||
|
Codex 开始前必须完整读取:
|
||||||
|
|
||||||
|
1. 根目录 `AGENTS.md`
|
||||||
|
2. `PARTICLE_BASELINE.md`
|
||||||
|
3. `docs/00_项目总则/` 下当前全部有效文档
|
||||||
|
4. `docs/00_项目规范/` 下当前全部有效文档
|
||||||
|
5. `docs/20_调查/10_架构调查报告_v1.md`
|
||||||
|
6. `docs/10_架构/20_粒子系统总体架构设计_v1.md`
|
||||||
|
7. `docs/报告/10_架构调查_Codex完成报告_v1.md`
|
||||||
|
8. `docs/报告/20_架构设计_Codex完成报告_v1.md`
|
||||||
|
9. `粒子开发记录.md`
|
||||||
|
10. 与下列现有实现直接相关的当前源码:
|
||||||
|
- Style Layer 注册链
|
||||||
|
- RenderLayer / RenderTree / RenderOrchestrator
|
||||||
|
- RasterLayer 或其他最接近的现有 Layer 示例
|
||||||
|
- CustomLayer / CustomDrawableLayer 示例
|
||||||
|
- Android MapRenderer / AndroidRendererFrontend
|
||||||
|
- `triggerRepaint` / `requestRender`
|
||||||
|
- `TransformState` / `TransformParameters` / `PaintParameters`
|
||||||
|
|
||||||
|
不得凭记忆或 MapLibre 通用知识直接开写。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 开始前 Git 检查
|
||||||
|
|
||||||
|
先执行只读检查并记录:
|
||||||
|
|
||||||
|
- 当前分支
|
||||||
|
- 当前 HEAD
|
||||||
|
- `git status`
|
||||||
|
- `origin` / 当前公开远端
|
||||||
|
- `particle/main` 是否与远端同步
|
||||||
|
|
||||||
|
如果存在与本任务无关的本地修改:
|
||||||
|
|
||||||
|
- 不得删除
|
||||||
|
- 不得覆盖
|
||||||
|
- 不得顺带提交
|
||||||
|
- 在完成报告中记录
|
||||||
|
- 本任务提交时只 stage 本任务相关文件
|
||||||
|
|
||||||
|
如果当前不是 `particle/main`,不要擅自切换覆盖用户工作。
|
||||||
|
|
||||||
|
先明确报告实际状态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 验证目标
|
||||||
|
|
||||||
|
本任务只回答下面四个问题。
|
||||||
|
|
||||||
|
### V1. 独立 Core Layer 能否进入真实 MapLibre Style / RenderTree 顺序
|
||||||
|
|
||||||
|
必须验证一个最小实验 Layer:
|
||||||
|
|
||||||
|
- 由 Core Style/Layer 系统识别
|
||||||
|
- 能生成对应 RenderLayer
|
||||||
|
- 能进入真实 RenderTree
|
||||||
|
- 能按 Style Layer 顺序参与渲染
|
||||||
|
- 不依赖 MapView 外部 Overlay
|
||||||
|
- 不依赖 JS/RN 每帧逻辑
|
||||||
|
- 不把实现塞进现有 RasterLayer
|
||||||
|
|
||||||
|
### V2. 该 Layer 能否在同一 MapLibre render frame 获得当前 Transform
|
||||||
|
|
||||||
|
至少验证能够在真正绘制路径中读取或使用:
|
||||||
|
|
||||||
|
- viewport size
|
||||||
|
- zoom
|
||||||
|
- bearing
|
||||||
|
- pitch
|
||||||
|
- projection / transform matrix 中架构要求的必要部分
|
||||||
|
|
||||||
|
验证重点不是把数值打印出来,而是确认这些值来自:
|
||||||
|
|
||||||
|
> 当前 MapLibre render frame 的真实 `PaintParameters / TransformParameters / TransformState` 链
|
||||||
|
|
||||||
|
而不是另建独立 Camera 状态。
|
||||||
|
|
||||||
|
### V3. Layer 活动时能否驱动持续 repaint
|
||||||
|
|
||||||
|
验证:
|
||||||
|
|
||||||
|
- Layer 处于“动画活动”状态时,可以通过 MapLibre 自己的 render loop 持续获得下一帧
|
||||||
|
- 不建立独立于 MapLibre 的第二套动画循环
|
||||||
|
- 不依赖 Android UI Timer
|
||||||
|
- 不依赖 JS `requestAnimationFrame`
|
||||||
|
|
||||||
|
### V4. Layer 停止活动后能否停止额外 repaint
|
||||||
|
|
||||||
|
验证:
|
||||||
|
|
||||||
|
- Layer 切换为静止/disabled 后,不继续无意义请求下一帧
|
||||||
|
- 地图自身正常 pan/zoom/render 不受影响
|
||||||
|
- 不产生永久 continuous rendering
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 本任务不是粒子实现
|
||||||
|
|
||||||
|
验证代码只能证明架构入口成立。
|
||||||
|
|
||||||
|
禁止在本任务实现:
|
||||||
|
|
||||||
|
- 速度场
|
||||||
|
- U/V Grid
|
||||||
|
- Field decode
|
||||||
|
- particle position
|
||||||
|
- age
|
||||||
|
- seed
|
||||||
|
- advection
|
||||||
|
- velocity texture
|
||||||
|
- particle state texture
|
||||||
|
- ping-pong
|
||||||
|
- Trail
|
||||||
|
- fade pass
|
||||||
|
- Land/Ocean Mask
|
||||||
|
- Wave/Tide/Current
|
||||||
|
- Wind fixture
|
||||||
|
- 真正 Particle Shader
|
||||||
|
- compute shader
|
||||||
|
|
||||||
|
如需要视觉确认,只允许使用最简单的验证图元或调试绘制。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
- 单色矩形
|
||||||
|
- 三角形
|
||||||
|
- 简单全屏/局部 quad
|
||||||
|
- 简单随帧变化的 alpha / position
|
||||||
|
|
||||||
|
不得把验证图元包装成“粒子初版”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 验证代码命名
|
||||||
|
|
||||||
|
验证代码必须明确表明其临时技术验证属性。
|
||||||
|
|
||||||
|
优先使用:
|
||||||
|
|
||||||
|
`ParticleValidation*`
|
||||||
|
|
||||||
|
或:
|
||||||
|
|
||||||
|
`RasterParticleValidation*`
|
||||||
|
|
||||||
|
不要直接把不成熟的验证代码命名成最终生产类并假装已经冻结。
|
||||||
|
|
||||||
|
如果必须使用正式架构类名才能验证注册链,例如:
|
||||||
|
|
||||||
|
- `RasterParticleLayer`
|
||||||
|
- `RenderRasterParticleLayer`
|
||||||
|
|
||||||
|
则必须:
|
||||||
|
|
||||||
|
1. 只实现验证所需最小骨架;
|
||||||
|
2. 在代码和文档中明确 `VALIDATION ONLY`;
|
||||||
|
3. 不提前加入未来未验证业务接口;
|
||||||
|
4. 不把临时调试字段伪装成正式公共 API。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 最小实现范围
|
||||||
|
|
||||||
|
Codex 应先调查当前最小实现需要触及哪些文件。
|
||||||
|
|
||||||
|
原则:
|
||||||
|
|
||||||
|
> 能用 5 个文件解决,不要改 15 个文件。
|
||||||
|
|
||||||
|
预计可能涉及,但不得机械照搬:
|
||||||
|
|
||||||
|
- Core style layer 声明
|
||||||
|
- Core style layer 实现
|
||||||
|
- 对应 RenderLayer
|
||||||
|
- LayerManager / 注册位置
|
||||||
|
- Android Test App 或现有测试入口
|
||||||
|
- 最小验证日志/状态
|
||||||
|
|
||||||
|
最终以当前真实代码为准。
|
||||||
|
|
||||||
|
不得为了“以后可能用到”提前创建整套 particle 目录树。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Style / RenderTree 验证要求
|
||||||
|
|
||||||
|
必须证明实验 Layer 是通过正式 MapLibre Layer 链进入渲染。
|
||||||
|
|
||||||
|
验证报告至少记录实际链:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Style Layer
|
||||||
|
→ LayerManager / RenderLayer creation
|
||||||
|
→ RenderTree / RenderOrchestrator
|
||||||
|
→ 对应 RenderLayer
|
||||||
|
→ Render pass
|
||||||
|
```
|
||||||
|
|
||||||
|
必须指出:
|
||||||
|
|
||||||
|
- 实际插入哪个 pass
|
||||||
|
- 与前后普通 Style Layer 的 order 是否正确
|
||||||
|
- 是否需要 tile/source
|
||||||
|
- 当前验证 Layer 是否 source-less
|
||||||
|
- 如果 source-less 不可行,实际最小 Source 依赖是什么
|
||||||
|
|
||||||
|
不能只通过 CustomLayer 示例证明。
|
||||||
|
|
||||||
|
本任务要验证的是总体架构选择的“独立 Core Layer 路线”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 同帧 Camera / Matrix 验证
|
||||||
|
|
||||||
|
验证代码必须从当前 render path 内取得 transform。
|
||||||
|
|
||||||
|
至少记录:
|
||||||
|
|
||||||
|
- 获取位置
|
||||||
|
- 对应源码对象
|
||||||
|
- 生命周期
|
||||||
|
- 是否每 frame 更新
|
||||||
|
- 是否与当前 RenderTree frame 同步
|
||||||
|
|
||||||
|
至少验证以下地图变化不会让实验 Layer 明显脱离地图:
|
||||||
|
|
||||||
|
- pan
|
||||||
|
- zoom
|
||||||
|
- bearing
|
||||||
|
- pitch
|
||||||
|
- resize
|
||||||
|
|
||||||
|
本任务不要求完成 world wrap / antimeridian 专项验证。
|
||||||
|
|
||||||
|
这些留给后续 Mask/Camera 技术验证。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Repaint 验证
|
||||||
|
|
||||||
|
实验 Layer 必须提供最小“活动/停止”活动模型。
|
||||||
|
|
||||||
|
例如内部验证状态:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ACTIVE
|
||||||
|
STATIC
|
||||||
|
```
|
||||||
|
|
||||||
|
### ACTIVE
|
||||||
|
|
||||||
|
必须证明:
|
||||||
|
|
||||||
|
- 当前 frame 完成后仍会请求 MapLibre 下一帧
|
||||||
|
- 连续帧来自 MapLibre render/repaint 链
|
||||||
|
- 能观察到 frame counter 持续增长
|
||||||
|
|
||||||
|
### STATIC
|
||||||
|
|
||||||
|
必须证明:
|
||||||
|
|
||||||
|
- 不再由该 Layer 主动触发下一帧
|
||||||
|
- 地图静止时 frame counter 不继续无意义增长
|
||||||
|
- 用户操作地图时仍能正常重新绘制
|
||||||
|
|
||||||
|
不得使用平台 Timer 模拟。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 日志要求
|
||||||
|
|
||||||
|
验证期间允许增加最少量调试日志。
|
||||||
|
|
||||||
|
日志至少能识别:
|
||||||
|
|
||||||
|
- Layer create
|
||||||
|
- RenderLayer create
|
||||||
|
- first render
|
||||||
|
- frame counter
|
||||||
|
- current active/static
|
||||||
|
- repaint request
|
||||||
|
- relevant transform snapshot
|
||||||
|
- destroy/deinitialize
|
||||||
|
|
||||||
|
禁止每帧输出大量完整 matrix 导致日志爆炸。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 首帧输出一次完整关键参数
|
||||||
|
- 后续每 N 帧输出 frame counter
|
||||||
|
- 状态变化时输出
|
||||||
|
- 销毁时输出
|
||||||
|
|
||||||
|
验证完成后是否保留日志,由报告明确说明。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Android 第一验证平台
|
||||||
|
|
||||||
|
本任务第一运行平台固定为:
|
||||||
|
|
||||||
|
**Android OpenGL**
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 当前架构设计将它作为第一共同验证入口;
|
||||||
|
- 本任务只验证 Core Layer + 同帧 transform + repaint;
|
||||||
|
- 不在本任务同时扩大到 Vulkan / iOS。
|
||||||
|
|
||||||
|
Vulkan 和 iOS 留到后续对应验证。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Android 编译规则:必须由用户手工执行
|
||||||
|
|
||||||
|
本项目 Android 编译必须由用户手工执行。
|
||||||
|
|
||||||
|
Codex 禁止运行:
|
||||||
|
|
||||||
|
- Gradle Android build
|
||||||
|
- `./gradlew assemble...`
|
||||||
|
- `./gradlew install...`
|
||||||
|
- Android npm build
|
||||||
|
- 任何等价 Android 编译命令
|
||||||
|
|
||||||
|
Codex 可以:
|
||||||
|
|
||||||
|
- 检查现有 Gradle 配置
|
||||||
|
- 准备代码
|
||||||
|
- 给出用户应执行的准确编译命令
|
||||||
|
- 分析用户返回的编译错误
|
||||||
|
- 在允许范围内修正本任务代码
|
||||||
|
|
||||||
|
Codex 不得代替用户执行 Android 编译。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. 运行/设备验证规则
|
||||||
|
|
||||||
|
本任务不得默认替用户执行:
|
||||||
|
|
||||||
|
- ADB 安装
|
||||||
|
- ADB 启动
|
||||||
|
- ADB 日志抓取
|
||||||
|
- 真机操作
|
||||||
|
|
||||||
|
除非用户在后续明确授权。
|
||||||
|
|
||||||
|
因此第一轮 Codex 工作完成后,应输出:
|
||||||
|
|
||||||
|
1. 用户手工编译命令
|
||||||
|
2. 应打开的 Test App / 页面 / Activity
|
||||||
|
3. 需要执行的地图操作
|
||||||
|
4. 需要观察的日志
|
||||||
|
5. PASS/FAIL 判定方法
|
||||||
|
|
||||||
|
用户提供编译/运行结果后,再完成最终技术验证结论。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. 两阶段执行方式
|
||||||
|
|
||||||
|
本任务允许同一份指令书分两次 Codex 执行。
|
||||||
|
|
||||||
|
### 阶段 A:验证代码准备
|
||||||
|
|
||||||
|
Codex:
|
||||||
|
|
||||||
|
1. 读取基线
|
||||||
|
2. 写最小验证代码
|
||||||
|
3. 做静态检查
|
||||||
|
4. 生成暂定验证报告
|
||||||
|
5. 给出用户手工编译/运行步骤
|
||||||
|
6. commit + push 到 `particle/main`
|
||||||
|
|
||||||
|
此时报告结论必须写:
|
||||||
|
|
||||||
|
`WAITING_MANUAL_VALIDATION`
|
||||||
|
|
||||||
|
不得写 PASS。
|
||||||
|
|
||||||
|
### 阶段 B:用户返回实际编译/运行结果后
|
||||||
|
|
||||||
|
Codex:
|
||||||
|
|
||||||
|
1. 读取用户提供的编译结果、日志和现象
|
||||||
|
2. 如果失败,只修复本验证范围内问题
|
||||||
|
3. 如果架构假设本身失败,停止,不强行修成另一套架构
|
||||||
|
4. 更新正式验证报告
|
||||||
|
5. 更新 Codex 完成报告
|
||||||
|
6. 更新 `粒子开发记录.md`
|
||||||
|
7. commit + push 到 `particle/main`
|
||||||
|
|
||||||
|
最终只能是:
|
||||||
|
|
||||||
|
- `PASS`
|
||||||
|
- `FAIL`
|
||||||
|
- `BLOCKED`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. PASS 标准
|
||||||
|
|
||||||
|
只有同时满足以下条件,`30-01` 才能 PASS:
|
||||||
|
|
||||||
|
- [ ] 独立 Core Style Layer 被正式识别
|
||||||
|
- [ ] 对应 RenderLayer 被创建
|
||||||
|
- [ ] 真正进入 RenderTree / Render pass
|
||||||
|
- [ ] Layer order 正确
|
||||||
|
- [ ] 从当前 render frame 获得必要 Transform
|
||||||
|
- [ ] pan 正常
|
||||||
|
- [ ] zoom 正常
|
||||||
|
- [ ] bearing 正常
|
||||||
|
- [ ] pitch 正常
|
||||||
|
- [ ] resize 正常
|
||||||
|
- [ ] ACTIVE 时由 MapLibre render loop 持续 repaint
|
||||||
|
- [ ] STATIC 时停止该 Layer 的额外 repaint
|
||||||
|
- [ ] 地图普通渲染不受破坏
|
||||||
|
- [ ] 未使用外部 Overlay
|
||||||
|
- [ ] 未建立平台 Timer 动画循环
|
||||||
|
- [ ] 未实现超出本任务范围的粒子系统
|
||||||
|
|
||||||
|
任何一项关键门槛失败,不得标 PASS。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18. FAIL 与停止条件
|
||||||
|
|
||||||
|
出现以下任一情况时,应停止继续向后实现:
|
||||||
|
|
||||||
|
### F1
|
||||||
|
|
||||||
|
独立 Core Layer 无法在不破坏 MapLibre 现有主架构的情况下进入真实 RenderTree。
|
||||||
|
|
||||||
|
### F2
|
||||||
|
|
||||||
|
无法从该 Layer 的真实 render frame 获得足够 transform。
|
||||||
|
|
||||||
|
### F3
|
||||||
|
|
||||||
|
必须依赖外部 Overlay 或独立动画 loop 才能工作。
|
||||||
|
|
||||||
|
### F4
|
||||||
|
|
||||||
|
持续 repaint 必须通过明显侵入 Map 主循环才能实现。
|
||||||
|
|
||||||
|
### F5
|
||||||
|
|
||||||
|
验证要求迫使 Android/iOS 分别拥有不同 particle engine。
|
||||||
|
|
||||||
|
若触发 F1-F5:
|
||||||
|
|
||||||
|
- 停止
|
||||||
|
- 不继续 GPU ping-pong
|
||||||
|
- 不继续 Mask
|
||||||
|
- 不继续 iOS 实现
|
||||||
|
- 报告失败事实
|
||||||
|
- 回到架构设计重新决策
|
||||||
|
|
||||||
|
不得为了让验证“通过”扩大修改范围。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19. 禁止事项
|
||||||
|
|
||||||
|
本任务禁止:
|
||||||
|
|
||||||
|
- 正式实现 Particle Field
|
||||||
|
- 正式实现 RasterArraySource 数据链
|
||||||
|
- 正式实现 GPU Simulation
|
||||||
|
- 正式实现 Trail
|
||||||
|
- 正式实现 Mask
|
||||||
|
- 接真实天气数据
|
||||||
|
- 引入新第三方依赖
|
||||||
|
- 升级 MapLibre
|
||||||
|
- 升级 Android SDK / NDK / Gradle
|
||||||
|
- 修改无关 Layer
|
||||||
|
- 重构 Renderer
|
||||||
|
- 重构 Map
|
||||||
|
- 修改 iOS 代码
|
||||||
|
- 实现 Vulkan 版本
|
||||||
|
- 建立 RN/JS bridge
|
||||||
|
- 新增产品 API
|
||||||
|
- 清理与本任务无关的旧代码
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 20. 文档输出
|
||||||
|
|
||||||
|
### 20.1 技术验证报告
|
||||||
|
|
||||||
|
生成:
|
||||||
|
|
||||||
|
`docs/60_测试/30_01_CoreRenderLayer接入最小技术验证报告_v1.md`
|
||||||
|
|
||||||
|
如果同主题已有文件,按版本递增,不覆盖旧文件。
|
||||||
|
|
||||||
|
报告必须区分:
|
||||||
|
|
||||||
|
- 静态确认
|
||||||
|
- 用户手工编译结果
|
||||||
|
- 用户运行结果
|
||||||
|
- 实际日志证据
|
||||||
|
- 推论
|
||||||
|
|
||||||
|
没有用户运行结果时,只能写:
|
||||||
|
|
||||||
|
`WAITING_MANUAL_VALIDATION`
|
||||||
|
|
||||||
|
### 20.2 Codex 完成报告
|
||||||
|
|
||||||
|
生成:
|
||||||
|
|
||||||
|
`docs/报告/30_01_CoreRenderLayer接入验证_Codex完成报告_v1.md`
|
||||||
|
|
||||||
|
同主题已有报告时递增版本号。
|
||||||
|
|
||||||
|
### 20.3 开发记录
|
||||||
|
|
||||||
|
追加:
|
||||||
|
|
||||||
|
`粒子开发记录.md`
|
||||||
|
|
||||||
|
不得覆盖历史内容。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 21. 不主动创建 Step
|
||||||
|
|
||||||
|
本任务不主动创建或修改 `docs/50_Step记录/`。
|
||||||
|
|
||||||
|
只有用户明确要求时才创建 Step。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 22. Git 收尾规则
|
||||||
|
|
||||||
|
本任务凡产生文件修改,都必须提交并 push。
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 本地主线:`particle/main`
|
||||||
|
- 公开远端对应分支:`particle/main`
|
||||||
|
|
||||||
|
### 22.1 提交范围
|
||||||
|
|
||||||
|
只允许提交:
|
||||||
|
|
||||||
|
- 本任务验证代码
|
||||||
|
- 本任务验证文档
|
||||||
|
- 本任务完成报告
|
||||||
|
- 本任务对 `粒子开发记录.md` 的追加
|
||||||
|
|
||||||
|
不得把与本任务无关的本地修改一起提交。
|
||||||
|
|
||||||
|
### 22.2 阶段 A
|
||||||
|
|
||||||
|
验证代码准备完成后:
|
||||||
|
|
||||||
|
1. 检查 `git diff`
|
||||||
|
2. 明确列出本任务文件
|
||||||
|
3. commit
|
||||||
|
4. push 到公开 `particle/main`
|
||||||
|
5. 确认远端可见
|
||||||
|
|
||||||
|
推荐提交信息:
|
||||||
|
|
||||||
|
`test: add core particle render layer validation`
|
||||||
|
|
||||||
|
### 22.3 阶段 B
|
||||||
|
|
||||||
|
用户手工验证结果回填后:
|
||||||
|
|
||||||
|
1. 更新验证报告
|
||||||
|
2. 更新完成报告
|
||||||
|
3. 更新开发记录
|
||||||
|
4. 如有必要修复最小验证代码
|
||||||
|
5. commit
|
||||||
|
6. push 到公开 `particle/main`
|
||||||
|
|
||||||
|
推荐提交信息:
|
||||||
|
|
||||||
|
`test: finalize core particle render layer validation`
|
||||||
|
|
||||||
|
### 22.4 Push 失败
|
||||||
|
|
||||||
|
如果 push 失败:
|
||||||
|
|
||||||
|
- 不得声称任务完整完成
|
||||||
|
- 完成报告必须写明:
|
||||||
|
- push 失败
|
||||||
|
- 错误原因
|
||||||
|
- 本地 commit hash
|
||||||
|
- 目标远端
|
||||||
|
- 目标分支
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 23. Git 信息记录
|
||||||
|
|
||||||
|
Codex 完成报告必须记录:
|
||||||
|
|
||||||
|
- 任务开始 HEAD
|
||||||
|
- 本次主要变更 commit hash
|
||||||
|
- commit message
|
||||||
|
- push 是否成功
|
||||||
|
- 公开远端
|
||||||
|
- 远端分支
|
||||||
|
- push 后远端可见状态
|
||||||
|
- 是否存在未提交的无关本地改动
|
||||||
|
|
||||||
|
为避免“提交文件记录自己的 commit hash”产生自引用问题:
|
||||||
|
|
||||||
|
- 完成报告中的 `主要变更 commit hash` 记录包含验证代码和主要文档的实质性提交;
|
||||||
|
- 如果随后还有仅用于回填报告元数据的收尾提交,Codex 最终回复中必须另外报告最终远端 HEAD;
|
||||||
|
- 不得为了回填当前提交自己的 hash 无限 amend。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 24. Codex 完成报告必须逐项说明
|
||||||
|
|
||||||
|
至少包括:
|
||||||
|
|
||||||
|
1. 前置文档读取
|
||||||
|
2. 当前 Git 基线
|
||||||
|
3. 实际修改文件
|
||||||
|
4. Style Layer 接入位置
|
||||||
|
5. RenderLayer 创建链
|
||||||
|
6. RenderTree / pass
|
||||||
|
7. Transform 获取链
|
||||||
|
8. Repaint 链
|
||||||
|
9. Android OpenGL 验证入口
|
||||||
|
10. 用户手工编译命令
|
||||||
|
11. 用户手工运行步骤
|
||||||
|
12. 当前状态:
|
||||||
|
- WAITING_MANUAL_VALIDATION
|
||||||
|
- PASS
|
||||||
|
- FAIL
|
||||||
|
- BLOCKED
|
||||||
|
13. 是否修改生产代码
|
||||||
|
14. 是否扩大架构范围
|
||||||
|
15. 是否安装/升级依赖
|
||||||
|
16. Git commit
|
||||||
|
17. push 结果
|
||||||
|
18. 是否可以进入下一个最小技术验证
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 25. 进入下一验证的条件
|
||||||
|
|
||||||
|
只有最终结果:
|
||||||
|
|
||||||
|
`PASS`
|
||||||
|
|
||||||
|
才允许进入下一个技术验证。
|
||||||
|
|
||||||
|
下一项预计验证:
|
||||||
|
|
||||||
|
> GPU State A/B + Trail A/B 的 gfx ping-pong、RenderTarget、同步/barrier 与 context recreation。
|
||||||
|
|
||||||
|
如果本任务是:
|
||||||
|
|
||||||
|
- FAIL
|
||||||
|
- BLOCKED
|
||||||
|
|
||||||
|
则不得提前执行下一项。
|
||||||
|
|
||||||
|
必须先回到架构层决定如何处理。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 26. 本任务完成定义
|
||||||
|
|
||||||
|
`30-01` 真正完成必须满足:
|
||||||
|
|
||||||
|
> 用户手工 Android OpenGL 编译和运行后,已经通过实际运行证据确认:独立 Core Layer 可以进入 MapLibre 正式 Style/RenderTree 渲染链,在同一 render frame 使用 MapLibre 当前 Transform,并通过 MapLibre 自己的 repaint 机制在活动时持续渲染、停止时停止额外帧。
|
||||||
|
|
||||||
|
只有静态代码完成、没有实际运行结果,不叫验证完成。
|
||||||
|
|
||||||
|
只能标记:
|
||||||
|
|
||||||
|
`WAITING_MANUAL_VALIDATION`
|
||||||
1184
docs/40_任务/30_02_GPU_PingPong与ContextRecreation验证指令书_v1.md
Normal file
1184
docs/40_任务/30_02_GPU_PingPong与ContextRecreation验证指令书_v1.md
Normal file
File diff suppressed because it is too large
Load Diff
3
docs/40_任务/README.md
Normal file
3
docs/40_任务/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Codex 任务目录
|
||||||
|
|
||||||
|
文件名 `STEP-xxx_<主题>_Codex任务_vN.md`;完成报告固定 `docs/报告/`。
|
||||||
255
docs/40_任务/STEP-060E_CoreOnlyLayer存在性检查修复_Codex任务_v1.md
Normal file
255
docs/40_任务/STEP-060E_CoreOnlyLayer存在性检查修复_Codex任务_v1.md
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# STEP-060E CoreOnly Layer 存在性检查修复 — Codex任务 v1
|
||||||
|
|
||||||
|
## 1. 任务定位
|
||||||
|
|
||||||
|
当前仍为 **STEP-060**,不得进入 STEP-061。
|
||||||
|
|
||||||
|
本轮修复 STEP-060D 代码审查确认的 Android 根因:
|
||||||
|
|
||||||
|
- `raster-particle` 与 `weather-color` layer 在 Android `LayerManagerAndroid` 中通过 `addLayerTypeCoreOnly(...)` 注册;
|
||||||
|
- Core 可以正常解析、持有和渲染这些 layer;
|
||||||
|
- 但 CoreOnly layer 没有 Java `Layer` peer;
|
||||||
|
- `Style.getLayer(id)` 最终依赖 `LayerManagerAndroid::createJavaLayerPeer(...)`;
|
||||||
|
- 对 CoreOnly layer,该函数返回 null;
|
||||||
|
- 因此 Bridge 用 `Style.getLayer()` 判断 layer 是否存在,必然产生假阴性。
|
||||||
|
|
||||||
|
这与当前现象一致:原生 Particle validation 成功,但 RN `WeatherRuntimeBridge.attach()` 报 weather layer 缺失。
|
||||||
|
|
||||||
|
本任务只修复这一 Android Java/JNI/Core 存在性检查链,并保持现有 CoreOnly 设计。
|
||||||
|
## 2. 开工基线
|
||||||
|
|
||||||
|
正式仓库:
|
||||||
|
|
||||||
|
`/mnt/sda1/MapLibreParticleLab`
|
||||||
|
|
||||||
|
分支:`particle/main`
|
||||||
|
|
||||||
|
本任务开工目标基线:
|
||||||
|
|
||||||
|
`2d2375cd532468821c6e3c158ee1a275f4522044`
|
||||||
|
|
||||||
|
该提交已包含 Win11 已验证的 RN Test Host 修复:
|
||||||
|
|
||||||
|
- React `19.2.3`
|
||||||
|
- RN 0.85.2 codegen 配置
|
||||||
|
- `NativeWeatherRuntimeBridge.ts` 同文件 type aliases
|
||||||
|
- 保留 Expo/RN repositories 的 Maven Local plugin 逻辑
|
||||||
|
- STEP-060D `13.4.1-step060d` 版本接线
|
||||||
|
|
||||||
|
开工前执行 `git pull --ff-only origin particle/main`,确认基线没有落后。
|
||||||
|
不得回退上述 Win11 已验证修复。
|
||||||
|
## 3. 已确认源码证据
|
||||||
|
|
||||||
|
以下证据已经由 ChatGPT 直接审查当前源码确认,不要用 Codex 完成报告替代:
|
||||||
|
|
||||||
|
1. `platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp`
|
||||||
|
- `RasterParticleLayerFactory` 使用 `addLayerTypeCoreOnly(...)`
|
||||||
|
- `WeatherColorLayerFactory` 使用 `addLayerTypeCoreOnly(...)`
|
||||||
|
- `createJavaLayerPeer(...)` 找不到 Java peer factory 时返回 null
|
||||||
|
2. `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp`
|
||||||
|
- `NativeMapView::getLayer(...)` 取得 Core layer 后调用 `createJavaLayerPeer(...)`
|
||||||
|
- 因而 CoreOnly layer 即使真实存在,Java `Style.getLayer()` 仍可得到 null
|
||||||
|
3. 当前 Bridge `WeatherRuntimeBridgeModule.attach()` 使用 `style.getLayer(...)` 做存在性判断,因此判断机制本身错误。
|
||||||
|
4. STEP-060D 新增 `style.getLayers().map { it.id }` 也不安全:CoreOnly layer 没有 Java peer,不能用 Java Layer peer 枚举作为 Core layer 真值来源。
|
||||||
|
5. `MapLibreMap.getStyle()` 只在 Style fully loaded 时返回非 null,所以本轮不是普通 style-ready 时机修复。
|
||||||
|
|
||||||
|
不得重新把问题归因于 React、MLRN mapStyle JSON 传递、AAR 混用或简单 attach 重试时机。
|
||||||
|
## 4. 必做实现:Core 直接存在性查询
|
||||||
|
|
||||||
|
新增通用、窄范围的 Android MapLibre API,用来查询 **Core Style 真值**,不得依赖 Java Source/Layer peer。
|
||||||
|
|
||||||
|
公开语义固定为:
|
||||||
|
|
||||||
|
- `MapLibreMap.hasStyleSource(String sourceId): boolean`
|
||||||
|
- `MapLibreMap.hasStyleLayer(String layerId): boolean`
|
||||||
|
|
||||||
|
具体落点至少覆盖:
|
||||||
|
|
||||||
|
- `NativeMap.java`
|
||||||
|
- `NativeMapView.java`
|
||||||
|
- `MapLibreMap.java`
|
||||||
|
- `native_map_view.hpp`
|
||||||
|
- `native_map_view.cpp`
|
||||||
|
- JNI method registration
|
||||||
|
|
||||||
|
C++ 最终判断必须直接基于:
|
||||||
|
|
||||||
|
- `map->getStyle().getSource(id) != nullptr`
|
||||||
|
- `map->getStyle().getLayer(id) != nullptr`
|
||||||
|
|
||||||
|
不得通过 `createJavaLayerPeer()`、`Style.getLayer()` 或其他 Java peer 路径间接判断。
|
||||||
|
## 5. Bridge attach 修正
|
||||||
|
|
||||||
|
修改正式:
|
||||||
|
|
||||||
|
`integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt`
|
||||||
|
|
||||||
|
attach 在取得 fully-loaded `style` 后,改为:
|
||||||
|
|
||||||
|
- source:使用 `map.hasStyleSource(WeatherIds.SOURCE)`
|
||||||
|
- weather color layer:使用 `map.hasStyleLayer(WeatherIds.COLOR_LAYER)`
|
||||||
|
- particle layer:使用 `map.hasStyleLayer(WeatherIds.PARTICLE_LAYER)`
|
||||||
|
|
||||||
|
三个结果继续独立输出。
|
||||||
|
|
||||||
|
必须删除/停止使用以下方式作为存在性真值:
|
||||||
|
|
||||||
|
- `style.getLayer(...)`
|
||||||
|
- `style.getLayers().map { it.id }`
|
||||||
|
- Java Layer peer 数量作为 Core layer 数量
|
||||||
|
|
||||||
|
source 也统一改用 Core 直接查询,不再让 Source/Layer 使用两套存在性语义。
|
||||||
|
## 6. STEP-060D 诊断修正
|
||||||
|
|
||||||
|
保留稳定错误码:
|
||||||
|
|
||||||
|
`WEATHER_OBJECTS_NOT_READY`
|
||||||
|
|
||||||
|
失败信息至少包含:
|
||||||
|
|
||||||
|
- `reactTag`
|
||||||
|
- `mapView=present|missing`
|
||||||
|
- `mapLibreMap=present|missing`
|
||||||
|
- `style=present|missing`
|
||||||
|
- `source[rn-weather-fields]=present|missing`
|
||||||
|
- `layer[rn-weather-color]=present|missing`
|
||||||
|
- `layer[rn-weather-particles]=present|missing`
|
||||||
|
|
||||||
|
另外使用公开 `Style.getUri()` / Kotlin 对应属性输出 `styleUri`;JSON style URI 为空时明确写 `<empty>` 或等价稳定表示。
|
||||||
|
|
||||||
|
不要再调用 `style.getLayers()` 构造诊断列表。
|
||||||
|
如果确实需要 ID 列表,只允许使用直接 Core ID 查询;本任务默认不要求新增列表 API,避免扩大修改面。
|
||||||
|
|
||||||
|
成功路径仍必须保持现有 `replaceWeatherRuntime(...)`、snapshot、listener 和 Phase A/B 控制语义不变。
|
||||||
|
## 7. 明确禁止
|
||||||
|
|
||||||
|
本任务不得:
|
||||||
|
|
||||||
|
- 将 `RasterParticleLayerFactory` 或 `WeatherColorLayerFactory` 从 `addLayerTypeCoreOnly(...)` 改成普通 Java layer registration
|
||||||
|
- 为了让 `Style.getLayer()` 非 null 而新建 WeatherColor/RasterParticle Java peer 类
|
||||||
|
- 改变 CoreOnly layer 的产品边界
|
||||||
|
- 用解析完整 Style JSON 作为正式存在性判断
|
||||||
|
- 增加 attach 定时重试或无限重试
|
||||||
|
- 修改 MLRN mapStyle 传递链来掩盖问题
|
||||||
|
- 修改 WeatherRuntime 控制状态机
|
||||||
|
- 实现 binary U/V field transport
|
||||||
|
- 进入 STEP-061
|
||||||
|
- 在 Win11 临时修改 Native 正式源码
|
||||||
|
- 执行 Win11 APK build/install、ADB 或设备测试
|
||||||
|
|
||||||
|
本轮目标是让 Java/RN Host 能以一个窄 API 查询 Core Style 真值,而不是把 CoreOnly 类型暴露成完整 Android Java Layer API。
|
||||||
|
## 8. Maven / AAR 版本
|
||||||
|
|
||||||
|
本轮必须使用新的临时版本,禁止复用 STEP-060D 内容不同但版本相同的 artifact:
|
||||||
|
|
||||||
|
`org.maplibre.gl:android-sdk-opengl:13.4.1-step060e`
|
||||||
|
|
||||||
|
同步更新至少包括:
|
||||||
|
|
||||||
|
- `platform/android/VERSION`
|
||||||
|
- RN Test Host `modules/weather-runtime-bridge/android/build.gradle`
|
||||||
|
- `plugins/withParticleNativeAndroid.js`
|
||||||
|
|
||||||
|
Codex **不得构建 AAR,也不得执行 Android/Gradle AAR 打包或 Maven Local 发布**。
|
||||||
|
|
||||||
|
Codex 本轮只负责把源码与版本接线准备到可由用户手工构建的状态,并在完成 Linux 静态检查/精确编译后 commit + push。
|
||||||
|
|
||||||
|
用户在 Codex 停止后手工执行 OpenGL Release custom AAR 构建与 Maven Local 发布。用户构建完成后再记录:
|
||||||
|
|
||||||
|
- Maven coordinate
|
||||||
|
- AAR build output 路径
|
||||||
|
- Maven Local AAR 路径
|
||||||
|
- AAR SHA256
|
||||||
|
- 四 ABI 是否存在
|
||||||
|
|
||||||
|
Codex 完成报告中必须明确写:`AAR_BUILD_NOT_EXECUTED_BY_CODEX`,不得把“源码已准备”表述成“AAR 已生成”。
|
||||||
|
|
||||||
|
不得建立新的正式 Maven pipeline;本轮仍为 STEP-060 临时可追溯版本。
|
||||||
|
## 9. Linux 验证要求
|
||||||
|
|
||||||
|
Codex 只执行与本轮源码相关的轻量、精确验证,不执行 AAR 打包。
|
||||||
|
|
||||||
|
至少完成:
|
||||||
|
|
||||||
|
- Android JNI/C++ 相关源码的精确编译或现有等价最小验证;
|
||||||
|
- Android Java/Kotlin 受影响源码的可行精确编译;若因当前生成工程/codegen 缺失无法完成,必须明确记录限制,不得通过扩大修改范围绕过;
|
||||||
|
- 检查新增 JNI 方法声明、实现、注册签名一致;
|
||||||
|
- 检查 `MapLibreMap.hasStyleSource/hasStyleLayer` 最终确实直达 Core Style;
|
||||||
|
- 检查 Bridge 已完全停止用 Java `Style.getLayer()` / `Style.getLayers()` 判断 CoreOnly layer 存在性;
|
||||||
|
- `git diff --check`;
|
||||||
|
- 检查最终 diff,不得混入无关文件。
|
||||||
|
|
||||||
|
编译输出遵循全局 `$build-output-throttle`。
|
||||||
|
|
||||||
|
本任务不执行 Jest,也不得新增 Jest 要求。
|
||||||
|
## 10. 文档要求
|
||||||
|
|
||||||
|
完成后更新 STEP-060E 对应中文 Step 记录与中文 Codex 完成报告。
|
||||||
|
|
||||||
|
同主题已有文档时优先更新或使用递增版本号,不得无意义创建多个不同文件名。
|
||||||
|
|
||||||
|
文档必须明确:
|
||||||
|
|
||||||
|
- 根因为 CoreOnly layer 无 Java peer,导致 Java `Style.getLayer()` 假阴性;
|
||||||
|
- 本轮采用 Core 直接存在性查询,不改变 CoreOnly 架构;
|
||||||
|
- Win11 已验证 RN Test Host 基线来自 `2d2375cd532468821c6e3c158ee1a275f4522044`;
|
||||||
|
- Maven 临时版本已切到 `13.4.1-step060e`;
|
||||||
|
- `AAR_BUILD_NOT_EXECUTED_BY_CODEX`;
|
||||||
|
- Android APK/ADB/设备验证尚未执行;
|
||||||
|
- STEP-060 尚未 CLOSED,未进入 STEP-061。
|
||||||
|
## 11. Git 收尾
|
||||||
|
|
||||||
|
完成规定源码、验证和文档后,只提交本任务相关变更到当前主线 `particle/main`。
|
||||||
|
|
||||||
|
必须:
|
||||||
|
|
||||||
|
- commit 本任务相关源码、版本接线、任务文件及文档;
|
||||||
|
- push 到 `origin/particle/main`;
|
||||||
|
- push 失败时不得声称任务完成;
|
||||||
|
- 不得提交与本任务无关的本地改动。
|
||||||
|
|
||||||
|
完成报告必须记录:
|
||||||
|
|
||||||
|
- source commit SHA;
|
||||||
|
- final HEAD;
|
||||||
|
- commit message;
|
||||||
|
- push 结果;
|
||||||
|
- remote branch;
|
||||||
|
- 预期 Maven coordinate:`org.maplibre.gl:android-sdk-opengl:13.4.1-step060e`;
|
||||||
|
- `AAR_BUILD_NOT_EXECUTED_BY_CODEX`。
|
||||||
|
## 12. 完成报告格式
|
||||||
|
|
||||||
|
结束时按以下必做项逐项报告:`已完成 / 未完成 / 无法验证`,并说明未完成或无法验证原因:
|
||||||
|
|
||||||
|
- Core `hasStyleSource` 查询链;
|
||||||
|
- Core `hasStyleLayer` 查询链;
|
||||||
|
- Bridge attach 改用 Core 真值;
|
||||||
|
- 移除危险的 `style.getLayers()` 诊断枚举;
|
||||||
|
- 增加 `styleUri` 诊断;
|
||||||
|
- 成功路径与 WeatherRuntime 控制语义保持不变;
|
||||||
|
- `13.4.1-step060e` 版本接线;
|
||||||
|
- Linux 精确验证;
|
||||||
|
- 文档更新;
|
||||||
|
- Git commit/push;
|
||||||
|
- AAR 未由 Codex 构建;
|
||||||
|
- 未进入 STEP-061。
|
||||||
|
|
||||||
|
不得用“基本完成”“应该可以”等模糊表述替代状态。
|
||||||
|
## 13. 停止条件与后续人工链路
|
||||||
|
|
||||||
|
Codex 成功 push 后立即停止,不得自行构建 AAR,不得进入 Win11 或 Android 运行验证。
|
||||||
|
|
||||||
|
后续固定人工/ChatGPT 链路:
|
||||||
|
|
||||||
|
1. ChatGPT 审核远端实际代码;
|
||||||
|
2. 用户在 Linux 手工构建 `13.4.1-step060e` OpenGL Release AAR并发布 Maven Local;
|
||||||
|
3. 记录 Linux AAR SHA256;
|
||||||
|
4. Win11 从 Git pull 最新 RN Test Host 源码;
|
||||||
|
5. Win11 通过 SCP 从 `root@192.168.200.184` 拉取 AAR;
|
||||||
|
6. Win11 计算 SHA256,必须与 Linux 完全一致;
|
||||||
|
7. 更新 Win11 Maven Local;
|
||||||
|
8. 执行 Bridge 与 MLRN 两条 `dependencyInsight`,必须共同解析到 `13.4.1-step060e`;
|
||||||
|
9. 用户手工构建 APK;
|
||||||
|
10. 再进行 Android attach/runtime 验证。
|
||||||
|
|
||||||
|
在上述 Android 结果出来前,不得宣布 STEP-060 CLOSED,也不得进入 STEP-061。
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
# STEP-060F WeatherColor Opacity 与 Android 收尾修复 — Codex任务 v1
|
||||||
|
|
||||||
|
## 1. 任务定位
|
||||||
|
|
||||||
|
当前仍属于 **STEP-060**,不得进入 STEP-061。
|
||||||
|
|
||||||
|
STEP-060E 已完成 CoreOnly layer 存在性检查修复,并已完成 Android 实际验证:
|
||||||
|
|
||||||
|
- `WeatherRuntime attached` 成功;
|
||||||
|
- `reactTag` 为有效 Fabric MLRNMapView tag;
|
||||||
|
- `WEATHER_OBJECTS_NOT_READY` 已消失;
|
||||||
|
- Phase A 的 enable / weather visible / particle visible / FPS 30 均返回 `ACCEPTED`;
|
||||||
|
- 无 Binary Field 时 runtime 保持 `WAITING_FOR_FIELDS / PAUSE / ...` 符合预期。
|
||||||
|
|
||||||
|
本任务是 STEP-060 最后一次源码收尾,集中处理:
|
||||||
|
|
||||||
|
1. 修复 `WeatherRuntime.setWeatherColorOpacity()` Android 真 bug;
|
||||||
|
2. 固化 Test Host Metro 8091 的 Android 8081 reverse 兼容;
|
||||||
|
3. 保持已提交的 Test Host 横屏锁定;
|
||||||
|
4. 准备 `13.4.1-step060f`,由用户后续一次性手工构建 AAR + APK 并做最终验证。
|
||||||
|
|
||||||
|
## 2. 开工基线
|
||||||
|
|
||||||
|
仓库:`/mnt/sda1/MapLibreParticleLab`
|
||||||
|
分支:`particle/main`
|
||||||
|
开工目标 HEAD / remote HEAD:`204102f26049049151cd5471fada804d471e15a9`
|
||||||
|
|
||||||
|
开工前必须:
|
||||||
|
|
||||||
|
- `git pull --ff-only origin particle/main`;
|
||||||
|
- 确认 HEAD / remote HEAD 一致;
|
||||||
|
- 检查 working tree,不得清理或提交与本任务无关的历史文件;
|
||||||
|
- 保留 STEP-060E `hasStyleSource/hasStyleLayer` Core 真值链;
|
||||||
|
- 保留 `app.json` 的 `"orientation": "landscape"`;
|
||||||
|
- 不得回退 Win11 已验证的 RN 0.85.2 / React 19.2.3 / Expo 56 / MLRN 11.3.6 基线。
|
||||||
|
|
||||||
|
## 3. 已确认 Android 实测证据
|
||||||
|
|
||||||
|
ChatGPT 已通过 CARBON7i + ADB 对 `13.4.1-step060e` 实测,以下为已确认输入,不要重新猜根因:
|
||||||
|
|
||||||
|
- `color stops` → `ACCEPTED`
|
||||||
|
- `particle RGBA` → `ACCEPTED`
|
||||||
|
- `trail RGBA` → `ACCEPTED`
|
||||||
|
- `particle count` → `ACCEPTED`
|
||||||
|
- `line width` → `ACCEPTED`
|
||||||
|
- `particle length` → `ACCEPTED`
|
||||||
|
- `particle opacity` → `ACCEPTED`
|
||||||
|
- `trail opacity` → `ACCEPTED`
|
||||||
|
- `trail fade` → `ACCEPTED`
|
||||||
|
- `weather opacity` → **`REJECTED`**
|
||||||
|
|
||||||
|
时间线相关控制在尚无 T0/T1 field window 时返回:
|
||||||
|
|
||||||
|
- PLAY / PAUSE → `REJECTED`
|
||||||
|
- HALF / NORMAL / DOUBLE → `REJECTED`
|
||||||
|
- seek now → `REJECTED`
|
||||||
|
- Native warning:`ParticleTimeline: event=REJECTED reason=TIMELINE_NOT_READY`
|
||||||
|
|
||||||
|
这是当前无 field 的正常 fail-closed 行为,本任务不得为了让这些命令在 `WAITING_FOR_FIELDS` 下返回 ACCEPTED 而修改 Timeline/Playback 语义。
|
||||||
|
|
||||||
|
真正 blocker 为设备两次稳定复现:
|
||||||
|
|
||||||
|
`ParticleGPU: event=STYLE_PROPERTY_SET result=UNSUPPORTED_PROPERTY`
|
||||||
|
|
||||||
|
对应 UI:`weather opacity: REJECTED`。
|
||||||
|
|
||||||
|
## 4. 已确认源码根因
|
||||||
|
|
||||||
|
当前 `WeatherRuntime.setWeatherColorOpacity(float)` 走:
|
||||||
|
|
||||||
|
`MapLibreMap.setStyleLayerFloatProperty(weatherColorLayerId, "weather-color-opacity", opacity)`。
|
||||||
|
|
||||||
|
但 `NativeMapView::setStyleLayerFloatProperty(...)` 只允许 `raster-particle-*` 属性,并把目标 layer `dynamic_cast` 为 `RasterParticleLayer`。
|
||||||
|
|
||||||
|
因此 `weather-color-opacity` 必然被 `UNSUPPORTED_PROPERTY` 拒绝。
|
||||||
|
|
||||||
|
Core `WeatherColorLayer` 已有正式 typed API:
|
||||||
|
|
||||||
|
- `float getOpacity() const`
|
||||||
|
- `void setOpacity(float)`
|
||||||
|
- finite value;Core 内部 clamp 到 `[0,1]`
|
||||||
|
|
||||||
|
Android 当前也已经有独立 `setWeatherColorStops(...)` Java → JNI → Core 路径,并直接 `dynamic_cast<WeatherColorLayer*>`。本任务应沿该模式做最小修正,不得扩大通用 particle property setter。
|
||||||
|
|
||||||
|
## 5. 必做实现 A:专用 WeatherColor opacity 路径
|
||||||
|
|
||||||
|
新增窄范围 typed Host API,建议稳定命名:
|
||||||
|
|
||||||
|
`MapLibreMap.setWeatherColorOpacity(String layerId, float opacity): boolean`
|
||||||
|
|
||||||
|
必须贯通至少:
|
||||||
|
|
||||||
|
- `NativeMap.java`
|
||||||
|
- `NativeMapView.java`
|
||||||
|
- `MapLibreMap.java`
|
||||||
|
- `native_map_view.hpp`
|
||||||
|
- `native_map_view.cpp`
|
||||||
|
- JNI method registration
|
||||||
|
|
||||||
|
C++ 必须:
|
||||||
|
|
||||||
|
- 直接从 Core Style 按 id 取 layer;
|
||||||
|
- `dynamic_cast<mbgl::style::WeatherColorLayer*>`;
|
||||||
|
- 非 WeatherColorLayer / layer 不存在 / 非 finite 输入时返回 false;
|
||||||
|
- 成功时调用 `WeatherColorLayer::setOpacity(opacity)` 并返回 true;
|
||||||
|
- 保持 `WeatherColorLayer` CoreOnly,不创建 Java Layer peer;
|
||||||
|
- 日志不得再复用误导性的 `ParticleGPU ... UNSUPPORTED_PROPERTY` 作为 WeatherColor 正常路径。
|
||||||
|
|
||||||
|
修改 `WeatherRuntime.setWeatherColorOpacity(float)`:
|
||||||
|
|
||||||
|
- 不再调用 `setStyleLayerFloatProperty(..., "weather-color-opacity", ...)`;
|
||||||
|
- 改调新的 typed `setWeatherColorOpacity(...)`;
|
||||||
|
- `WeatherRuntime.CommandResult` 契约保持 `ACCEPTED / REJECTED`;
|
||||||
|
- 不在 Bridge/JS 增加第二套状态或兼容分支。
|
||||||
|
|
||||||
|
不得把 `weather-color-opacity` 加进现有 `setStyleLayerFloatProperty()` 的 particle whitelist 后再混合处理两个 layer 类型;应保持 WeatherColor 与 RasterParticle 两条 typed 路径分离。
|
||||||
|
|
||||||
|
## 6. 必做实现 B:Metro 8091 启动兼容
|
||||||
|
|
||||||
|
Android 实测确认:Test Host Metro 固定监听 CARBON7i `8091`,但当前 Debug APK 的 RN dev-server 探测仍从设备 `localhost:8081` 发起。
|
||||||
|
|
||||||
|
成功运行时需要同时存在:
|
||||||
|
|
||||||
|
```text
|
||||||
|
adb -s 192.168.1.146 reverse tcp:8091 tcp:8091
|
||||||
|
adb -s 192.168.1.146 reverse tcp:8081 tcp:8091
|
||||||
|
```
|
||||||
|
|
||||||
|
仓库当前未跟踪 Win11 legacy 的 `scripts/05-install-and-run.ps1`。本任务应先检查现有仓库 README / 测试文档 / scripts 状态,再以最小可维护方式把该启动规则纳入 Git 受控来源。
|
||||||
|
Win11 已验证 legacy 脚本现状为:
|
||||||
|
|
||||||
|
- `$device = '192.168.1.146'`
|
||||||
|
- `adb -s $device get-state`
|
||||||
|
- `adb -s $device reverse tcp:8091 tcp:8091`
|
||||||
|
- `adb -s $device install -r $apk`
|
||||||
|
- force-stop + `org.maplibre.particle.rntesthost/.MainActivity` 启动
|
||||||
|
|
||||||
|
修正时只需在 install 前增加:
|
||||||
|
|
||||||
|
`adb -s $device reverse tcp:8081 tcp:8091`
|
||||||
|
|
||||||
|
不得删除原 `8091 -> 8091` reverse,不得改设备地址、package、activity 或 Metro 端口。
|
||||||
|
|
||||||
|
如果决定把脚本正式纳入仓库,则创建 `integration/react-native/MapLibreParticleRNTestHost/scripts/05-install-and-run.ps1`,内容以上述已验证行为为基线;同时更新中文 README / Android 人工验收文档说明双 reverse。不得顺手引入整套无关 Win11 脚本。
|
||||||
|
|
||||||
|
## 7. 必做实现 C:横屏配置保持
|
||||||
|
|
||||||
|
`204102f26049049151cd5471fada804d471e15a9` 已把:
|
||||||
|
|
||||||
|
`app.json -> "orientation": "landscape"`
|
||||||
|
|
||||||
|
提交到 `particle/main`。
|
||||||
|
|
||||||
|
本任务只需保护该配置,并在文档中明确下一次 Android prebuild/build 后应生成 `MainActivity android:screenOrientation="landscape"`。不得回退 portrait;Codex 不需要为了横屏运行 Android prebuild 或 APK build。
|
||||||
|
|
||||||
|
## 8. Maven / AAR 临时版本
|
||||||
|
|
||||||
|
Native Android API/JNI 会变化,因此必须使用新 coordinate:
|
||||||
|
|
||||||
|
`org.maplibre.gl:android-sdk-opengl:13.4.1-step060f`
|
||||||
|
|
||||||
|
同步更新至少:
|
||||||
|
|
||||||
|
- `platform/android/VERSION`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js`
|
||||||
|
- README 中当前 temporary native version 描述
|
||||||
|
|
||||||
|
不得复用 `13.4.1-step060e` 发布内容不同的 artifact。
|
||||||
|
|
||||||
|
Codex **不得构建 AAR,不得 publish Maven Local,不得构建 APK,不得运行 ADB/安装/启动 App**。
|
||||||
|
|
||||||
|
完成报告必须明确:`AAR_BUILD_NOT_EXECUTED_BY_CODEX`。
|
||||||
|
|
||||||
|
## 9. Linux 验证边界
|
||||||
|
|
||||||
|
只执行本轮必要的静态/精确非 Android-runtime 验证:
|
||||||
|
|
||||||
|
- JNI Java/C++ 声明、实现、registration 签名一致性;
|
||||||
|
- 新 WeatherColor opacity 路径最终直达 `WeatherColorLayer::setOpacity()`;
|
||||||
|
- `WeatherRuntime.setWeatherColorOpacity()` 不再走 particle `setStyleLayerFloatProperty()`;
|
||||||
|
- 保持 particle float setter whitelist 与 `RasterParticleLayer` 语义不扩大;
|
||||||
|
- 检查 Metro 双 reverse 脚本/文档内容;
|
||||||
|
- 检查 `app.json` 仍为 landscape;
|
||||||
|
- 可执行 Linux OpenGL/C++ 精确编译或已有等价最小验证;
|
||||||
|
- 不执行 Android Gradle build/compile task;
|
||||||
|
- 不执行 Jest;
|
||||||
|
- `git diff --check`;
|
||||||
|
- 最终 diff 不得混入无关文件。
|
||||||
|
|
||||||
|
编译输出遵循全局 `$build-output-throttle`。
|
||||||
|
|
||||||
|
## 10. 明确禁止
|
||||||
|
|
||||||
|
本任务不得:
|
||||||
|
|
||||||
|
- 修改 `RasterParticleLayerFactory` / `WeatherColorLayerFactory` 的 CoreOnly 注册;
|
||||||
|
- 新建 WeatherColor Java Layer peer;
|
||||||
|
- 把 WeatherColor opacity 混进 particle property whitelist;
|
||||||
|
- 改变 Timeline 在无 field 时 `TIMELINE_NOT_READY` 的 fail-closed 行为;
|
||||||
|
- 实现或提前提交 Binary U/V Field Bridge;
|
||||||
|
- 进入 STEP-061;
|
||||||
|
- 修改 NavSea 正式代码;
|
||||||
|
- 启动 Metro、Android emulator、ADB 或设备验证;
|
||||||
|
- 为了验证横屏单独构建 APK;
|
||||||
|
- 清理历史 build/node_modules/untracked 文件。
|
||||||
|
|
||||||
|
## 11. 文档要求
|
||||||
|
完成后至少:
|
||||||
|
|
||||||
|
- 新增 `docs/报告/STEP-060F_WeatherColorOpacity与Android收尾修复_Codex完成报告_v1.md`;
|
||||||
|
- 生成 `docs/50_Step记录/STEP-060_RNTestHost与WeatherRuntime薄桥接_v2.md`,保留 v1 作为历史;
|
||||||
|
- 更新 `docs/60_测试/STEP-060_RNTestHost_Android人工编译与验收_v1.md` 或递增版本,加入:双 reverse、横屏、weather opacity 最终验证;
|
||||||
|
- 更新 `粒子开发记录.md`。
|
||||||
|
|
||||||
|
文档必须记录:
|
||||||
|
|
||||||
|
- STEP-060E Android attach 已 PASS;
|
||||||
|
- `WEATHER_OBJECTS_NOT_READY` 已解决;
|
||||||
|
- `TIMELINE_NOT_READY` 在无 Binary Field 时是预期行为;
|
||||||
|
- STEP-060F blocker 是 WeatherColor opacity 错走 particle-only setter;
|
||||||
|
- 修复后临时版本为 `13.4.1-step060f`;
|
||||||
|
- Android 最终 AAR/APK/设备复验仍等待用户;
|
||||||
|
- 当前状态必须是 `IMPLEMENTED / WAITING_ANDROID_VALIDATION`,不得提前写 CLOSED / PASS。
|
||||||
|
|
||||||
|
## 12. Git 收尾
|
||||||
|
|
||||||
|
只提交本任务相关源码、版本接线、脚本/文档和本任务文件。
|
||||||
|
|
||||||
|
必须:
|
||||||
|
|
||||||
|
- `git diff --check` PASS;
|
||||||
|
- commit 到当前 `particle/main`;
|
||||||
|
- push `origin/particle/main`;
|
||||||
|
- push 失败时不得声称完成;
|
||||||
|
- 不得提交与本任务无关的本地改动。
|
||||||
|
|
||||||
|
完成报告必须记录:
|
||||||
|
|
||||||
|
- Starting HEAD;
|
||||||
|
- source commit SHA;
|
||||||
|
- Final HEAD;
|
||||||
|
- Remote HEAD;
|
||||||
|
- commit message;
|
||||||
|
- push 结果;
|
||||||
|
- remote branch;
|
||||||
|
- 预期 Maven coordinate:`org.maplibre.gl:android-sdk-opengl:13.4.1-step060f`;
|
||||||
|
- `AAR_BUILD_NOT_EXECUTED_BY_CODEX`。
|
||||||
|
|
||||||
|
## 13. Codex 最终报告清单
|
||||||
|
|
||||||
|
结束时逐项报告 `已完成 / 未完成 / 无法验证`:
|
||||||
|
|
||||||
|
- WeatherColor typed opacity Java API;
|
||||||
|
- NativeMap / NativeMapView JNI 链;
|
||||||
|
- C++ 直达 `WeatherColorLayer::setOpacity()`;
|
||||||
|
- `WeatherRuntime.setWeatherColorOpacity()` 切换到 typed 路径;
|
||||||
|
- particle property setter 未扩大;
|
||||||
|
- Metro `8081 -> 8091` 与 `8091 -> 8091` 双 reverse 纳入受控来源;
|
||||||
|
- landscape 配置保持;
|
||||||
|
- `13.4.1-step060f` 版本接线;
|
||||||
|
- Linux 精确验证;
|
||||||
|
- 中文文档更新;
|
||||||
|
- Git commit/push;
|
||||||
|
- AAR/APK/ADB 未由 Codex 执行;
|
||||||
|
- 未进入 STEP-061。
|
||||||
|
|
||||||
|
## 14. 停止条件与后续人工链路
|
||||||
|
|
||||||
|
Codex 成功 push 后立即停止,不得自行进入 Android artifact/runtime 阶段。
|
||||||
|
|
||||||
|
后续固定顺序:
|
||||||
|
|
||||||
|
1. ChatGPT 审核 `particle/main` 远端实际源码与 diff;
|
||||||
|
2. 审核通过后,用户在 Linux 手工构建并 publish `13.4.1-step060f` OpenGL Release AAR;
|
||||||
|
3. Linux 记录 build AAR 与 Maven Local AAR SHA256,必须一致;
|
||||||
|
4. CARBON7i Git pull 最新 Test Host 源码;
|
||||||
|
5. AAR 临时复制到 `/mnt/nas/pbf/`,Win11 使用 `curl.exe -L` 从 `https://pbf.jpcn.ccwu.cc/` 下载;
|
||||||
|
6. 下载 URL 必须使用每次不同的 `?v=<cache-busting-value>` 防止 CDN cache;
|
||||||
|
7. Win11 SHA256 必须与 Linux 完全一致;
|
||||||
|
8. 安装 Win11 Maven Local;
|
||||||
|
9. Bridge 与 MLRN 两条 `dependencyInsight` 必须共同解析到 `13.4.1-step060f`;
|
||||||
|
10. 用户手工 `assembleDebug`;
|
||||||
|
11. 使用修正后的启动流程安装/启动;
|
||||||
|
12. Android 最终验收。
|
||||||
|
|
||||||
|
最终 Android hard gate:
|
||||||
|
|
||||||
|
- App 锁定横屏;
|
||||||
|
- `WeatherRuntime attached`;
|
||||||
|
- `WEATHER_OBJECTS_NOT_READY` 不出现;
|
||||||
|
- `weather opacity: ACCEPTED`;
|
||||||
|
- 不再出现 `STYLE_PROPERTY_SET result=UNSUPPORTED_PROPERTY`;
|
||||||
|
- Phase A 基础命令继续工作;
|
||||||
|
- 无 field 时 `TIMELINE_NOT_READY` 仍允许存在。
|
||||||
|
|
||||||
|
只有上述最终 Android 结果通过后,才允许把 **STEP-060 标记为 CLOSED / PASS**,然后进入 **STEP-061 Binary U/V Field Transport**。
|
||||||
173
docs/40_任务/STEP-061_Binary_UV_Field_Transport_Codex任务_v1.md
Normal file
173
docs/40_任务/STEP-061_Binary_UV_Field_Transport_Codex任务_v1.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# STEP-061 Binary U/V Field Transport Codex任务 v1
|
||||||
|
|
||||||
|
## 1. 任务状态与目标
|
||||||
|
|
||||||
|
- 当前正式分支:`particle/main`。
|
||||||
|
- 本任务开始前必须确认本地 HEAD 与 `origin/particle/main` 一致。
|
||||||
|
- STEP-060 已 `CLOSED / PASS`,当前正式进入 STEP-061。
|
||||||
|
- 目标:打通 RN Test Host → WeatherRuntime → Android Java/JNI → RasterArraySource/Core 的正式 Binary U/V Field 提交链,并在 RN Host 中得到可验证的 Timeline / Runtime Field Window 证据。
|
||||||
|
- 本任务只实现 STEP-061 源码与静态验证;不要执行最终 AAR 发布、Windows Maven Local、APK、ADB 或 Android 实机验收,这些由任务完成后的端到端流程执行。
|
||||||
|
|
||||||
|
## 2. 开工前必读
|
||||||
|
|
||||||
|
必须先读取并遵守:
|
||||||
|
|
||||||
|
1. `AGENTS.md`
|
||||||
|
2. `PARTICLE_BASELINE.md`
|
||||||
|
3. `docs/00_项目规范/` 全部当前规范
|
||||||
|
4. `docs/50_Step记录/STEP-060_RNTestHost与WeatherRuntime薄桥接_v3.md`
|
||||||
|
5. `docs/报告/STEP-060F_Android最终验收报告_v1.md`
|
||||||
|
6. `docs/40_任务/STEP-053_Windy-style_Canonical_Field与Host_MapLibre_Submission_Contract_Codex任务_v1.md`
|
||||||
|
7. `docs/50_Step记录/STEP-053_Windy-style_Canonical_Field与Host_MapLibre_Submission_Contract_v1.md`
|
||||||
|
8. `docs/报告/STEP-053_Windy-style_Field与Host_Submission_Codex完成报告_v2.md`
|
||||||
|
9. `粒子开发记录.md` 最新相关记录
|
||||||
|
## 3. 已调查并冻结的实际基线
|
||||||
|
|
||||||
|
当前源码已经存在以下正式链路,禁止重复造第二套 Core ingress:
|
||||||
|
|
||||||
|
- `WeatherRuntime.submitWindField(String metadataJson, byte[] payload)`
|
||||||
|
- `MapLibreMap.submitRasterArraySourceHostField(...)`
|
||||||
|
- `NativeMapView` / JNI `jbyteArray`
|
||||||
|
- `RasterArraySource::submitHostField(metadata, payload)`
|
||||||
|
- `LocalParticleFieldFeed::load(...)`
|
||||||
|
- `ParticleFieldDataAdapter::decode(...)`
|
||||||
|
|
||||||
|
Canonical Field Contract 继续沿用 STEP-053,不重新定义:
|
||||||
|
|
||||||
|
- schema:`maplibre-particle-field/v1`
|
||||||
|
- layout:`interleaved-uv`
|
||||||
|
- encoding:`float32-le`
|
||||||
|
- origin:north-west
|
||||||
|
- storage:row-major
|
||||||
|
- U:eastward m/s
|
||||||
|
- V:northward m/s
|
||||||
|
- payload byteLength 必须与 width × height × 2 × sizeof(float) 一致。
|
||||||
|
- `generation`、`validTime` 必须非零并满足 Timeline 单调性。
|
||||||
|
|
||||||
|
RN Test Host 当前为 React Native `0.85.2`,`@react-native/codegen 0.85.3`,Expo `56.0.4`;当前 Codegen Spec 不提供可直接映射为 Kotlin `byte[]` 的 ArrayBuffer/Uint8Array 参数类型。Expo FileSystem `56.0.11` 已存在并支持 `File.write(string | Uint8Array)`。
|
||||||
|
## 4. 本阶段正式 Binary Transport 设计
|
||||||
|
|
||||||
|
本阶段固定采用 **binary file-reference transport**:
|
||||||
|
|
||||||
|
1. JS/TS 用 `ArrayBuffer + DataView` 或等价方式构造 canonical U/V bytes,并显式以 little-endian 写入 float32。
|
||||||
|
2. JS/TS 用 Expo FileSystem `File.write(Uint8Array)` 把原始 bytes 写入 App 本地临时文件。
|
||||||
|
3. TurboModule 公共调用只传递 `metadataJson + payloadFileUri/path`,不把 U/V 数组塞进 JSON。
|
||||||
|
4. Android bridge 从本地文件引用读取原始 `byte[]`,然后调用既有 `WeatherRuntime.submitWindField(metadataJson, byte[])`。
|
||||||
|
5. Core 仍由既有 `LocalParticleFieldFeed` / `ParticleFieldDataAdapter` 做最终 schema、维度、bounds、layout、encoding、byteLength、generation、validTime 等验证并 fail-closed。
|
||||||
|
|
||||||
|
该设计是正式 Binary U/V transport,不是临时编码兼容层。禁止:
|
||||||
|
|
||||||
|
- `number[]` / `Array<number>` 跨 RN bridge 传 U/V payload;
|
||||||
|
- 把 payload 放入 JSON;
|
||||||
|
- base64;
|
||||||
|
- hex/string 编码;
|
||||||
|
- provider/network/vendor 下载逻辑进入 MapLibre Native/Core;
|
||||||
|
- 新建第二套 RasterArray/Timeline/Field Adapter;
|
||||||
|
- 绕过 `WeatherRuntime.submitWindField(...)` 直接写 Core 内部状态。
|
||||||
|
|
||||||
|
本阶段不要求 JSI zero-copy;如果未来需要纯内存/zero-copy buffer transport,应另开性能 Step,不在这里叠加复杂度。
|
||||||
|
## 5. 必做实现
|
||||||
|
|
||||||
|
### 5.1 RN bridge API
|
||||||
|
|
||||||
|
在现有 `weather-runtime-bridge` 上最小扩展,不新建第二个模块:
|
||||||
|
|
||||||
|
- 增加一个稳定的 Binary Field 提交 API,参数语义为 `metadataJson + payloadFileUri/path`。
|
||||||
|
- 返回值必须能让 RN Host 看到 `WeatherRuntime.FieldSubmitResult` 的结构化结果名称或等价稳定字符串。
|
||||||
|
- 可以同时增加一个最小 `clearWindFields()`/reset API,若它对重复验收和 Fast Refresh 后清空 Timeline 是必要的;必须继续调用既有 `WeatherRuntime.clearWindFields()`,不得自己管理 Timeline。
|
||||||
|
- Kotlin 侧只负责:解析本地引用、读取原始 bytes、做必要的文件可读性/大小保护、调用 `WeatherRuntime.submitWindField()`、归一化返回值。
|
||||||
|
- 文件读取失败必须 fail-closed,不能提交空 payload,也不能伪造成功。
|
||||||
|
|
||||||
|
### 5.2 RN Test Host canonical fixture
|
||||||
|
|
||||||
|
在 Test Host 内生成确定性、很小但有效的至少两帧 canonical WIND Field:
|
||||||
|
|
||||||
|
- T0 / T1 grid 的 width、height、bounds、layout、encoding、noData 完全兼容。
|
||||||
|
- T1 的 `validTime` 与 `generation` 严格大于 T0。
|
||||||
|
- payload 使用 `DataView.setFloat32(offset, value, true)` 或等价明确 little-endian 的方法写 U/V。
|
||||||
|
- 写成原始二进制临时文件;禁止 base64 或数组 JSON。
|
||||||
|
- metadata 必须包含 Core 当前真实 parser 要求的 schema、fieldId、fieldType、displayDomain、generation、validTime、grid、bounds、vector、payload.byteLength 等字段。
|
||||||
|
- Host 不得计算或持有 T0/T1/MIX Runtime 状态;它只生产并提交帧,窗口选择继续由 Core 管理。
|
||||||
|
### 5.3 正向与负向证据
|
||||||
|
|
||||||
|
RN Host 必须提供无需猜测的验收状态,至少包括:
|
||||||
|
|
||||||
|
- Binary Transport:READY / ERROR;
|
||||||
|
- payload 形式:明确显示 `FILE_REF / FLOAT32_LE / INTERLEAVED_UV` 或等价语义;
|
||||||
|
- Invalid Probe:提交一个确定性非法 payload(优先 byteLength/实际 bytes 不匹配),必须得到 Core 的拒绝结果;
|
||||||
|
- T0 Submit Result:应为 `ACCEPTED_PENDING_INITIAL_WINDOW`;
|
||||||
|
- T1 Submit Result:应为 `INITIAL_WINDOW_READY`;
|
||||||
|
- Runtime Field Window:最终不再是 `TIMELINE_NOT_READY`,应能显示来自 Native/Core 的 T0/T1/MIX/generation/time 等已有 runtime window 证据。
|
||||||
|
|
||||||
|
负向 probe 只能验证既有 Core fail-closed,不得为了让 probe 通过去放宽 Core 校验。
|
||||||
|
|
||||||
|
### 5.4 依赖与工程
|
||||||
|
|
||||||
|
- `expo-file-system` 当前实际安装版本为 `56.0.11`;如果源码直接 import,必须把它作为 Test Host 的明确直接依赖并保持与当前 Expo 56 兼容,不升级整个 Expo/RN 栈。
|
||||||
|
- 只修改完成本任务真正必要的 RN Test Host / bridge 文件。
|
||||||
|
- 默认不修改 MapLibre Core;只有调查证明现有 STEP-053/057 WeatherRuntime contract 存在真实阻塞缺陷时才能做最小修正,并在完成报告中单独说明原因和影响。
|
||||||
|
- 不进入 NavSea,不修改任何 NavSea 文件。
|
||||||
|
## 6. 允许修改范围
|
||||||
|
|
||||||
|
优先允许:
|
||||||
|
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/App.tsx`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/package.json`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/package-lock.json`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/README.md`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/src/**`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/**` 中源码和必要配置;禁止提交 `build/` 产物。
|
||||||
|
- 本任务要求的中文完成报告。
|
||||||
|
|
||||||
|
默认禁止:
|
||||||
|
|
||||||
|
- provider/network/vendor/Weather Service/HTTP/download/cache/prefetch 业务代码进入 Native/Core;
|
||||||
|
- 修改 NavSea;
|
||||||
|
- 修改与 STEP-061 无关的 UI、粒子视觉参数、地图行为;
|
||||||
|
- 大范围重构 STEP-053~060 已冻结链路;
|
||||||
|
- 提交现有历史 untracked 文件、build 目录、临时日志、AAR/APK。
|
||||||
|
|
||||||
|
若必须超出允许范围,先停止并在完成报告中标为阻塞,不要自行扩大任务。
|
||||||
|
## 7. 验证要求
|
||||||
|
|
||||||
|
Codex 完成源码后必须执行并记录:
|
||||||
|
|
||||||
|
1. `npm run typecheck`(RN Test Host)。
|
||||||
|
2. 当前 bridge Codegen Spec 的必要生成/静态检查;只做验证,不提交生成的 build 产物。
|
||||||
|
3. 对修改文件执行必要的语法/静态检查。
|
||||||
|
4. `git diff --check`。
|
||||||
|
5. 明确检索并证明正式提交链中不存在以 `number[]`、base64、payload JSON 传输 U/V 的实现。
|
||||||
|
|
||||||
|
未经用户明确要求,本任务**不加入 Jest,不新增 Jest 测试,不运行 Jest**。
|
||||||
|
|
||||||
|
本阶段不要运行:
|
||||||
|
|
||||||
|
- `make apackage`;
|
||||||
|
- 最终 AAR Maven publish;
|
||||||
|
- Windows Gradle APK build;
|
||||||
|
- ADB 安装或实机测试。
|
||||||
|
|
||||||
|
AAR 编译与端到端 Android 验收将在 Codex 源码审查通过后,于同一个 `particle-codex:step061` tmux window 中继续执行。
|
||||||
|
|
||||||
|
编译/Codegen 等 noisy 命令遵循全局 `$build-output-throttle`。
|
||||||
|
## 8. 必做输出
|
||||||
|
|
||||||
|
完成时必须实际生成:
|
||||||
|
|
||||||
|
1. STEP-061 所需源码修改。
|
||||||
|
2. 必要的 Test Host README/bridge contract 说明,明确 binary file-reference transport、canonical bytes、调用链和限制。
|
||||||
|
3. `docs/报告/STEP-061_Binary_UV_Field_Transport_Codex完成报告_v1.md`。
|
||||||
|
|
||||||
|
完成报告至少写明:
|
||||||
|
|
||||||
|
- 调查后的真实实现路径;
|
||||||
|
- 修改文件清单;
|
||||||
|
- RN binary file-reference transport 如何避免 number[] / base64;
|
||||||
|
- Kotlin 如何把原始 bytes 交给 `WeatherRuntime.submitWindField()`;
|
||||||
|
- invalid probe、T0、T1、Runtime Field Window 的预期结果;
|
||||||
|
- 实际执行的静态验证及结果;
|
||||||
|
- 未执行的 AAR / Windows / Android 实机项目;
|
||||||
|
- 是否修改 Core;若修改,必须逐项说明必要性;
|
||||||
|
- `git status --short` 与当前未提交范围摘要。
|
||||||
|
|
||||||
|
不要 commit,不要 push。完成后停在工作区,等待 ChatGPT 通过 RDC 审查真实 diff。
|
||||||
272
docs/40_任务/STEP-061_新窗口交接与端到端执行流程_v1.md
Normal file
272
docs/40_任务/STEP-061_新窗口交接与端到端执行流程_v1.md
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
# STEP-061 新窗口交接与端到端执行流程 v1
|
||||||
|
|
||||||
|
## 1. 文档目的
|
||||||
|
|
||||||
|
本文件用于在新的 ChatGPT 会话窗口中无缝继续 MapLibreParticleLab / 粒子系统开发。
|
||||||
|
新窗口不得依赖旧聊天记录猜测状态;应先读取本文件,再读取仓库中的实际基线与最新 Step 记录。
|
||||||
|
|
||||||
|
本文件同时固定从“写 STEP-061 Codex 任务 MD”到“Codex 实现、源码审查、AAR、Win11 Maven、APK、Android 实机验收、Git 收尾”的完整协作流程。
|
||||||
|
|
||||||
|
## 2. 当前冻结基线
|
||||||
|
|
||||||
|
- Linux repo:`/mnt/sda1/MapLibreParticleLab`
|
||||||
|
- branch:`particle/main`
|
||||||
|
- STEP-060 closeout baseline commit:`3d34ec7628d6b429d223a9022f05a9192a0a8d4d`
|
||||||
|
- 新窗口开始时必须实时查询 `particle/main` 的 local HEAD / remote HEAD;不得把本文件中的历史 commit 当作当前 HEAD
|
||||||
|
- STEP-060:`CLOSED / PASS`
|
||||||
|
- STEP-060 最终文档:`docs/50_Step记录/STEP-060_RNTestHost与WeatherRuntime薄桥接_v3.md`
|
||||||
|
- STEP-060F Android 最终验收:`docs/报告/STEP-060F_Android最终验收报告_v1.md`
|
||||||
|
- 当前最后验证 AAR:`org.maplibre.gl:android-sdk-opengl:13.4.1-step060f`
|
||||||
|
- 最后验证 AAR SHA256:`53dfcd43f3479ae43a3c0fd91c10ccbfec7cf1c84ae22ae3435c94393806da6d`
|
||||||
|
|
||||||
|
STEP-060 已实测:横屏、双 Metro reverse、WeatherRuntime attach、CoreOnly source/layer 真值链、WeatherColor opacity typed setter 均通过。
|
||||||
|
无 Binary Field 时 `TIMELINE_NOT_READY` 仍是预期 fail-closed。
|
||||||
|
## 3. 新窗口第一步必须读取
|
||||||
|
|
||||||
|
依次读取并以实际仓库内容为准:
|
||||||
|
|
||||||
|
1. `AGENTS.md`
|
||||||
|
2. `PARTICLE_BASELINE.md`
|
||||||
|
3. `docs/50_Step记录/STEP-060_RNTestHost与WeatherRuntime薄桥接_v3.md`
|
||||||
|
4. `docs/报告/STEP-060F_Android最终验收报告_v1.md`
|
||||||
|
5. `粒子开发记录.md`
|
||||||
|
6. STEP-053 Host → MapLibre submission contract 相关任务/记录
|
||||||
|
7. 当前与 Binary U/V / Canonical Field / Runtime Field Window 相关源码
|
||||||
|
|
||||||
|
若本文件中的 HEAD 已落后于远端,先以 `origin/particle/main` 的最新实际代码重新确认差异,不得用旧聊天内容覆盖新提交。
|
||||||
|
|
||||||
|
## 4. STEP-061 架构边界
|
||||||
|
|
||||||
|
STEP-061 才允许开始 Binary U/V Field Transport。
|
||||||
|
正式数据获取、Weather Service、HTTP/provider、下载、重试、缓存、预取和业务调度属于最终宿主 React Native NavSea 的 JS/TS 层。
|
||||||
|
MapLibre Native/C++ 不得引入 provider、network、vendor 或天气业务获取逻辑。
|
||||||
|
|
||||||
|
底层 field 采用 Windy-style 二维规则网格向量场:metadata + U/V payload。
|
||||||
|
宿主通过稳定的 Host → MapLibre 接口提交已取得并规范化的数据;不得把大 field 作为 `number[]` JSON 数组或 base64 方案长期传输。
|
||||||
|
C++ 负责接收、校验、fail-closed、ParticleFieldSnapshot、Timeline、Runtime Field Window、GPU simulation/rendering。
|
||||||
|
WeatherColorLayer 与 RasterParticleLayer 继续分离,现有 CoreOnly 架构不得倒退。
|
||||||
|
## 5. 固定 tmux / Codex 工作流
|
||||||
|
|
||||||
|
所有粒子系统 Codex 操作统一使用固定 session:`particle-codex`。
|
||||||
|
不要为新任务创建第二个 tmux session;每个独立 Step 新建一个 window。
|
||||||
|
|
||||||
|
当前历史窗口:
|
||||||
|
- `0: manual`
|
||||||
|
- `1: step060f`
|
||||||
|
|
||||||
|
STEP-061 使用新 window,例如:`step061`。
|
||||||
|
建议命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tmux new-window -t particle-codex -n step061 -c /mnt/sda1/MapLibreParticleLab
|
||||||
|
```
|
||||||
|
|
||||||
|
用户查看:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@192.168.200.184
|
||||||
|
tmux attach -t particle-codex
|
||||||
|
```
|
||||||
|
|
||||||
|
只读查看:`tmux attach -r -t particle-codex`。
|
||||||
|
ChatGPT/RDC 可用 `tmux capture-pane -pt particle-codex:step061` 读取执行状态。
|
||||||
|
SSH 断开不影响 tmux 中的 Codex、编译或 shell 任务。
|
||||||
|
## 6. Codex CLI 固定方式
|
||||||
|
|
||||||
|
当前可用 Codex:`codex-cli 0.153.4`。
|
||||||
|
已验证启动方式:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
codex -C /mnt/sda1/MapLibreParticleLab \
|
||||||
|
-m gpt-5.6-terra \
|
||||||
|
-s workspace-write \
|
||||||
|
-a never \
|
||||||
|
--no-alt-screen \
|
||||||
|
"完整阅读并执行 <STEP-061任务MD>;严格遵守任务范围。"
|
||||||
|
```
|
||||||
|
|
||||||
|
默认使用 medium reasoning;需要改变模型/权限时必须先明确说明原因。
|
||||||
|
`workspace-write` 可以改源码,但 Codex sandbox 已实测会把 `.git` 视为只读,无法可靠创建 `.git/index.lock` 或写 `FETCH_HEAD`。
|
||||||
|
因此新固定流程是:
|
||||||
|
|
||||||
|
- Codex:实现源码、允许范围内的静态/精确验证、中文文档和完成报告;
|
||||||
|
- Codex 不得伪称 commit/push;报告写 `GIT_CLOSEOUT_BY_RDC_AFTER_REVIEW`;
|
||||||
|
- ChatGPT:读取真实 diff 并审查;
|
||||||
|
- 审查通过后,由 ChatGPT 通过 RDC 执行精准 `git add`、commit、push;
|
||||||
|
- 不得把历史 untracked 文件顺手加入提交。
|
||||||
|
|
||||||
|
除非用户明确要求,不写/跑 Jest。Android APK 编译仍由用户手工执行;AAR 编译可按本文件授权由 ChatGPT/RDC 在 tmux 中执行。
|
||||||
|
## 7. AAR 构建固定流程
|
||||||
|
|
||||||
|
只有 Codex 源码完成、ChatGPT 审查通过、source commit 已 push 后,才进入 AAR。
|
||||||
|
AAR 在 STEP 对应的同一个 tmux window 中执行,不新建第二个编译 window。
|
||||||
|
|
||||||
|
**JOBS 必须固定为 1。**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /mnt/sda1/MapLibreParticleLab/platform/android
|
||||||
|
JOBS=1 RENDERER=opengl BUILDTYPE=Release make apackage
|
||||||
|
```
|
||||||
|
|
||||||
|
必须确认输出含 `--max-workers=1`,并最终 `BUILD SUCCESSFUL`。
|
||||||
|
随后在同一 window:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :MapLibreAndroid:publishOpenglreleasePublicationToMavenLocal --console=plain
|
||||||
|
```
|
||||||
|
|
||||||
|
再比较 build output 与 Maven Local AAR:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sha256sum \
|
||||||
|
MapLibreAndroid/build/outputs/aar/MapLibreAndroid-opengl-release.aar \
|
||||||
|
/root/.m2/repository/org/maplibre/gl/android-sdk-opengl/<VERSION>/android-sdk-opengl-<VERSION>.aar
|
||||||
|
```
|
||||||
|
|
||||||
|
两个 SHA256 必须完全一致,否则停止后续 Win11 流程。
|
||||||
|
## 8. AAR → Win11 传输固定流程
|
||||||
|
|
||||||
|
Linux 将新版本 AAR/POM/module 临时复制到 `/mnt/nas/pbf`,该目录对应公网根目录 `https://pbf.jpcn.ccwu.cc/`。
|
||||||
|
只作为临时交接,Win11 验证完成后立即删除公开文件。
|
||||||
|
|
||||||
|
Win11 固定设备:CARBON7i。
|
||||||
|
下载目录:`D:\React\MapLibreParticleAAR\<VERSION>\`。
|
||||||
|
必须使用 `curl.exe -L`,并在 URL 后加每次不同的 `?v=` 防止 CDN/cache 命中旧产物,例如:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
curl.exe -L --retry 5 --retry-delay 2 `
|
||||||
|
-o "D:\React\MapLibreParticleAAR\<VERSION>\android-sdk-opengl-<VERSION>.aar" `
|
||||||
|
"https://pbf.jpcn.ccwu.cc/android-sdk-opengl-<VERSION>.aar?v=<NEW_VALUE>"
|
||||||
|
```
|
||||||
|
|
||||||
|
POM/module 同样使用新 `?v=` 下载。
|
||||||
|
然后:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-FileHash "D:\React\MapLibreParticleAAR\<VERSION>\android-sdk-opengl-<VERSION>.aar" -Algorithm SHA256
|
||||||
|
```
|
||||||
|
|
||||||
|
Win11 SHA256 必须与 Linux 一致。
|
||||||
|
一致后复制三件套到:
|
||||||
|
`C:\Users\taibe\.m2\repository\org\maplibre\gl\android-sdk-opengl\<VERSION>\`。
|
||||||
|
再次核对 Maven Local AAR SHA256。
|
||||||
|
## 9. Win11 源码同步与 Test Host
|
||||||
|
|
||||||
|
Git sparse 工作副本:
|
||||||
|
`D:\React\MapLibreParticleLab-Win11-SYNC\integration\react-native\MapLibreParticleRNTestHost`
|
||||||
|
|
||||||
|
当前实际运行 Test Host:
|
||||||
|
`D:\React\MapLibreParticleRNTestHost-Win11-STEP060-20260907`
|
||||||
|
|
||||||
|
固定顺序:
|
||||||
|
|
||||||
|
1. Win11 sparse repo `git pull --ff-only origin particle/main`;
|
||||||
|
2. 以 Git 工作副本为来源同步本 Step 受控 Test Host 源文件到 legacy runtime 目录;
|
||||||
|
3. 不覆盖 legacy 目录中已验证且本 Step 无关的生成环境;
|
||||||
|
4. 若新 Step 修改 Expo plugin/nativeVersion,则同步更新当前生成 `android/build.gradle` 的对应临时版本;
|
||||||
|
5. 保持 `app.json` landscape,并确认生成 Manifest 仍是 `android:screenOrientation="landscape"`;
|
||||||
|
6. `05-install-and-run.ps1` 必须保留两条 reverse:`8091 -> 8091` 与 `8081 -> 8091`。
|
||||||
|
|
||||||
|
Metro 固定 `8091`,不要启动第二个 Metro,也不要改成 8081。
|
||||||
|
|
||||||
|
Win11 Gradle 基线:
|
||||||
|
`C:\Users\taibe\.gradle\wrapper\dists\gradle-8.14.3-bin\cv11ve7ro1n3o1j4so8xd9n66\gradle-8.14.3\bin\gradle.bat`
|
||||||
|
|
||||||
|
依赖验收必须分别检查 Bridge 与 MLRN,两者都只能解析到当前 Step 的 `<VERSION>`。
|
||||||
|
建议在 legacy Test Host `android` 目录执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$g = 'C:\Users\taibe\.gradle\wrapper\dists\gradle-8.14.3-bin\cv11ve7ro1n3o1j4so8xd9n66\gradle-8.14.3\bin\gradle.bat'
|
||||||
|
& $g :maplibre-particle_weather-runtime-bridge:dependencyInsight --configuration debugRuntimeClasspath --dependency org.maplibre.gl:android-sdk-opengl --console=plain --no-daemon --no-watch-fs
|
||||||
|
& $g :maplibre_maplibre-react-native:dependencyInsight --configuration debugRuntimeClasspath --dependency org.maplibre.gl:android-sdk-opengl --console=plain --no-daemon --no-watch-fs
|
||||||
|
```
|
||||||
|
|
||||||
|
两条输出必须共同出现 `org.maplibre.gl:android-sdk-opengl:<VERSION>`,且均 `BUILD SUCCESSFUL`。
|
||||||
|
|
||||||
|
## 10. APK 编译边界
|
||||||
|
|
||||||
|
Android APK 编译默认仍由用户手工执行;ChatGPT/RDC 不自行触发 `assembleDebug`,除非用户在当次任务中再次明确授权。
|
||||||
|
固定命令:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd D:\React\MapLibreParticleRNTestHost-Win11-STEP060-20260907\android
|
||||||
|
C:\Users\taibe\.gradle\wrapper\dists\gradle-8.14.3-bin\cv11ve7ro1n3o1j4so8xd9n66\gradle-8.14.3\bin\gradle.bat `
|
||||||
|
:app:assembleDebug `
|
||||||
|
--console=plain `
|
||||||
|
--no-daemon `
|
||||||
|
--no-watch-fs `
|
||||||
|
-PreactNativeArchitectures=x86_64
|
||||||
|
```
|
||||||
|
|
||||||
|
只有用户返回 `BUILD SUCCESSFUL` 后才进入安装和设备验收。
|
||||||
|
## 11. Android 安装与运行时验收
|
||||||
|
|
||||||
|
Android 测试设备:`192.168.1.146`。
|
||||||
|
所有 ADB 命令必须显式带:`adb -s 192.168.1.146 ...`。
|
||||||
|
|
||||||
|
优先使用 legacy Test Host:
|
||||||
|
`D:\React\MapLibreParticleRNTestHost-Win11-STEP060-20260907\scripts\05-install-and-run.ps1`
|
||||||
|
|
||||||
|
该脚本负责:设备检查、`8091 -> 8091`、`8081 -> 8091`、install -r、force-stop、启动 `.MainActivity`。
|
||||||
|
安装后先确认:
|
||||||
|
|
||||||
|
- Activity 为 `SCREEN_ORIENTATION_LANDSCAPE`;
|
||||||
|
- Metro bundle 正常;
|
||||||
|
- `WeatherRuntime attached`;
|
||||||
|
- 无 `WEATHER_OBJECTS_NOT_READY`、JNI crash、SIGSEGV、FATAL EXCEPTION。
|
||||||
|
|
||||||
|
STEP-061 的 Binary U/V 具体验收必须由正式 STEP-061 任务 MD 明确,包括提交 payload 后的 Runtime/Field Window/Timeline 预期状态。
|
||||||
|
原则上必须证明 binary ingress 实际进入 Native/Core,而不是只验证 JS 参数接受。
|
||||||
|
若任务要求完整控制 UI,可使用现有 `07-enable-full-validation-ui.ps1`,测试完成后必须执行 `08-restore-baseline-ui.ps1`。
|
||||||
|
|
||||||
|
运行时结果应同时读取 UI result、runtime snapshot 与当前 App PID 的 logcat;不得只凭屏幕视觉判断。
|
||||||
|
测试失败时先判断是 Host/Bridge、JNI、Core field validation、Timeline 还是 renderer,不得在 NavSea 层增加兼容逻辑掩盖上游问题。
|
||||||
|
## 12. 临时公开文件清理
|
||||||
|
|
||||||
|
Win11 AAR/POM/module 下载和 SHA 验证完成后,Linux 必须删除 `/mnt/nas/pbf` 中本 Step 临时公开的三件套。
|
||||||
|
不得把 AAR 放进 Git。
|
||||||
|
|
||||||
|
## 13. 每个 STEP 的完整顺序
|
||||||
|
|
||||||
|
新窗口必须按下列顺序推进,不跳步:
|
||||||
|
|
||||||
|
1. 读取本交接 MD + 实际仓库基线;
|
||||||
|
2. 调查 STEP-061 当前源码与边界;
|
||||||
|
3. 生成中文 STEP-061 Codex 任务 MD;
|
||||||
|
4. 先 commit/push 任务 MD;
|
||||||
|
5. 在 `particle-codex` 新建 `step061` window;
|
||||||
|
6. 在该 window 启动新 Codex CLI;
|
||||||
|
7. Codex 完成源码/文档/允许验证后退出到 shell;
|
||||||
|
8. ChatGPT 审查真实 diff;
|
||||||
|
9. RDC 精准 commit/push source changes;
|
||||||
|
10. 同一 tmux window 以 `JOBS=1` 构建 AAR;
|
||||||
|
11. Maven Local publish + Linux 双 SHA;
|
||||||
|
12. `/mnt/nas/pbf` 临时发布;
|
||||||
|
13. CARBON7i `curl.exe -L ?v=<new>` 下载 + SHA;
|
||||||
|
14. Win11 Maven Local + 两条 dependencyInsight;
|
||||||
|
15. 用户手工 APK build;
|
||||||
|
16. ChatGPT/RDC 安装、ADB、UI/logcat 实测;
|
||||||
|
17. 恢复 baseline validation UI;
|
||||||
|
18. 删除 `/mnt/nas/pbf` 临时文件;
|
||||||
|
19. 写最终 Step/验收报告/开发记录;
|
||||||
|
20. commit/push 最终文档并核对 local/remote HEAD。
|
||||||
|
## 14. 关键停止条件
|
||||||
|
|
||||||
|
出现以下任一情况必须停止后续打包/设备流程并先定位:
|
||||||
|
|
||||||
|
- Codex 修改越过 STEP-061 边界;
|
||||||
|
- 引入 provider/network/vendor 逻辑到 MapLibre Native/Core;
|
||||||
|
- 采用大规模 `number[]` JSON/base64 作为正式 binary field transport;
|
||||||
|
- source review 未通过却直接 build AAR;
|
||||||
|
- Linux build/Maven Local/Win11 SHA 不一致;
|
||||||
|
- Bridge 与 MLRN dependencyInsight 解析到不同 AAR 或旧版本;
|
||||||
|
- Android build 失败;
|
||||||
|
- 设备出现 crash/JNI/SIGSEGV 或 field fail-open;
|
||||||
|
- 未完成 Android hard gate 就提前把 STEP 标记 CLOSED/PASS。
|
||||||
|
|
||||||
|
## 15. 新窗口开始语义
|
||||||
|
|
||||||
|
用户在新 ChatGPT 窗口只需说明:
|
||||||
|
“继续 MapLibreParticleLab STEP-061,先读取 `docs/40_任务/STEP-061_新窗口交接与端到端执行流程_v1.md` 并按实际仓库状态执行。”
|
||||||
|
|
||||||
|
新窗口收到后应直接读取此文件和仓库,不要求用户重新复述 AAR、tmux、Win11、ADB 或 STEP-060 历史。
|
||||||
346
docs/40_任务/STEP-062_RNTestHost真实WeatherV2底图粒子综合验证_Codex任务_v1.md
Normal file
346
docs/40_任务/STEP-062_RNTestHost真实WeatherV2底图粒子综合验证_Codex任务_v1.md
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
# STEP-062 RN Test Host 真实 WeatherV2 底图粒子综合验证 — Codex任务 v1
|
||||||
|
|
||||||
|
## 1. 任务定位
|
||||||
|
|
||||||
|
当前正式分支:`particle/main`。
|
||||||
|
|
||||||
|
开工基线:
|
||||||
|
|
||||||
|
- STEP-061 已正式 `CLOSED / PASS`。
|
||||||
|
- 当前 local / remote HEAD 均为 `729d22169da5cb2d5a4eafbded8dde894739dee6`。
|
||||||
|
- Native/Core 冻结制品:`org.maplibre.gl:android-sdk-opengl:13.4.1-step061`。
|
||||||
|
- STEP-062 不进入 NavSea,只在 `MapLibreParticleRNTestHost` 内完成接近最终使用形态的天气地图综合验证。
|
||||||
|
|
||||||
|
本阶段目标不是继续扩展 AAR,而是证明:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GSI 底图
|
||||||
|
+
|
||||||
|
真实 WeatherV2 canonical T0/T1/T2/T3
|
||||||
|
+
|
||||||
|
WeatherColorLayer
|
||||||
|
+
|
||||||
|
RasterParticleLayer
|
||||||
|
+
|
||||||
|
Timeline / Playback
|
||||||
|
```
|
||||||
|
|
||||||
|
在 React Native Test Host APK 中可以稳定共同工作。
|
||||||
|
|
||||||
|
## 2. 最高优先级边界
|
||||||
|
|
||||||
|
本任务默认**禁止修改和禁止重新构建 AAR**:
|
||||||
|
|
||||||
|
- 不修改 `src/`、`include/` 下 MapLibre Native/Core 粒子实现;
|
||||||
|
- 不修改 `platform/android/MapLibreAndroid`;
|
||||||
|
- 不修改 JNI;
|
||||||
|
- 不修改 `WeatherRuntime.java`;
|
||||||
|
- 不修改 STEP-061 Binary ingress;
|
||||||
|
- 不执行 `make apackage`;
|
||||||
|
- 不执行 Maven Local publish;
|
||||||
|
- 不生成新的 `13.4.1-step062` AAR;
|
||||||
|
- 不进入 NavSea。
|
||||||
|
|
||||||
|
Test Host 继续固定消费已经通过实机验收的:
|
||||||
|
|
||||||
|
```text
|
||||||
|
org.maplibre.gl:android-sdk-opengl:13.4.1-step061
|
||||||
|
```
|
||||||
|
|
||||||
|
如果实现或 Android 验收证明 `step061` Native/Core 存在真实阻塞缺陷,必须停止扩大 STEP-062,并在报告中明确 `BLOCKED_BY_NATIVE`;不得为了方便顺手改 Core 后重出 AAR。
|
||||||
|
|
||||||
|
## 3. 开工前必读与真实基线
|
||||||
|
|
||||||
|
必须先读取并遵守:
|
||||||
|
|
||||||
|
1. `AGENTS.md`
|
||||||
|
2. `PARTICLE_BASELINE.md`
|
||||||
|
3. `docs/00_项目规范/` 当前规范
|
||||||
|
4. `docs/50_Step记录/STEP-061_Binary_UV_Field_Transport_v2.md`
|
||||||
|
5. `docs/报告/STEP-061_Android最终验收报告_v1.md`
|
||||||
|
6. `docs/50_Step记录/STEP-055_Android真实WeatherV2风场综合可视化_v7.md`
|
||||||
|
7. `docs/20_调查/STEP-056_RN天气渲染桥接现状调查_v1.md`
|
||||||
|
8. `integration/react-native/MapLibreParticleRNTestHost/` 当前实际源码
|
||||||
|
9. Android Validation 中当前 GSI / REAL WEATHER 的实际实现,不得凭记忆重写。
|
||||||
|
|
||||||
|
当前已确认事实:
|
||||||
|
|
||||||
|
- RN Test Host 的底图目前只是 `background-color: #10243a`,没有真实地图瓦片。
|
||||||
|
- Android Validation 已使用 GSI 标准地图:`https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png`,tileSize=256,maxzoom=18。
|
||||||
|
- 当前真实 WeatherV2 数据为 ECMWF HRES 2026-08-07 四帧:`2026080700/06/12/18`。
|
||||||
|
- 每帧 grid=`513×769`,payload=`3,155,976 bytes`。
|
||||||
|
- bounds=`west 112.5 / south 0.0 / east 157.5 / north 55.77657301866769`。
|
||||||
|
- canonical contract 仍是 `maplibre-particle-field/v1`、`float32-le`、`interleaved-uv`、U eastward、V northward。
|
||||||
|
- RN bridge 已有 `submitWindField`、forecast time、PLAY/PAUSE、playback speed、WeatherColor stops、particle/trail color、粒子视觉参数、targetFps、getState;本阶段不新增 Native API。
|
||||||
|
|
||||||
|
## 4. 真实 WeatherV2 资产
|
||||||
|
|
||||||
|
源资产固定来自:
|
||||||
|
|
||||||
|
```text
|
||||||
|
platform/android/MapLibreAndroidTestApp/src/main/assets/integration/particle-weatherv2/
|
||||||
|
ecmwf-hres-wind-20260807/
|
||||||
|
```
|
||||||
|
|
||||||
|
把本阶段实际需要的 `dataset.json`、四份 `metadata.json`、四份 `field.f32` **原样复制**到 RN Test Host 自己的受控 assets 目录,例如:
|
||||||
|
|
||||||
|
```text
|
||||||
|
integration/react-native/MapLibreParticleRNTestHost/assets/weather/
|
||||||
|
ecmwf-hres-wind-20260807/...
|
||||||
|
```
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 不重新生成、不重采样、不裁剪、不改 metadata;
|
||||||
|
- 不把 `.f32` 转成 JSON、base64、hex 或 `number[]`;
|
||||||
|
- 复制后逐文件 SHA-256 与源资产一致;
|
||||||
|
- `field.f32` 内容必须与已有 Git blob 完全一致;
|
||||||
|
- 不创建第二套“近似 WeatherV2”测试数据。
|
||||||
|
|
||||||
|
## 5. RN 资产加载与 Binary transport
|
||||||
|
|
||||||
|
继续使用 STEP-061 的正式 file-reference transport,不新增第二条路径。
|
||||||
|
|
||||||
|
建议实现:
|
||||||
|
|
||||||
|
1. 将 `expo-asset` 作为 Test Host 的明确直接依赖,版本保持当前 Expo 56 兼容范围,不升级 Expo/RN。
|
||||||
|
2. 新增 `metro.config.js`,只补充 `.f32` 为 asset extension;不得改动无关 Metro 行为。
|
||||||
|
3. 新增一个小型 `realWeatherDataset.ts`(或等价文件),用静态 `require()` 注册四帧 `.f32` asset 与 metadata。
|
||||||
|
4. 通过 `Asset.fromModule(...).downloadAsync()` 获得 app 本地 `file://` URI。
|
||||||
|
5. metadata 继续以小型 JSON string 传入;payload 只传本地文件引用。
|
||||||
|
6. 不在 JS 中把 3.01 MiB payload materialize 成数值数组;不做 base64。
|
||||||
|
|
||||||
|
必须明确显示并验证:
|
||||||
|
|
||||||
|
```text
|
||||||
|
REAL DATA TRANSPORT = FILE_REF / FLOAT32_LE / INTERLEAVED_UV
|
||||||
|
```
|
||||||
|
|
||||||
|
若 Expo Asset 最终不能稳定给出本地文件 URI,先报告阻塞;不要退回 base64/number[]。
|
||||||
|
|
||||||
|
## 6. GSI 底图
|
||||||
|
|
||||||
|
将 RN Test Host 的纯色背景升级为真实 GSI 标准底图。
|
||||||
|
|
||||||
|
Style 最小结构固定为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
background
|
||||||
|
GSI raster basemap
|
||||||
|
WeatherColorLayer
|
||||||
|
RasterParticleLayer
|
||||||
|
```
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- raster source 使用 Android Validation 已验证 URL;
|
||||||
|
- source 写明合适 attribution(至少标明国土地理院 / GSI);
|
||||||
|
- tileSize=256,maxzoom=18;
|
||||||
|
- WeatherColor 和 Particle 必须在 GSI 之上;
|
||||||
|
- 不把 WeatherColor 或粒子烘焙进底图;
|
||||||
|
- 不增加 provider-specific Weather 逻辑。
|
||||||
|
|
||||||
|
使用 MLRN 11.3.6 当前真实 `Camera` API,初始视图应直接覆盖 WeatherV2 bounds 或合理聚焦日本及周边;不得让用户每次启动后手工从世界视图找数据。
|
||||||
|
|
||||||
|
页面至少显示:
|
||||||
|
|
||||||
|
- `Basemap: GSI READY / ERROR / LOADING`
|
||||||
|
- map/style loading error(若存在)
|
||||||
|
- 当前 weather dataset identity。
|
||||||
|
|
||||||
|
## 7. T0~T3 提交与 Timeline
|
||||||
|
|
||||||
|
新增一个明确的 `LOAD REAL WEATHER T0-T3` 综合入口。
|
||||||
|
|
||||||
|
每次执行前必须:
|
||||||
|
|
||||||
|
1. `clearWindFields()`;
|
||||||
|
2. 确认四帧本地 asset 已 materialize 为 file URI;
|
||||||
|
3. 按 validTime 严格递增提交 T0/T1/T2/T3;
|
||||||
|
4. 每帧提交后记录结构化结果;
|
||||||
|
5. 最终重新读取 Native `getState()`。
|
||||||
|
|
||||||
|
硬断言:
|
||||||
|
|
||||||
|
- T0 = `ACCEPTED_PENDING_INITIAL_WINDOW`
|
||||||
|
- T1 = `INITIAL_WINDOW_READY`
|
||||||
|
- T2 = `ACCEPTED_NEXT_SNAPSHOT`
|
||||||
|
- T3 = `ACCEPTED_NEXT_SNAPSHOT`
|
||||||
|
- 最终 Native state = `READY`
|
||||||
|
|
||||||
|
任一不符都显示 `ERROR`,不得把部分成功显示为 READY。
|
||||||
|
|
||||||
|
UI 至少显示:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Dataset: ECMWF HRES 20260807
|
||||||
|
T0 2026080700: <result>
|
||||||
|
T1 2026080706: <result>
|
||||||
|
T2 2026080712: <result>
|
||||||
|
T3 2026080718: <result>
|
||||||
|
Runtime: READY / ...
|
||||||
|
```
|
||||||
|
|
||||||
|
RN 只提交 producer timestamp,不保存第二套 T0/T1/MIX Runtime Window;Core Timeline 仍是唯一 owner。
|
||||||
|
|
||||||
|
## 8. Playback / Forecast 综合控制
|
||||||
|
|
||||||
|
在现有 Bridge API 上补齐 Test Host 可操作 UI,不新增 Native contract:
|
||||||
|
|
||||||
|
- PLAY
|
||||||
|
- PAUSE
|
||||||
|
- HALF / NORMAL / DOUBLE
|
||||||
|
- seek T0 / T1 / T2 / T3 对应的 forecast timestamp
|
||||||
|
- FPS AUTO / 30 / 60
|
||||||
|
- WeatherColor visible / opacity
|
||||||
|
- Particle visible
|
||||||
|
- WeatherColor stops(可使用已冻结的测试色带)
|
||||||
|
- particleColor / trailColor
|
||||||
|
- particle count / line width / length / opacity / trail opacity / trail fade
|
||||||
|
|
||||||
|
测试 UI 可以紧凑,但必须让真实地图保持主要可视区域;不要把控制面做成遮住地图的大面板。
|
||||||
|
|
||||||
|
Forecast seek 必须调用现有 `setForecastTime(timestampMs)`;禁止把 index/T0/T1/MIX 暴露成 JS 业务状态。
|
||||||
|
|
||||||
|
## 9. 视觉与地图综合验收准备
|
||||||
|
|
||||||
|
代码必须让后续 Android 一次 APK 验收能够观察:
|
||||||
|
|
||||||
|
- GSI 海岸线、地名等底图正常;
|
||||||
|
- WeatherColor 与地图地理位置对齐;
|
||||||
|
- Particle 与 WeatherColor 使用同一真实 Field;
|
||||||
|
- pan / zoom / bearing 后粒子与底图保持地理锁定;
|
||||||
|
- T0/T1/T2/T3 seek 后场景可继续渲染;
|
||||||
|
- PLAY/PAUSE/速度切换不造成 style reload;
|
||||||
|
- WeatherColor visible 与 Particle visible 相互独立;
|
||||||
|
- FPS 切换不改变 forecast/canonical 数据语义。
|
||||||
|
|
||||||
|
## 10. 临时文件与稳定性
|
||||||
|
|
||||||
|
本阶段要明确 3 MiB 级 asset 的生命周期,但不做 zero-copy 性能工程。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- `expo-asset` 产生的本地文件由 Expo/系统 cache 生命周期管理;不要额外复制多份 payload;
|
||||||
|
- 若代码另建临时文件,提交结束后必须有明确清理路径;
|
||||||
|
- Fast Refresh、重复 LOAD、clear、detach 不得无限累积自建 `.f32` 临时文件;
|
||||||
|
- 不为了“减少 copy”引入 JSI、direct ByteBuffer、mmap 或新的 Native transport;
|
||||||
|
- Android 验收阶段观察连续 T0~T3 提交是否出现 OOM、长时间卡死或明显异常 GC;本任务不提前做性能优化。
|
||||||
|
|
||||||
|
## 11. 允许修改范围
|
||||||
|
|
||||||
|
优先只允许:
|
||||||
|
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/App.tsx`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/src/**`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/assets/**`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/metro.config.js`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/package.json`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/package-lock.json`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/README.md`
|
||||||
|
- 本 Step 的中文记录/报告/开发记录。
|
||||||
|
|
||||||
|
如非必要,不修改 `modules/weather-runtime-bridge/**`;当前 Bridge API 已足够完成任务。
|
||||||
|
|
||||||
|
明确禁止提交:
|
||||||
|
|
||||||
|
- `android/` generated build 目录;
|
||||||
|
- `node_modules/`;
|
||||||
|
- `.codex-build-logs/`;
|
||||||
|
- APK/AAR;
|
||||||
|
- 与本任务无关的历史 untracked 文件。
|
||||||
|
|
||||||
|
## 12. Codex 静态验证
|
||||||
|
|
||||||
|
Codex 必须执行并记录:
|
||||||
|
|
||||||
|
1. `npm run typecheck`。
|
||||||
|
2. `metro.config.js` 的 Node 语法/配置检查。
|
||||||
|
3. 四帧 Test Host asset 与源 WeatherV2 asset 的 SHA-256 一致性检查。
|
||||||
|
4. 检索正式 transport 不存在 `number[]` / payload JSON / base64 / hex。
|
||||||
|
5. 检查 style layer 顺序确实为 GSI → WeatherColor → Particle。
|
||||||
|
6. `git diff --check`。
|
||||||
|
|
||||||
|
未经用户明确要求:**不加入 Jest,不运行 Jest**。
|
||||||
|
|
||||||
|
Codex 不执行:
|
||||||
|
|
||||||
|
- Android Gradle;
|
||||||
|
- `assembleDebug`;
|
||||||
|
- ADB;
|
||||||
|
- APK install;
|
||||||
|
- Android 设备测试;
|
||||||
|
- AAR build/publish。
|
||||||
|
|
||||||
|
Android APK 编译和设备综合验收仍由后续 CARBON7i 手工周期执行,并尽量一次覆盖全部 STEP-062 验收项。
|
||||||
|
|
||||||
|
## 13. 必做输出
|
||||||
|
|
||||||
|
至少完成:
|
||||||
|
|
||||||
|
1. RN Test Host 的 GSI + Real Weather + WeatherColor + Particle + Playback 代码。
|
||||||
|
2. 原样复用的四帧 WeatherV2 assets。
|
||||||
|
3. 更新 Test Host 中文 README,说明 STEP-062 操作方法和冻结的 `step061` AAR。
|
||||||
|
4. `docs/50_Step记录/STEP-062_RNTestHost真实WeatherV2底图粒子综合验证_v1.md`。
|
||||||
|
5. `docs/报告/STEP-062_RNTestHost真实WeatherV2底图粒子综合验证_Codex完成报告_v1.md`。
|
||||||
|
6. 追加 `粒子开发记录.md`。
|
||||||
|
|
||||||
|
完成报告必须明确:
|
||||||
|
|
||||||
|
- 实际修改文件;
|
||||||
|
- GSI source/layer 与 camera 实现;
|
||||||
|
- 四帧 asset 来源、大小和 SHA 对照;
|
||||||
|
- Expo Asset → local file URI → STEP-061 bridge 的实际调用链;
|
||||||
|
- T0~T3 的预期 submit result;
|
||||||
|
- Playback/forecast/visual 控件接线;
|
||||||
|
- 是否修改任何 Native/Core/WeatherRuntime/JNI(预期:否);
|
||||||
|
- 是否构建/发布 AAR(必须:否);
|
||||||
|
- 实际静态验证结果;
|
||||||
|
- Android APK/设备仍待哪些项目验证。
|
||||||
|
|
||||||
|
源码阶段状态只能写:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IMPLEMENTED / WAITING_ANDROID_VALIDATION
|
||||||
|
```
|
||||||
|
|
||||||
|
不得提前写 Android PASS。
|
||||||
|
|
||||||
|
## 14. Git 收尾
|
||||||
|
|
||||||
|
本任务会生成/修改项目文件,因此完成后必须:
|
||||||
|
|
||||||
|
1. 逐项对照本任务必做项;
|
||||||
|
2. `git diff --check` PASS;
|
||||||
|
3. 只 `git add` 本任务相关文件;
|
||||||
|
4. commit 到当前 `particle/main`;
|
||||||
|
5. `git push origin particle/main`;
|
||||||
|
6. 完成报告记录 final commit、commit message、push 结果、remote branch;
|
||||||
|
7. push 失败不得声称任务完成。
|
||||||
|
|
||||||
|
不得把现有历史 untracked 文件加入提交。
|
||||||
|
|
||||||
|
## 15. 最终必做清单
|
||||||
|
|
||||||
|
- [ ] GSI raster basemap 已进入 RN Test Host
|
||||||
|
- [ ] layer order = GSI → WeatherColor → Particle
|
||||||
|
- [ ] Camera 初始范围能直接看到 WeatherV2 区域
|
||||||
|
- [ ] 四帧真实 WeatherV2 asset 原样复制且 SHA 一致
|
||||||
|
- [ ] `.f32` 走 Expo Asset local file URI
|
||||||
|
- [ ] 继续使用 STEP-061 file-reference transport
|
||||||
|
- [ ] T0/T1/T2/T3 结果做严格 hard assertion
|
||||||
|
- [ ] 最终 Native state 必须要求 READY
|
||||||
|
- [ ] PLAY/PAUSE/速度/forecast seek 可操作
|
||||||
|
- [ ] WeatherColor 与 Particle 独立显隐和视觉控制保留
|
||||||
|
- [ ] FPS AUTO/30/60 可操作
|
||||||
|
- [ ] 不创建第二套 T0/T1/MIX 状态
|
||||||
|
- [ ] 不使用 number[]/payload JSON/base64/hex
|
||||||
|
- [ ] 不修改 Native/Core/JNI/WeatherRuntime
|
||||||
|
- [ ] 不构建或发布 AAR
|
||||||
|
- [ ] 不进入 NavSea
|
||||||
|
- [ ] 不加入或运行 Jest
|
||||||
|
- [ ] `npm run typecheck` PASS
|
||||||
|
- [ ] asset SHA 检查 PASS
|
||||||
|
- [ ] `git diff --check` PASS
|
||||||
|
- [ ] Step/报告/开发记录完成
|
||||||
|
- [ ] commit + push `origin/particle/main`
|
||||||
@@ -0,0 +1,889 @@
|
|||||||
|
# STEP-063 MapLibre 粒子 Android v1 发布工程化与 React Native 可导入包 Codex 任务 v1
|
||||||
|
|
||||||
|
## 0. 任务性质
|
||||||
|
|
||||||
|
本任务开始把已经通过 Android 实机综合验证的 MapLibre Particle 从“开发工程状态”整理为“可版本化、可发布、可由 React Native 通过包依赖导入的组件”。
|
||||||
|
|
||||||
|
本任务属于发布工程化与包结构整理,不是继续开发新的粒子渲染能力。
|
||||||
|
|
||||||
|
当前用户已确认:
|
||||||
|
|
||||||
|
- Android MapLibre Particle 当前能力达到满意水平。
|
||||||
|
- STEP-062 可按 `CLOSED / PASS` 收尾。
|
||||||
|
- 当前阶段不接入 NavSea。
|
||||||
|
- 发布服务器、npm Registry、Maven Repository 的最终部署位置暂不决定。
|
||||||
|
- npm 正式包名采用:
|
||||||
|
- `@navsea/maplibre-particle-rn`
|
||||||
|
- 正式版本规则采用:
|
||||||
|
- `<上游 MapLibre Native 版本>-particle.<NavSea 修订号>`
|
||||||
|
- 当前计划首个正式版本:
|
||||||
|
- `13.4.1-particle.1`
|
||||||
|
- Android Maven artifact 计划采用:
|
||||||
|
- group: `cc.navsea.maplibre`
|
||||||
|
- artifact: `android-sdk-particle-opengl`
|
||||||
|
- version: `13.4.1-particle.1`
|
||||||
|
|
||||||
|
如果仓库实际调查证明基础 MapLibre Native 并非 `13.4.1`,必须停止版本改写并先报告,不得自行猜测或强行套用版本号。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1. 本任务目标
|
||||||
|
|
||||||
|
完成以下四个目标:
|
||||||
|
|
||||||
|
1. 冻结并记录当前 MapLibre Particle 的真实上游版本基线。
|
||||||
|
2. 建立正式版本号、release manifest、CHANGELOG 与发布契约。
|
||||||
|
3. 把当前 React Native 接入整理成一个正式 npm package:
|
||||||
|
- `@navsea/maplibre-particle-rn`
|
||||||
|
4. 建立一个“只通过发布包消费”的独立 Consumer Test Host,用于后续证明 NavSea 不需要源码路径或 MapLibre Native 源码编译即可使用。
|
||||||
|
|
||||||
|
本任务结束时,不要求真正上传到任何服务器。
|
||||||
|
|
||||||
|
最终服务器发布位置后续单独决定。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2. 强制边界
|
||||||
|
|
||||||
|
## 2.1 禁止继续扩展粒子功能
|
||||||
|
|
||||||
|
本任务不得新增或重写:
|
||||||
|
|
||||||
|
- Particle GPU simulation
|
||||||
|
- WeatherColor 算法
|
||||||
|
- Timeline / Playback 算法
|
||||||
|
- HostField 二进制入口
|
||||||
|
- RasterArraySource 核心语义
|
||||||
|
- U/V canonical contract
|
||||||
|
- MapLibre renderer
|
||||||
|
- JNI 核心路径
|
||||||
|
- WeatherRuntime 业务语义
|
||||||
|
|
||||||
|
除非当前发布工程化过程中发现明确的“无法打包/无法消费”的结构性阻塞。
|
||||||
|
|
||||||
|
如果出现此类阻塞:
|
||||||
|
|
||||||
|
1. 停止扩大修改。
|
||||||
|
2. 明确记录阻塞位置。
|
||||||
|
3. 说明最小修复建议。
|
||||||
|
4. 不得借发布任务顺便重构 Native/Core。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.2 不接入 NavSea
|
||||||
|
|
||||||
|
本任务不得修改 NavSea 仓库。
|
||||||
|
|
||||||
|
不得:
|
||||||
|
|
||||||
|
- 在 NavSea 中安装 npm package
|
||||||
|
- 修改 NavSea Metro
|
||||||
|
- 修改 NavSea Gradle
|
||||||
|
- 修改 NavSea Pod
|
||||||
|
- 修改 NavSea Store / Adapter / Action
|
||||||
|
- 将 Test Host 逻辑搬入 NavSea
|
||||||
|
|
||||||
|
本任务只负责把发布组件准备好。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.3 不决定最终发布服务器
|
||||||
|
|
||||||
|
本任务不得擅自加入:
|
||||||
|
|
||||||
|
- 公司 npm Registry URL
|
||||||
|
- GitHub Packages
|
||||||
|
- GitLab Packages
|
||||||
|
- Gitea Packages
|
||||||
|
- Nexus
|
||||||
|
- Artifactory
|
||||||
|
- Verdaccio
|
||||||
|
- 私有 Maven Repository URL
|
||||||
|
- 生产用户名、密码、Token、证书
|
||||||
|
|
||||||
|
可以建立与 Registry 无关的标准发布配置和说明。
|
||||||
|
|
||||||
|
可以使用本地 `.tgz`、本地 staging 目录作为“未上传发布物”验证手段。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.4 Android 编译边界
|
||||||
|
|
||||||
|
严格遵守项目长期规则:
|
||||||
|
|
||||||
|
**所有 Android 编译由用户手工执行。**
|
||||||
|
|
||||||
|
Codex 不得执行:
|
||||||
|
|
||||||
|
- Android Gradle assemble
|
||||||
|
- Android Gradle build
|
||||||
|
- AAR build
|
||||||
|
- AAR publish
|
||||||
|
- Maven Local publish
|
||||||
|
- APK build
|
||||||
|
- APK install
|
||||||
|
- ADB
|
||||||
|
- Android 实机测试
|
||||||
|
|
||||||
|
Codex 可以:
|
||||||
|
|
||||||
|
- 修改构建配置
|
||||||
|
- 修改版本号
|
||||||
|
- 准备 publication 配置
|
||||||
|
- 做非 Android 编译的静态检查
|
||||||
|
- 执行 TypeScript 编译/类型检查(若不触发 Android build)
|
||||||
|
- 执行 `npm pack`
|
||||||
|
- 检查 `.tgz` 内容
|
||||||
|
- 编写用户手工 Android 构建命令
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.5 不执行 Jest
|
||||||
|
|
||||||
|
本任务不执行 Jest,也不新增 Jest 作为验收要求。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3. 第一步:真实基线只读调查
|
||||||
|
|
||||||
|
开始修改前必须先从当前仓库真实代码调查。
|
||||||
|
|
||||||
|
至少确认:
|
||||||
|
|
||||||
|
## 3.1 MapLibre Native 基础版本
|
||||||
|
|
||||||
|
从实际构建文件、版本定义、Gradle publication、Git metadata 等确认:
|
||||||
|
|
||||||
|
- upstream MapLibre Native version
|
||||||
|
- 当前 Android renderer variant
|
||||||
|
- 当前 STEP-061 AAR 坐标
|
||||||
|
- 当前 STEP-061 AAR version
|
||||||
|
- 当前 Native/Core source 与该版本之间的关系
|
||||||
|
|
||||||
|
预期:
|
||||||
|
|
||||||
|
```text
|
||||||
|
upstreamMapLibreNative = 13.4.1
|
||||||
|
currentDevelopmentArtifact = org.maplibre.gl:android-sdk-opengl:13.4.1-step061
|
||||||
|
renderer = opengl
|
||||||
|
```
|
||||||
|
|
||||||
|
但必须以仓库实际内容为准。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.2 React Native 基线
|
||||||
|
|
||||||
|
调查当前 RN bridge / package 的真实位置与结构。
|
||||||
|
|
||||||
|
至少确认:
|
||||||
|
|
||||||
|
- 当前 RN package 名
|
||||||
|
- 当前 RN package version
|
||||||
|
- 当前 `package.json`
|
||||||
|
- 当前 Android module / Gradle 文件
|
||||||
|
- 当前 autolinking 方式
|
||||||
|
- 当前 MapLibre RN 公共 API 导出方式
|
||||||
|
- `WeatherRuntime`
|
||||||
|
- `RasterParticleLayer`
|
||||||
|
- `WeatherColorLayer`
|
||||||
|
- HostField submit API
|
||||||
|
- RN Test Host 当前如何引用该 RN 模块
|
||||||
|
- 是否存在 `file:`、workspace path、source path 等开发期依赖
|
||||||
|
|
||||||
|
必须明确:
|
||||||
|
|
||||||
|
**哪些路径只是开发期源码引用,正式 npm package 后必须消失。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.3 当前正式能力链路
|
||||||
|
|
||||||
|
只读确认当前已冻结链路:
|
||||||
|
|
||||||
|
```text
|
||||||
|
React Native Host
|
||||||
|
↓ metadataJson + local file:// binary payload
|
||||||
|
RN bridge
|
||||||
|
↓
|
||||||
|
WeatherRuntime
|
||||||
|
↓
|
||||||
|
RasterArraySource::submitHostField()
|
||||||
|
↓
|
||||||
|
LocalParticleFieldFeed
|
||||||
|
↓
|
||||||
|
ParticleFieldDataAdapter
|
||||||
|
↓
|
||||||
|
ParticleFieldSnapshot
|
||||||
|
↓
|
||||||
|
Core Timeline / Runtime Field Window
|
||||||
|
↓
|
||||||
|
WeatherColorLayer + RasterParticleLayer
|
||||||
|
```
|
||||||
|
|
||||||
|
不得因为发布工程化另建第二套入口。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4. 第二步:正式版本契约
|
||||||
|
|
||||||
|
调查确认基础版本无误后,建立正式版本契约。
|
||||||
|
|
||||||
|
## 4.1 正式版本
|
||||||
|
|
||||||
|
首个正式版本:
|
||||||
|
|
||||||
|
```text
|
||||||
|
13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
语义:
|
||||||
|
|
||||||
|
```text
|
||||||
|
13.4.1
|
||||||
|
= 上游 MapLibre Native 基础版本
|
||||||
|
|
||||||
|
particle
|
||||||
|
= NavSea Particle 扩展发行线
|
||||||
|
|
||||||
|
1
|
||||||
|
= 基于该 upstream version 的第 1 个 NavSea Particle 正式修订
|
||||||
|
```
|
||||||
|
|
||||||
|
后续规则:
|
||||||
|
|
||||||
|
```text
|
||||||
|
13.4.1-particle.2
|
||||||
|
13.4.1-particle.3
|
||||||
|
|
||||||
|
13.5.0-particle.1
|
||||||
|
|
||||||
|
14.0.0-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
禁止以后恢复使用 `step061` 这种开发阶段编号作为正式发行版本。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4.2 npm package
|
||||||
|
|
||||||
|
正式 npm package:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@navsea/maplibre-particle-rn
|
||||||
|
```
|
||||||
|
|
||||||
|
正式首发 version:
|
||||||
|
|
||||||
|
```text
|
||||||
|
13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
要求 package 名清楚表达:
|
||||||
|
|
||||||
|
- NavSea 维护
|
||||||
|
- MapLibre
|
||||||
|
- Particle
|
||||||
|
- React Native
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4.3 Android Maven artifact
|
||||||
|
|
||||||
|
正式 Android artifact:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
不得继续使用官方坐标作为 NavSea 正式发行坐标:
|
||||||
|
|
||||||
|
```text
|
||||||
|
org.maplibre.gl:android-sdk-opengl
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
Java/Kotlin package namespace 不要求因此重命名。
|
||||||
|
|
||||||
|
本任务重点是 Maven publication coordinate 与依赖契约,不允许进行大规模 Java/C++ namespace rename。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5. 第三步:建立 Release Manifest
|
||||||
|
|
||||||
|
在项目中建立稳定、机器可读的 release manifest。
|
||||||
|
|
||||||
|
路径由 Codex根据现有仓库结构选择,但建议类似:
|
||||||
|
|
||||||
|
```text
|
||||||
|
release/maplibre-particle/release-manifest.json
|
||||||
|
```
|
||||||
|
|
||||||
|
或:
|
||||||
|
|
||||||
|
```text
|
||||||
|
release/maplibre-particle/13.4.1-particle.1.json
|
||||||
|
```
|
||||||
|
|
||||||
|
至少包含:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"release": "13.4.1-particle.1",
|
||||||
|
"upstreamMapLibreNative": "13.4.1",
|
||||||
|
"extensionLine": "particle",
|
||||||
|
"extensionRevision": 1,
|
||||||
|
"platform": {
|
||||||
|
"android": {
|
||||||
|
"renderer": "opengl"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"reactNativePackage": {
|
||||||
|
"name": "@navsea/maplibre-particle-rn",
|
||||||
|
"version": "13.4.1-particle.1"
|
||||||
|
},
|
||||||
|
"nativeArtifact": {
|
||||||
|
"group": "cc.navsea.maplibre",
|
||||||
|
"artifact": "android-sdk-particle-opengl",
|
||||||
|
"version": "13.4.1-particle.1"
|
||||||
|
},
|
||||||
|
"publication": {
|
||||||
|
"npmRegistry": null,
|
||||||
|
"mavenRepository": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
可以增加必要字段,例如:
|
||||||
|
|
||||||
|
- source commit
|
||||||
|
- compatibility
|
||||||
|
- canonical field schema
|
||||||
|
- RN compatibility
|
||||||
|
- minSdk / compileSdk(如果项目已有真实事实)
|
||||||
|
- release state
|
||||||
|
|
||||||
|
但不得猜测未确认版本。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6. 第四步:正式 React Native package
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@navsea/maplibre-particle-rn@13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
必须成为 NavSea 未来唯一面对的 React Native MapLibre Particle 包入口。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.1 设计原则
|
||||||
|
|
||||||
|
NavSea 未来目标使用方式:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import {
|
||||||
|
MapView,
|
||||||
|
Camera,
|
||||||
|
RasterParticleLayer,
|
||||||
|
WeatherColorLayer,
|
||||||
|
WeatherRuntime,
|
||||||
|
} from '@navsea/maplibre-particle-rn';
|
||||||
|
```
|
||||||
|
|
||||||
|
实际导出名称必须依据当前仓库真实 API。
|
||||||
|
|
||||||
|
不得为了示例强行重命名现有稳定 API。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.2 不允许正式消费源码路径
|
||||||
|
|
||||||
|
正式 package 不得要求 Consumer:
|
||||||
|
|
||||||
|
```text
|
||||||
|
file:../MapLibreParticleLab/...
|
||||||
|
../../platform/react-native
|
||||||
|
workspace 源码路径
|
||||||
|
CMake source path
|
||||||
|
MapLibre Native source tree
|
||||||
|
本地源码软链接
|
||||||
|
```
|
||||||
|
|
||||||
|
Consumer 只能依赖正式 npm package。
|
||||||
|
|
||||||
|
Android Native 应通过正式 artifact dependency 解决,而不是 Consumer 直接编译 MapLibre Native 源码。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.3 包内部结构
|
||||||
|
|
||||||
|
根据当前 React Native package 实际结构做最小整理。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- `package.json` 名称正式改为 `@navsea/maplibre-particle-rn`
|
||||||
|
- version = `13.4.1-particle.1`
|
||||||
|
- 保留正确的 React Native autolinking
|
||||||
|
- 保留当前稳定 JS/TS 公共 API
|
||||||
|
- 保留 Native bridge
|
||||||
|
- 内部 Android Gradle dependency 改为正式 artifact:
|
||||||
|
- `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1`
|
||||||
|
- 不得同时再自动引入官方 `org.maplibre.gl:android-sdk-opengl` 导致双 Native AAR
|
||||||
|
- 不得引入第二份 MapLibre RN native module
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.4 唯一 MapLibre RN 入口原则
|
||||||
|
|
||||||
|
需要重点审计:
|
||||||
|
|
||||||
|
Consumer 是否仍然需要同时安装:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@maplibre/maplibre-react-native
|
||||||
|
```
|
||||||
|
|
||||||
|
如果当前 NavSea Particle RN package 本质上是对 MapLibre RN 的完整 fork/扩展,则正式目标应是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Consumer
|
||||||
|
↓
|
||||||
|
@navsea/maplibre-particle-rn
|
||||||
|
↓
|
||||||
|
唯一 RN native module
|
||||||
|
↓
|
||||||
|
cc.navsea.maplibre:android-sdk-particle-opengl
|
||||||
|
```
|
||||||
|
|
||||||
|
不应形成:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@maplibre/maplibre-react-native
|
||||||
|
+
|
||||||
|
@navsea/maplibre-particle-rn
|
||||||
|
```
|
||||||
|
|
||||||
|
两套 Native MapLibre 同时进入 App。
|
||||||
|
|
||||||
|
如果仓库实际结构暂时无法做到唯一入口,必须在报告中明确说明,不得假装已经完成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7. 第五步:发布包内容审计
|
||||||
|
|
||||||
|
必须能够执行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
npm pack
|
||||||
|
```
|
||||||
|
|
||||||
|
生成类似:
|
||||||
|
|
||||||
|
```text
|
||||||
|
navsea-maplibre-particle-rn-13.4.1-particle.1.tgz
|
||||||
|
```
|
||||||
|
|
||||||
|
实际文件名以 npm 规则为准。
|
||||||
|
|
||||||
|
Codex 必须检查 tarball 内容。
|
||||||
|
|
||||||
|
至少确保:
|
||||||
|
|
||||||
|
必须包含:
|
||||||
|
|
||||||
|
- package.json
|
||||||
|
- JS/TS build output 或正式 package 所需源码
|
||||||
|
- type declarations
|
||||||
|
- React Native native module 必需文件
|
||||||
|
- android module 必需文件
|
||||||
|
- README
|
||||||
|
- LICENSE/NOTICE(依据仓库现有许可要求)
|
||||||
|
- 必需 plugin/config 文件
|
||||||
|
|
||||||
|
不得包含:
|
||||||
|
|
||||||
|
- node_modules
|
||||||
|
- `.git`
|
||||||
|
- 大量 build cache
|
||||||
|
- Android APK
|
||||||
|
- 临时 log
|
||||||
|
- Test Host
|
||||||
|
- WeatherV2 测试数据
|
||||||
|
- `.codex-build-logs`
|
||||||
|
- unrelated docs
|
||||||
|
- 任何 credential
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8. 第六步:Android artifact publication 配置
|
||||||
|
|
||||||
|
把 Android Native publication 从开发阶段坐标整理成正式坐标。
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
```text
|
||||||
|
groupId:
|
||||||
|
cc.navsea.maplibre
|
||||||
|
|
||||||
|
artifactId:
|
||||||
|
android-sdk-particle-opengl
|
||||||
|
|
||||||
|
version:
|
||||||
|
13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
必须保留:
|
||||||
|
|
||||||
|
```text
|
||||||
|
renderer = opengl
|
||||||
|
```
|
||||||
|
|
||||||
|
本阶段只准备 publication 配置。
|
||||||
|
|
||||||
|
Codex 不执行 AAR build 或 publish。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 9. 第七步:建立独立 Consumer Test Host
|
||||||
|
|
||||||
|
新增一个真正用于验证“发布包消费”的最小 React Native Test Host。
|
||||||
|
|
||||||
|
名称建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MapLibreParticlePackageConsumerTestHost
|
||||||
|
```
|
||||||
|
|
||||||
|
实际路径应遵守现有 integration 结构。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9.1 Consumer Test Host 的核心约束
|
||||||
|
|
||||||
|
它不得:
|
||||||
|
|
||||||
|
- 引用 MapLibreParticleLab RN 源码目录
|
||||||
|
- 使用 workspace source dependency
|
||||||
|
- 使用相对路径导入正式 RN package 源码
|
||||||
|
- 直接引用 MapLibre Native source tree
|
||||||
|
- 自己实现 WeatherRuntime bridge
|
||||||
|
- 复制 Native bridge 源码
|
||||||
|
|
||||||
|
它只能通过安装后的:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@navsea/maplibre-particle-rn
|
||||||
|
```
|
||||||
|
|
||||||
|
使用功能。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9.2 当前服务器未决定时的测试方式
|
||||||
|
|
||||||
|
由于最终 npm/Maven 服务器尚未决定,本阶段允许:
|
||||||
|
|
||||||
|
### npm
|
||||||
|
|
||||||
|
使用本地:
|
||||||
|
|
||||||
|
```text
|
||||||
|
npm pack
|
||||||
|
```
|
||||||
|
|
||||||
|
生成 `.tgz` 后安装:
|
||||||
|
|
||||||
|
```text
|
||||||
|
npm install <package.tgz>
|
||||||
|
```
|
||||||
|
|
||||||
|
这属于“发布包消费”,不是源码路径消费。
|
||||||
|
|
||||||
|
### Android Native artifact
|
||||||
|
|
||||||
|
本任务只准备正式 artifact publication。
|
||||||
|
|
||||||
|
后续由用户手工构建并放入 Maven Local 或临时 staging repository,再进行 Consumer APK 编译。
|
||||||
|
|
||||||
|
不得因为服务器未决定,就退回 source path。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9.3 Consumer Test Host 最低功能
|
||||||
|
|
||||||
|
先做最小 smoke host,不要求复制 STEP-062 全部复杂测试 UI。
|
||||||
|
|
||||||
|
最低要求:
|
||||||
|
|
||||||
|
- MapLibre MapView
|
||||||
|
- GSI basemap
|
||||||
|
- `WeatherColorLayer`
|
||||||
|
- `RasterParticleLayer`
|
||||||
|
- `WeatherRuntime`
|
||||||
|
- 一个最小真实或 canonical fixture 提交入口
|
||||||
|
- Runtime state 可见
|
||||||
|
- Particle / WeatherColor 显示
|
||||||
|
|
||||||
|
如果已有可复用 STEP-062 Test Host 逻辑,可以复用业务层代码,但不得通过源码 dependency 复用 RN package。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 10. 第八步:文档
|
||||||
|
|
||||||
|
所有文档中文。
|
||||||
|
|
||||||
|
至少新增或更新:
|
||||||
|
|
||||||
|
## 10.1 架构/发布设计
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/30_设计/MapLibre粒子Android发布与ReactNative包设计_v1.md
|
||||||
|
```
|
||||||
|
|
||||||
|
至少说明:
|
||||||
|
|
||||||
|
- upstream version
|
||||||
|
- NavSea Particle version rule
|
||||||
|
- npm package name
|
||||||
|
- Maven coordinate
|
||||||
|
- RN package 与 Native artifact 关系
|
||||||
|
- 为什么 Consumer 不编译 MapLibre Native 源码
|
||||||
|
- 为什么不能同时装官方 RN MapLibre 和 NavSea Particle RN
|
||||||
|
- server registry 尚未决定
|
||||||
|
- 本地 `.tgz` / staging 只是预发布验证手段
|
||||||
|
- 后续 upstream upgrade 规则
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10.2 STEP 记录
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/50_Step记录/STEP-063_MapLibre粒子Android_v1发布工程化_v1.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10.3 Codex 完成报告
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/报告/STEP-063_MapLibre粒子Android_v1发布工程化_Codex完成报告_v1.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10.4 CHANGELOG
|
||||||
|
|
||||||
|
为正式 package 建立中文 CHANGELOG 或遵循仓库现有发布记录结构。
|
||||||
|
|
||||||
|
至少记录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
13.4.1-particle.1
|
||||||
|
|
||||||
|
- 基于 MapLibre Native 13.4.1
|
||||||
|
- Android OpenGL
|
||||||
|
- WeatherColor
|
||||||
|
- RasterParticle
|
||||||
|
- HostField binary ingress
|
||||||
|
- Timeline / Playback
|
||||||
|
- React Native WeatherRuntime
|
||||||
|
- STEP-061 Native/RN ingress 验证
|
||||||
|
- STEP-062 GSI + WeatherV2 + Particle Android 综合验证
|
||||||
|
```
|
||||||
|
|
||||||
|
不得把没有验证的能力写成已通过。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 11. 静态验收
|
||||||
|
|
||||||
|
Codex 可执行且应执行:
|
||||||
|
|
||||||
|
- package JSON 校验
|
||||||
|
- TypeScript typecheck
|
||||||
|
- `npm pack`
|
||||||
|
- tarball 内容列表审计
|
||||||
|
- 检查正式 package 中不存在开发期 `file:` dependency
|
||||||
|
- 检查正式 package 中不存在 MapLibre Native source path
|
||||||
|
- 检查 Android Gradle dependency 指向正式 coordinate
|
||||||
|
- 检查没有同时依赖正式 Particle AAR 与官方 MapLibre AAR
|
||||||
|
- `git diff --check`
|
||||||
|
- repository search 审计旧 `13.4.1-step061` 使用位置
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
历史文档中出现 `13.4.1-step061` 是合法的。
|
||||||
|
|
||||||
|
但正式 release/package/build config 不得继续把它作为当前发行依赖。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 12. 用户手工 Android 验证入口
|
||||||
|
|
||||||
|
Codex 完成本任务后,必须给出准确的用户手工命令,但不得自行执行。
|
||||||
|
|
||||||
|
后续用户将手工完成两个阶段:
|
||||||
|
|
||||||
|
## 阶段 A:正式 AAR
|
||||||
|
|
||||||
|
构建:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
并验证 artifact 生成。
|
||||||
|
|
||||||
|
最终 publish server 尚未决定,因此可先由用户手工发布到 Maven Local 或指定 staging 目录用于验证。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 B:Consumer Test Host APK
|
||||||
|
|
||||||
|
Consumer 只安装:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@navsea/maplibre-particle-rn@13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
通过正式 artifact dependency 获取 Android Native。
|
||||||
|
|
||||||
|
然后用户手工:
|
||||||
|
|
||||||
|
- Android assembleDebug
|
||||||
|
- 安装 APK
|
||||||
|
- 实机 smoke
|
||||||
|
- GSI
|
||||||
|
- WeatherColor
|
||||||
|
- Particle
|
||||||
|
- Runtime READY
|
||||||
|
|
||||||
|
通过后才能正式创建 release tag。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 13. Git Tag 规则
|
||||||
|
|
||||||
|
本任务暂时不得创建正式 release tag。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
`13.4.1-particle.1` 必须先经过:
|
||||||
|
|
||||||
|
1. 正式 AAR 手工构建
|
||||||
|
2. npm tarball package consumer 验证
|
||||||
|
3. Consumer Test Host Android APK 实机验证
|
||||||
|
|
||||||
|
全部通过后,再单独执行最终 Release Freeze 任务创建 tag。
|
||||||
|
|
||||||
|
建议未来 tag:
|
||||||
|
|
||||||
|
```text
|
||||||
|
maplibre-particle/android/13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
实际 tag 命名在最终发布任务确认。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 14. Git 收尾
|
||||||
|
|
||||||
|
本任务会修改项目文件,因此必须提交并 push 到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
origin/particle/main
|
||||||
|
```
|
||||||
|
|
||||||
|
严格遵守项目 Git 规则。
|
||||||
|
|
||||||
|
不得:
|
||||||
|
|
||||||
|
```text
|
||||||
|
git add .
|
||||||
|
git add -A
|
||||||
|
```
|
||||||
|
|
||||||
|
必须精确 add 本任务文件。
|
||||||
|
|
||||||
|
不得提交历史无关 untracked 文件。
|
||||||
|
|
||||||
|
完成后:
|
||||||
|
|
||||||
|
```text
|
||||||
|
git fetch origin
|
||||||
|
git rev-parse HEAD
|
||||||
|
git rev-parse origin/particle/main
|
||||||
|
git status --short
|
||||||
|
```
|
||||||
|
|
||||||
|
必须确认:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HEAD == origin/particle/main
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 push 失败:
|
||||||
|
|
||||||
|
**不得声称任务完成。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 15. 任务完成状态
|
||||||
|
|
||||||
|
如果代码/package/publication 配置均完成,静态检查和 npm pack 均通过,但尚未执行用户手工 Android 构建:
|
||||||
|
|
||||||
|
状态写:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IMPLEMENTED / WAITING_MANUAL_RELEASE_VALIDATION
|
||||||
|
```
|
||||||
|
|
||||||
|
不得提前写:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RELEASED
|
||||||
|
CLOSED / PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
正式 `CLOSED / PASS` 等待后续用户手工 AAR + Consumer APK 验证。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 16. Codex 最终报告必须包含
|
||||||
|
|
||||||
|
- 实际确认的 upstream MapLibre Native version
|
||||||
|
- 当前 RN 基线
|
||||||
|
- 正式版本号
|
||||||
|
- npm package name
|
||||||
|
- Maven coordinate
|
||||||
|
- release manifest 路径
|
||||||
|
- 正式 RN package 路径
|
||||||
|
- Consumer Test Host 路径
|
||||||
|
- `npm pack` 结果
|
||||||
|
- tarball 文件名
|
||||||
|
- tarball 内容审计结果
|
||||||
|
- 是否仍存在正式配置中的 `step061`
|
||||||
|
- 是否存在 `file:` 或 source-path consumer dependency
|
||||||
|
- 是否修改 Native/Core 功能代码
|
||||||
|
- 是否执行 Android Gradle
|
||||||
|
- 是否构建 AAR
|
||||||
|
- 是否执行 ADB
|
||||||
|
- 是否执行 Jest
|
||||||
|
- 用户下一步手工命令
|
||||||
|
- commit hash
|
||||||
|
- commit message
|
||||||
|
- push result
|
||||||
|
- remote branch
|
||||||
|
- Final HEAD
|
||||||
|
- 历史无关 untracked 项说明
|
||||||
|
|
||||||
517
docs/40_任务/STEP-064_RN纯FieldRenderer接口V2_Codex任务_v1.md
Normal file
517
docs/40_任务/STEP-064_RN纯FieldRenderer接口V2_Codex任务_v1.md
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
# STEP-064 RN 纯 Field Renderer 接口 V2 — Codex 任务 v1
|
||||||
|
|
||||||
|
## 0. 任务定位
|
||||||
|
|
||||||
|
本任务把 `@navsea/maplibre-particle-rn` 从当前开发期的 Weather/Wind 导向 RN API,整理为正式宿主可使用的**纯 Field Renderer RN SDK**。
|
||||||
|
|
||||||
|
目标 RN 包版本:
|
||||||
|
|
||||||
|
```text
|
||||||
|
13.4.1-particle.2
|
||||||
|
```
|
||||||
|
|
||||||
|
本任务不是粒子算法升级,不是 WeatherV2 功能扩展,也不是 Native/Core 重构。
|
||||||
|
|
||||||
|
核心原则只有一条:
|
||||||
|
|
||||||
|
> React/宿主负责业务、数据获取、provider、数据类型和显示策略;Particle RN SDK 只接收 canonical Field 并控制 MapLibre Native 图场与粒子渲染。
|
||||||
|
|
||||||
|
当前除了 Test App 没有正式消费者,因此本任务**不承担旧 RN 公共 API 兼容责任**,允许直接删除旧接口并同步修改测试宿主。
|
||||||
|
## 1. 仓库与当前基线
|
||||||
|
|
||||||
|
仓库:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/mnt/sda1/MapLibreParticleLab
|
||||||
|
```
|
||||||
|
|
||||||
|
分支:
|
||||||
|
|
||||||
|
```text
|
||||||
|
particle/main
|
||||||
|
```
|
||||||
|
|
||||||
|
任务书编写时源码基线:
|
||||||
|
|
||||||
|
```text
|
||||||
|
f31c0ea1571aa3429ec170fbf38bdecf45c847ae
|
||||||
|
```
|
||||||
|
|
||||||
|
开始执行时必须重新确认 `git status --short`、当前分支、`HEAD` 与 `origin/particle/main`。若远端已合法推进,先阅读新增提交,不得 reset 或覆盖。
|
||||||
|
|
||||||
|
当前工作树存在与本任务无关的 modified/untracked 文件。禁止 `git add .`、`git add -A`,禁止恢复、删除、覆盖或提交这些无关文件。
|
||||||
|
## 2. 执行前必须阅读
|
||||||
|
|
||||||
|
至少阅读以下实际文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AGENTS.md
|
||||||
|
PARTICLE_BASELINE.md
|
||||||
|
docs/00_项目规范/01_Codex协作规范_v1.md
|
||||||
|
docs/00_项目规范/02_构建与验证规范_v1.md
|
||||||
|
docs/00_项目规范/03_Step阶段记录规范_v1.md
|
||||||
|
docs/00_项目规范/05_粒子架构原则_v1.md
|
||||||
|
packages/maplibre-particle-rn/package.json
|
||||||
|
packages/maplibre-particle-rn/src/index.ts
|
||||||
|
packages/maplibre-particle-rn/src/types.ts
|
||||||
|
packages/maplibre-particle-rn/src/NativeWeatherRuntimeBridge.ts
|
||||||
|
packages/maplibre-particle-rn/src/weatherV2/*
|
||||||
|
packages/maplibre-particle-rn/app.plugin.js
|
||||||
|
integration/react-native/MapLibreParticlePackageConsumerTestHost/*
|
||||||
|
release/maplibre-particle/release-manifest.json
|
||||||
|
docs/30_设计/MapLibre粒子ReactNative使用说明_v2.md
|
||||||
|
```
|
||||||
|
|
||||||
|
实现必须依据当前实际源码,不得根据任务书示例猜接口或目录。
|
||||||
|
## 3. 冻结的架构边界
|
||||||
|
|
||||||
|
### 3.1 SDK 不理解业务
|
||||||
|
|
||||||
|
`@navsea/maplibre-particle-rn` 的正式 RN 公共接口不得出现或要求调用方传入以下业务含义:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WeatherV2
|
||||||
|
ECMWF
|
||||||
|
wind
|
||||||
|
wave
|
||||||
|
rain
|
||||||
|
tide
|
||||||
|
weather layer
|
||||||
|
dataset
|
||||||
|
manifest
|
||||||
|
time_index
|
||||||
|
HTTP URL/provider/cache/retry
|
||||||
|
Route / ETA
|
||||||
|
NavSea 业务状态
|
||||||
|
```
|
||||||
|
|
||||||
|
这些全部属于 React/宿主层。
|
||||||
|
|
||||||
|
### 3.2 数据只有一份
|
||||||
|
|
||||||
|
同一份 canonical Field 进入 Runtime 一次。图场 Renderer 与 Particle Renderer 是对同一 Field 的两种独立显示方式,不建立两套 Field 数据、不要求重复提交。
|
||||||
|
逻辑必须保持:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Canonical Field
|
||||||
|
├─ Field Renderer ON / OFF
|
||||||
|
└─ Particle Renderer ON / OFF
|
||||||
|
```
|
||||||
|
|
||||||
|
宿主可以任意组合两个 Renderer 的显示状态。SDK 不根据业务类型自动决定是否显示粒子。
|
||||||
|
|
||||||
|
### 3.3 本任务不改变 canonical / Native 技术
|
||||||
|
|
||||||
|
本任务保持当前已验证 Native/Core 能力:
|
||||||
|
|
||||||
|
```text
|
||||||
|
maplibre-particle-field/v1
|
||||||
|
metadataJson + local file payload
|
||||||
|
float32-le / interleaved-uv
|
||||||
|
Timeline / T0-T1 Runtime Field Window
|
||||||
|
GPU 时间插值
|
||||||
|
WeatherColorLayer
|
||||||
|
RasterParticleLayer
|
||||||
|
Particle State / Trail
|
||||||
|
Playback / target FPS
|
||||||
|
```
|
||||||
|
|
||||||
|
不得为了 RN 命名整理修改 C++、shader、JNI、Java/Kotlin WeatherRuntime、MapLibre Native renderer 或 Android AAR 内容。
|
||||||
|
## 4. 必做项 A:建立最终 RN 公共 API
|
||||||
|
|
||||||
|
新增纯渲染公共入口,建议名称:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
FieldRuntime
|
||||||
|
```
|
||||||
|
|
||||||
|
正式调用方应从包根入口使用它。目标公开方法至少包括:
|
||||||
|
|
||||||
|
```text
|
||||||
|
attach()
|
||||||
|
detach()
|
||||||
|
getState()
|
||||||
|
clearFields()
|
||||||
|
submitField()
|
||||||
|
setEnabled()
|
||||||
|
setFieldVisible()
|
||||||
|
setParticleVisible()
|
||||||
|
setTargetFps()
|
||||||
|
setTargetTime()
|
||||||
|
setPlaybackState()
|
||||||
|
setPlaybackSpeed()
|
||||||
|
setFieldColorStops()
|
||||||
|
setParticleColor()
|
||||||
|
setTrailColor()
|
||||||
|
setParticleCount()
|
||||||
|
setParticleLineWidth()
|
||||||
|
setParticleLength()
|
||||||
|
setParticleOpacity()
|
||||||
|
setTrailOpacity()
|
||||||
|
setTrailFade()
|
||||||
|
setFieldOpacity()
|
||||||
|
```
|
||||||
|
|
||||||
|
这些方法可以在 RN/TS 层薄映射到当前已验证的 Native bridge 方法,不得因此修改 Native 方法名。
|
||||||
|
公共 API 中不得继续暴露以下旧名称:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WeatherRuntime
|
||||||
|
submitWindField
|
||||||
|
clearWindFields
|
||||||
|
setWeatherEnabled
|
||||||
|
setWeatherColorVisible
|
||||||
|
setWeatherColorStops
|
||||||
|
setWeatherColorOpacity
|
||||||
|
setForecastTime
|
||||||
|
```
|
||||||
|
|
||||||
|
当前内部 `NativeWeatherRuntimeBridge.ts`、TurboModule 名 `WeatherRuntimeBridge`、Native 生成类名等若属于既有 Native ABI/Codegen 接缝,可作为本任务内部实现细节保留;但不得从包根入口继续暴露,也不得要求宿主使用这些名字。
|
||||||
|
|
||||||
|
由于没有正式消费者,不建立 deprecated alias,不增加 compatibility wrapper,不同时维护两套公共 API。
|
||||||
|
|
||||||
|
## 5. 必做项 B:公共类型去业务化
|
||||||
|
|
||||||
|
将面向宿主的类型改为纯 Field Renderer 语义。
|
||||||
|
|
||||||
|
例如当前 `ColorStop.speedMps` 不应继续作为最终 RN 公共命名;建议公开为:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type FieldColorStop = Readonly<{
|
||||||
|
value: number;
|
||||||
|
red: number;
|
||||||
|
green: number;
|
||||||
|
blue: number;
|
||||||
|
alpha: number;
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
RN 内部可把 `value` 薄转换成当前 Native bridge 所需字段,不修改 Native/Core。
|
||||||
|
以下现有类型可在确认无业务耦合后继续使用或适当重命名:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RuntimeState
|
||||||
|
PlaybackState
|
||||||
|
TargetFps
|
||||||
|
CommandResult
|
||||||
|
FieldSubmitResult
|
||||||
|
RuntimeSnapshot
|
||||||
|
```
|
||||||
|
|
||||||
|
不得新增:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WeatherLayer
|
||||||
|
WindLayer
|
||||||
|
RainLayer
|
||||||
|
WaveLayer
|
||||||
|
TideLayer
|
||||||
|
ProviderType
|
||||||
|
DatasetType
|
||||||
|
```
|
||||||
|
|
||||||
|
`submitField()` 仍只负责把当前 canonical metadata 与本地 binary payload 交给既有 Native ingress。RN SDK 不解析业务类型,也不根据 field 内容决定 Renderer 组合。
|
||||||
|
|
||||||
|
当前 canonical metadata 中若存在 Native/Core 已冻结的历史字段名,本任务不为追求命名纯净而修改其 schema;RN 层只把它视为 Native 数据契约,不增加业务解释。
|
||||||
|
## 6. 必做项 C:WeatherV2 完全移出正式 package
|
||||||
|
|
||||||
|
当前 package 根入口存在:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export * from "./weatherV2";
|
||||||
|
```
|
||||||
|
|
||||||
|
且 `packages/maplibre-particle-rn/src/weatherV2/` 内包含 HTTP、manifest、time index、WVECBIN1 decoder 和高层 loader。
|
||||||
|
|
||||||
|
这些全部属于宿主业务/数据适配,不属于纯 Renderer SDK。
|
||||||
|
|
||||||
|
必须:
|
||||||
|
|
||||||
|
1. 从正式 package 根导出删除 WeatherV2 API;
|
||||||
|
2. 从正式 package 源码中移除 `src/weatherV2/`;
|
||||||
|
3. 不在 package 内留下其他 provider/network/weather helper 替代品;
|
||||||
|
4. Consumer Test Host 若仍要验证真实 WeatherV2,可以把必要 WeatherV2 逻辑放到 Test Host 自己的 `src/` 中;
|
||||||
|
5. Test Host 的业务代码最终只把 canonical Field 交给 `FieldRuntime.submitField()`。
|
||||||
|
|
||||||
|
禁止新增诸如 `loadWeatherV2IntoRuntime()`、`loadLayer()`、`WeatherFieldType` 等换名后的业务包装。
|
||||||
|
## 7. 必做项 D:MapLibre RN 与 Style Helper 收口
|
||||||
|
|
||||||
|
正式 package 不再把自己伪装成完整 MapLibre RN facade。
|
||||||
|
|
||||||
|
包根入口应停止:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export * from "@maplibre/maplibre-react-native";
|
||||||
|
```
|
||||||
|
|
||||||
|
宿主继续直接安装并使用固定 peer dependency:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@maplibre/maplibre-react-native@11.3.6
|
||||||
|
```
|
||||||
|
|
||||||
|
宿主地图组件从官方包导入;粒子/图场 Runtime 从 `@navsea/maplibre-particle-rn` 导入。必须确认这不会引入第二份 MLRN native module。
|
||||||
|
|
||||||
|
删除业务化的 `particleWeatherIds`、`createParticleWeatherStyle()` 公共命名。建议提供纯渲染命名:
|
||||||
|
|
||||||
|
```text
|
||||||
|
particleFieldIds
|
||||||
|
createParticleFieldStyleFragment()
|
||||||
|
```
|
||||||
|
|
||||||
|
Style helper 只能返回/描述 Particle 所需 source + Field layer + Particle layer,不得内置 GSI、WeatherV2 或任何业务底图。
|
||||||
|
当前 Native attach 依赖的内部 style ID 若仍是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
rn-weather-fields
|
||||||
|
rn-weather-color
|
||||||
|
rn-weather-particles
|
||||||
|
```
|
||||||
|
|
||||||
|
本任务不得为了字符串改名进入 Native/Core。允许 `particleFieldIds` 暂时映射这些既有内部 ID;README 必须要求宿主使用导出的常量/helper,不要自行硬编码这些历史字符串。
|
||||||
|
|
||||||
|
## 8. 必做项 E:RN import 在无 Native module 平台安全
|
||||||
|
|
||||||
|
当前 `NativeWeatherRuntimeBridge.ts` 使用 `TurboModuleRegistry.getEnforcing()`,会让不具备 `WeatherRuntimeBridge` 的平台在 import 阶段直接失败。
|
||||||
|
|
||||||
|
本任务只在 RN/TS 层解决:
|
||||||
|
|
||||||
|
- 包根入口被 import 时,不得因为 Native module 缺失立即 throw;
|
||||||
|
- `FieldRuntime` 真正执行 Native 方法时,若 bridge 不存在,应以清楚、稳定的错误失败;
|
||||||
|
- 不为此新增 iOS Native 实现;
|
||||||
|
- 不修改 Android Native bridge ABI;
|
||||||
|
- 不允许静默返回伪 `ACCEPTED` 或伪 Runtime state。
|
||||||
|
|
||||||
|
具体实现应依据 RN 0.85.2 TurboModule API 做最小改动。
|
||||||
|
## 9. 必做项 F:版本关系必须真实
|
||||||
|
|
||||||
|
RN package 版本升级为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@navsea/maplibre-particle-rn@13.4.1-particle.2
|
||||||
|
```
|
||||||
|
|
||||||
|
但本任务不修改 Native,因此 Android Native artifact 继续使用当前已验证版本:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
不得仅为了数字一致而复制、重编或发布内容相同的 `.2` AAR。
|
||||||
|
|
||||||
|
`app.plugin.js` 的 dependency substitution 必须继续解析到 Native `.1` AAR,除非实际调查发现当前正式 AAR 坐标已经合法变化。
|
||||||
|
|
||||||
|
更新 release manifest,使其明确区分:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RN package version = 13.4.1-particle.2
|
||||||
|
Native artifact version = 13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
并注明 `.2` 是 RN-only public API revision,Native/Core source 与已验证 `.1` AAR 不变。
|
||||||
|
不得覆盖或重新上传现有:
|
||||||
|
|
||||||
|
```text
|
||||||
|
navsea-maplibre-particle-rn-13.4.1-particle.1.tgz
|
||||||
|
```
|
||||||
|
|
||||||
|
本任务只生成新的 `.2` 本地 pack 产物供后续验证,不负责正式静态 HTTPS 上传或 release tag。
|
||||||
|
|
||||||
|
## 10. 必做项 G:Consumer Test Host 同步到最终 API
|
||||||
|
|
||||||
|
修改:
|
||||||
|
|
||||||
|
```text
|
||||||
|
integration/react-native/MapLibreParticlePackageConsumerTestHost/
|
||||||
|
```
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- `Map` / `Camera` 改为直接从 `@maplibre/maplibre-react-native` 导入;
|
||||||
|
- Renderer Runtime 只从 `@navsea/maplibre-particle-rn` 导入 `FieldRuntime` 和纯渲染类型/helper;
|
||||||
|
- 本地真实 WeatherV2 测试能力可以继续存在,但 WeatherV2 HTTP、decoder、选帧、临时文件管理全部位于 Test Host 自己的业务目录;
|
||||||
|
- 最终提交给 SDK 的仍是 canonical metadata + local binary file;
|
||||||
|
- Test Host 不得反向依赖 package 内部源码路径;
|
||||||
|
- Test Host UI 可以继续使用 Weather/ECMWF 文案,因为它是测试宿主,不是 SDK 公共接口。
|
||||||
|
旧 `MapLibreParticleRNTestHost` 属于历史 bridge 验证宿主,不要求为了本任务全部重构;除非它参与当前 package 构建/类型检查并形成 blocker,否则不要扩大范围。
|
||||||
|
|
||||||
|
## 11. 必做项 H:文档更新
|
||||||
|
|
||||||
|
保留现有:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/30_设计/MapLibre粒子ReactNative使用说明_v2.md
|
||||||
|
```
|
||||||
|
|
||||||
|
作为 `.1` 历史接口说明,不覆盖。
|
||||||
|
|
||||||
|
新增:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/30_设计/MapLibre粒子ReactNative使用说明_v3.md
|
||||||
|
```
|
||||||
|
|
||||||
|
v3 必须以 `13.4.1-particle.2` 最终真实代码为准,说明:
|
||||||
|
|
||||||
|
- 安装 `.2` RN package 与 MLRN 11.3.6;
|
||||||
|
- RN `.2` 对应 Native AAR `.1`;
|
||||||
|
- Map/Camera 从官方 MLRN 导入;
|
||||||
|
- `FieldRuntime` 全部公共方法、参数和返回值;
|
||||||
|
- canonical Field submission;
|
||||||
|
- 图场与粒子独立显示开关;
|
||||||
|
- Style fragment 如何合入宿主现有 style;
|
||||||
|
- React/宿主负责业务数据,SDK 不负责 provider/network/weather。
|
||||||
|
同步更新:
|
||||||
|
|
||||||
|
```text
|
||||||
|
packages/maplibre-particle-rn/README.md
|
||||||
|
packages/maplibre-particle-rn/CHANGELOG.md
|
||||||
|
packages/maplibre-particle-rn/package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
README 不得继续推荐 WeatherV2 高层 loader,也不得把 SDK 描述成天气客户端。
|
||||||
|
|
||||||
|
CHANGELOG 必须明确 `.2`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
- RN 公共接口去 Weather/Wind 业务化
|
||||||
|
- WeatherV2 helper 移出正式 package
|
||||||
|
- 图场与粒子继续消费同一 canonical Field,并可独立开关
|
||||||
|
- Native/Core/GPU 未修改
|
||||||
|
- 继续复用 Android AAR 13.4.1-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. 明确禁止范围
|
||||||
|
|
||||||
|
本任务禁止修改:
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/mbgl/**
|
||||||
|
src/mbgl/**
|
||||||
|
platform/android/MapLibreAndroid/**
|
||||||
|
shaders/**
|
||||||
|
CMake / Core renderer
|
||||||
|
Android AAR 内容与 publication 源码
|
||||||
|
NavSea 仓库
|
||||||
|
```
|
||||||
|
|
||||||
|
若实现纯 RN API 时发现必须修改以上任一 Native/Core 文件,立即停止扩大修改,在完成报告中写明 blocker 和原因。
|
||||||
|
## 13. 静态与包验证
|
||||||
|
|
||||||
|
Codex 必须完成以下非 Android 验证:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. package TypeScript typecheck
|
||||||
|
2. Consumer Test Host TypeScript typecheck
|
||||||
|
3. npm pack
|
||||||
|
4. tarball 内容审计
|
||||||
|
5. package 根导出审计
|
||||||
|
6. 业务语义残留审计
|
||||||
|
7. git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
`npm pack` 生成的新 `.2` tgz 不提交 Git,除非仓库当前发布规则明确要求;不得碰现有未跟踪 `.1` tgz。
|
||||||
|
|
||||||
|
包内容必须确认不存在:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/weatherV2/
|
||||||
|
WeatherV2HttpClient
|
||||||
|
loadWeatherV2WindIntoRuntime
|
||||||
|
decodeWeatherV2WindFrame
|
||||||
|
NavSea 业务源码
|
||||||
|
Test Host
|
||||||
|
node_modules
|
||||||
|
临时日志/构建缓存
|
||||||
|
```
|
||||||
|
|
||||||
|
允许内部保留 Native ABI 接缝中的 `WeatherRuntimeBridge` 名称和现有 `rn-weather-*` style ID,但这些不得作为新的业务 API 暴露。
|
||||||
|
## 14. Android 验证边界
|
||||||
|
|
||||||
|
本任务不得执行 Android Gradle、AAR build/publish、APK build/install、ADB 或设备操作。
|
||||||
|
|
||||||
|
因为 Native AAR 不变,本任务完成后优先采用既有包含 `.1` Native bridge/AAR 的 Consumer APK,通过 Metro 验证新的 `.2` JS/TS API;只有后续确认 Native 文件或 Android 构建配置实际发生变化时,才需要用户重新编译 APK。
|
||||||
|
|
||||||
|
Codex 最终必须给出用户后续最小验证步骤,但不得自行执行 Android 操作。
|
||||||
|
|
||||||
|
## 15. Step、开发记录与完成报告
|
||||||
|
|
||||||
|
执行完成后新增/更新:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/50_Step记录/STEP-064_RN纯FieldRenderer接口V2_v1.md
|
||||||
|
docs/报告/STEP-064_RN纯FieldRenderer接口V2_Codex完成报告_v1.md
|
||||||
|
粒子开发记录.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Step 状态在未完成人工 Consumer 验证前写为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
进行中(代码完成,待 Consumer 验证)
|
||||||
|
```
|
||||||
|
|
||||||
|
完成报告可使用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IMPLEMENTED / WAITING_CONSUMER_VALIDATION
|
||||||
|
```
|
||||||
|
|
||||||
|
不得提前写 RELEASED、CLOSED / PASS 或 Android validated。
|
||||||
|
## 16. Git 收尾
|
||||||
|
|
||||||
|
完成代码、文档和静态验证后,必须只精确 stage 本任务文件并提交,随后 push:
|
||||||
|
|
||||||
|
```text
|
||||||
|
origin/particle/main
|
||||||
|
```
|
||||||
|
|
||||||
|
禁止把当前已有无关 modified/untracked 文件带入提交。
|
||||||
|
|
||||||
|
建议代码提交信息:
|
||||||
|
|
||||||
|
```text
|
||||||
|
refactor: make RN particle API field-renderer only
|
||||||
|
```
|
||||||
|
|
||||||
|
如文档单独提交,建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs: record STEP-064 RN field renderer API v2
|
||||||
|
```
|
||||||
|
|
||||||
|
Push 后必须确认:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HEAD == origin/particle/main
|
||||||
|
```
|
||||||
|
|
||||||
|
Push 失败不得声称任务完成。
|
||||||
|
## 17. 最终必做清单
|
||||||
|
|
||||||
|
- [ ] 确认 `particle/main`、HEAD、远端 HEAD 与工作树
|
||||||
|
- [ ] 阅读任务要求与当前 `.1` 实际 package/Consumer 源码
|
||||||
|
- [ ] RN package version 升为 `13.4.1-particle.2`
|
||||||
|
- [ ] 建立 `FieldRuntime` 纯渲染公共入口
|
||||||
|
- [ ] 公共 API 删除 Weather/Wind 专用名称
|
||||||
|
- [ ] 不建立旧 API 兼容 wrapper
|
||||||
|
- [ ] `submitField()` / `clearFields()` 继续薄映射既有 Native ingress
|
||||||
|
- [ ] 图场与粒子保持同一 Field、独立显示开关
|
||||||
|
- [ ] `FieldColorStop.value` 等公共类型去业务化
|
||||||
|
- [ ] `WeatherV2` 完全移出正式 package
|
||||||
|
- [ ] package 不再 re-export 整个 MLRN
|
||||||
|
- [ ] Style helper 改为纯 Particle Field fragment,不内置业务底图
|
||||||
|
- [ ] 无 Native module 平台 import package 不立即崩溃
|
||||||
|
- [ ] Consumer Test Host 改用最终 `.2` API
|
||||||
|
- [ ] WeatherV2 测试逻辑如需保留,仅存在于 Test Host 业务层
|
||||||
|
- [ ] Native/Core/JNI/shader/AAR 无功能修改
|
||||||
|
- [ ] Android AAR 继续为 `13.4.1-particle.1`
|
||||||
|
- [ ] release manifest 明确 RN `.2` ↔ Native `.1`
|
||||||
|
- [ ] README、CHANGELOG、React Native 使用说明 v3 更新
|
||||||
|
- [ ] package/Consumer TypeScript 检查通过
|
||||||
|
- [ ] `.2` `npm pack` 与 tarball 内容审计通过
|
||||||
|
- [ ] `git diff --check` 通过
|
||||||
|
- [ ] 更新 STEP-064、完成报告、粒子开发记录
|
||||||
|
- [ ] 精确 commit 并 push `origin/particle/main`
|
||||||
|
- [ ] 最终报告 commit hash、message、push result、Final HEAD
|
||||||
|
|
||||||
|
Codex 不需要在开工前提交实施计划。结束时直接按本清单逐项报告“已完成 / 未完成 / 无法验证”,任何未完成项必须说明原因,不得静默跳过。
|
||||||
@@ -0,0 +1,532 @@
|
|||||||
|
# STEP-065 MapLibreParticleLab V3 通用 Scalar / Vector Field 架构冻结 — Codex 任务 v1
|
||||||
|
|
||||||
|
## 0. 任务定位
|
||||||
|
|
||||||
|
本任务是 MapLibreParticleLab V3 的第一步:**只读调查当前真实实现,并冻结 V3 通用 Scalar / Vector Field Contract 与迁移方案。**
|
||||||
|
|
||||||
|
本任务不直接修改 C++、shader、JNI、Kotlin/Java、React Native Runtime 或 Android AAR。
|
||||||
|
|
||||||
|
V3 的目标不是“增加 Rain/Wave 两种天气类型”,而是把当前主要围绕 WIND + interleaved U/V 的运行时升级为:
|
||||||
|
|
||||||
|
> provider-neutral、business-neutral 的二维时序 Scalar / Vector Field Rendering Runtime。
|
||||||
|
|
||||||
|
当前已冻结的上游边界:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WeatherV2 -> NSFT -> NavSea JS/TS -> Canonical Field -> MapLibreParticleLab V3
|
||||||
|
```
|
||||||
|
|
||||||
|
WeatherV2 统一向 NavSea 提供 NSFT;NavSea JS/TS 负责网络、缓存、NSFT 解码、tile 拼接、业务单位与 canonical 化;MapLibreParticleLab 从 canonical field 开始接管。
|
||||||
|
## 1. 仓库与执行基线
|
||||||
|
|
||||||
|
仓库:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/mnt/sda1/MapLibreParticleLab
|
||||||
|
```
|
||||||
|
|
||||||
|
分支:`particle/main`
|
||||||
|
|
||||||
|
任务书生成时 HEAD:
|
||||||
|
|
||||||
|
```text
|
||||||
|
2e3ab26275a8d1873d764cf6927783a7b0406836
|
||||||
|
```
|
||||||
|
|
||||||
|
该 HEAD 已完成并冻结 STEP-064 RN 纯 Field Renderer `13.4.1-particle.2`。
|
||||||
|
|
||||||
|
执行开始时必须重新确认:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git branch --show-current
|
||||||
|
git rev-parse HEAD
|
||||||
|
git rev-parse origin/particle/main
|
||||||
|
git status --short
|
||||||
|
```
|
||||||
|
|
||||||
|
若远端已合法推进,先阅读新增提交后再继续;禁止 reset、checkout 覆盖或回退用户工作。
|
||||||
|
当前工作区已知存在与本任务无关的用户修改和历史未跟踪文件,特别包括 STEP-062 文档、Android/Expo/build 目录、旧任务书和 tgz。
|
||||||
|
|
||||||
|
必须遵守:
|
||||||
|
|
||||||
|
- 禁止 `git add .`、`git add -A`。
|
||||||
|
- 禁止删除、恢复、格式化、移动无关 modified/untracked 文件。
|
||||||
|
- 本任务只允许新增/修改 STEP-065 明确列出的文档产物和本任务书自身。
|
||||||
|
- 本任务不允许以“清理工作区”为理由处理历史文件。
|
||||||
|
|
||||||
|
## 2. 执行前必须阅读
|
||||||
|
|
||||||
|
至少完整阅读:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AGENTS.md
|
||||||
|
PARTICLE_BASELINE.md
|
||||||
|
docs/00_项目规范/01_Codex协作规范_v1.md
|
||||||
|
docs/00_项目规范/02_构建与验证规范_v1.md
|
||||||
|
docs/00_项目规范/03_Step阶段记录规范_v1.md
|
||||||
|
docs/00_项目规范/05_粒子架构原则_v1.md
|
||||||
|
docs/40_任务/STEP-064_RN纯FieldRenderer接口V2_Codex任务_v1.md
|
||||||
|
docs/50_Step记录/STEP-064_RN纯FieldRenderer接口V2_v1.md
|
||||||
|
docs/报告/STEP-064_RN纯FieldRenderer接口V2_Codex完成报告_v1.md
|
||||||
|
```
|
||||||
|
并重点阅读当前真实实现:
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/mbgl/particle/field/field_data_adapter.hpp
|
||||||
|
src/mbgl/particle/field/field_data_adapter.cpp
|
||||||
|
所有 Timeline / Runtime Field Window 实现
|
||||||
|
WeatherColorLayer 相关 C++ / shader / shader generator
|
||||||
|
RasterParticleLayer 相关 C++ / particle simulation shader
|
||||||
|
GPU field texture / upload / reuse / lifecycle 实现
|
||||||
|
Android Host WeatherRuntime / JNI submission
|
||||||
|
packages/maplibre-particle-rn/src/index.ts
|
||||||
|
packages/maplibre-particle-rn/src/types.ts
|
||||||
|
packages/maplibre-particle-rn/src/FieldRuntime.ts
|
||||||
|
packages/maplibre-particle-rn/src/NativeWeatherRuntimeBridge.ts
|
||||||
|
integration/react-native 中当前正式 Consumer/Test Host
|
||||||
|
```
|
||||||
|
|
||||||
|
必须通过当前源码回答问题,不得根据历史任务书猜实现。
|
||||||
|
|
||||||
|
## 3. WeatherV2 外部输入事实与只读核对
|
||||||
|
|
||||||
|
WeatherV2 实际目录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/root/sourceserver/weatherV2
|
||||||
|
```
|
||||||
|
可只读核对以下文件,不得修改 WeatherV2:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/dev/core/nsft.py
|
||||||
|
src/dev/core/field_pmtiles.py
|
||||||
|
src/dev/layers/wind.py
|
||||||
|
src/dev/layers/tide.py
|
||||||
|
src/dev/layers/rain.py
|
||||||
|
src/dev/layers/waves.py
|
||||||
|
```
|
||||||
|
|
||||||
|
已调查结论必须作为 V3 设计输入:
|
||||||
|
|
||||||
|
- Wind NSFT:物理速度 U/V,单位 m/s,vector-only。
|
||||||
|
- Tide/Current NSFT:物理速度 U/V,单位 m/s,vector-only。
|
||||||
|
- Rain NSFT:rain rate scalar + valid mask;当前 NSFT 不保存 precipitation type。
|
||||||
|
- Wave NSFT:height scalar + period scalar + normalized direction-to U/V;direction U/V 不是 m/s。
|
||||||
|
- NSFT grid 为 `wm_grid_257`,NW origin,行 north->south,列 west->east。
|
||||||
|
- 同 zoom 相邻 257 tile 有共享边界,NavSea 拼接后 canonical grid 可保持 `(tiles*256)+1` 规则,无需 Core 理解 tile。
|
||||||
|
- Forecast target time 必须按实际 validTime/time_index 使用,Core 不假设固定 1h cadence。
|
||||||
|
|
||||||
|
这些事实如与当前 WeatherV2 代码不一致,报告差异并以当前代码为准。
|
||||||
|
## 4. V3 冻结边界
|
||||||
|
|
||||||
|
V3 Core/Native 不得负责:
|
||||||
|
|
||||||
|
- NSFT / PMTiles / WeatherV2 解析;
|
||||||
|
- HTTP、provider、manifest、time_index、cache、retry、prefetch;
|
||||||
|
- ECMWF / CMEMS 等业务来源;
|
||||||
|
- Wind / Rain / Wave / Tide 业务分支;
|
||||||
|
- kt、mm/h、m、m/s 等宿主业务单位转换;
|
||||||
|
- NavSea 当前 Layer、Forecast UI 或业务状态。
|
||||||
|
|
||||||
|
V3 从 Canonical Field Submission 开始负责:
|
||||||
|
|
||||||
|
- descriptor / payload validation;
|
||||||
|
- Snapshot;
|
||||||
|
- Timeline / target time / playback;
|
||||||
|
- T0/T1/MIX;
|
||||||
|
- Runtime Field Window;
|
||||||
|
- OLD_T1 -> NEW_T0 reuse;
|
||||||
|
- GPU channel resource 生命周期;
|
||||||
|
- Field Renderer;
|
||||||
|
- Particle Renderer;
|
||||||
|
- MapLibre style/viewport lifecycle recovery;
|
||||||
|
- fail-closed。
|
||||||
|
## 5. 必须冻结的最小 Canonical 模型
|
||||||
|
|
||||||
|
V3.0 只设计:
|
||||||
|
|
||||||
|
```text
|
||||||
|
FieldSnapshot
|
||||||
|
├─ common descriptor
|
||||||
|
├─ scalar channel? 0 or 1
|
||||||
|
├─ vector channel? 0 or 1 (U/V)
|
||||||
|
└─ shared validity
|
||||||
|
```
|
||||||
|
|
||||||
|
禁止第一版扩成 N scalar / N vector generic graph。
|
||||||
|
|
||||||
|
必须支持三种合法组合:
|
||||||
|
|
||||||
|
```text
|
||||||
|
vector-only
|
||||||
|
scalar-only
|
||||||
|
scalar + vector
|
||||||
|
```
|
||||||
|
|
||||||
|
Neither 必须 invalid / reject。
|
||||||
|
|
||||||
|
优先扩展当前 `ParticleFieldDescriptor / ParticleFieldSnapshot / ParticleFieldDataAdapter`,禁止并行创建 `GenericWeatherField`、`WindFieldV2` 等第二套重复模型。
|
||||||
|
|
||||||
|
当前 `fieldType == "WIND"`、`displayDomain == "GLOBAL"` 等硬编码必须被调查并给出 V3 最小迁移方案;Core 最终不得靠 WIND/WAVE/RAIN/TIDE 决定数据能力。
|
||||||
|
## 6. Renderer Contract 必须与数据 Contract 分离
|
||||||
|
|
||||||
|
必须调查并冻结以下原则:
|
||||||
|
|
||||||
|
`FieldSnapshot` 只描述“有什么数据”;Renderer config 描述“如何画”。
|
||||||
|
|
||||||
|
第一版 Field Renderer 的通用值来源建议为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SCALAR
|
||||||
|
VECTOR_MAGNITUDE
|
||||||
|
```
|
||||||
|
|
||||||
|
即:
|
||||||
|
|
||||||
|
- `SCALAR`:色场读取 explicit scalar channel;
|
||||||
|
- `VECTOR_MAGNITUDE`:色场读取 `sqrt(U^2 + V^2)`。
|
||||||
|
|
||||||
|
不得让 Renderer 根据 business type 或“有没有 scalar”偷偷猜来源。
|
||||||
|
|
||||||
|
必须调查当前 WeatherColorLayer/shader 是否硬依赖 `length(U,V)`,以及将其泛化所需最小接口、uniform/property、GPU resource 与 style property 改动。
|
||||||
|
|
||||||
|
`FieldColorStop.value` 的 RN 公共语义保持 generic;调查 Native/JNI/C++ 内 `speedMps` 命名实际范围,并给出兼容迁移路径。
|
||||||
|
## 7. Vector Semantics 必须业务无关
|
||||||
|
|
||||||
|
必须调查当前 Particle Integrator / shader 是否隐含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
U/V = physical velocity (m/s)
|
||||||
|
```
|
||||||
|
|
||||||
|
V3 需要覆盖至少两类数学语义:
|
||||||
|
|
||||||
|
```text
|
||||||
|
VELOCITY
|
||||||
|
DIRECTION
|
||||||
|
```
|
||||||
|
|
||||||
|
目标映射:
|
||||||
|
|
||||||
|
- Wind/Tide -> VELOCITY;
|
||||||
|
- Wave direction-to U/V -> DIRECTION。
|
||||||
|
|
||||||
|
不得在 Core 增加 Wind/Wave/Tide enum 来解决。
|
||||||
|
|
||||||
|
对于 DIRECTION,设计必须说明:
|
||||||
|
|
||||||
|
- T0/T1 vector mix 后是否 normalize;
|
||||||
|
- 接近零向量/接近反向插值如何 fail-closed;
|
||||||
|
- particle movement 的独立 advection scale 放在哪一层;
|
||||||
|
- 如何保证 VELOCITY 当前 Wind 行为不被改变。
|
||||||
|
|
||||||
|
本任务只冻结方案,不实现 Wave physics。
|
||||||
|
## 8. Binary Submission / Channel Plane 设计
|
||||||
|
|
||||||
|
当前 RN 公共入口是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
submitField(metadataJson, payloadFileUri)
|
||||||
|
```
|
||||||
|
|
||||||
|
当前 Core canonical payload 是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
float32-le / interleaved-uv
|
||||||
|
```
|
||||||
|
|
||||||
|
必须调查 RN -> Android bridge -> JNI -> Core 的真实 ownership、文件读取、byte[]/buffer、size limit 与 decode 流程。
|
||||||
|
|
||||||
|
V3 优先评估独立 plane:
|
||||||
|
|
||||||
|
```text
|
||||||
|
metadata
|
||||||
|
vectorPayload? // float32-le interleaved U/V
|
||||||
|
scalarPayload? // float32-le scalar plane
|
||||||
|
```
|
||||||
|
|
||||||
|
不得使用巨大 JS number[]、JSON Array 或 Base64 作为正式大数据通路。
|
||||||
|
|
||||||
|
必须明确:scalar-only、vector-only、scalar+vector 三种提交如何表达;文件引用是一个 container 还是两个 file URI;JNI/Core 最小修改面是什么。
|
||||||
|
|
||||||
|
独立 plane 是首选方向,但最终冻结必须依据当前代码,不能为了迎合任务书而忽略更安全的现有接缝。
|
||||||
|
## 9. Timeline / Runtime Window 保护项
|
||||||
|
|
||||||
|
以下现有能力视为 V3 保护基线,不得在设计中推倒重来:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Core-owned Timeline / Playback
|
||||||
|
T0 / T1 / MIX
|
||||||
|
正常连续播放 simulationRebuild=false
|
||||||
|
正常连续播放 trailCleared=false
|
||||||
|
Runtime Field Window
|
||||||
|
OLD_T1 -> NEW_T0 reuse
|
||||||
|
only NEW_T1 upload
|
||||||
|
Pause 语义
|
||||||
|
Invalid / Gap fail-closed
|
||||||
|
WeatherColorLayer 与 RasterParticleLayer 分离
|
||||||
|
```
|
||||||
|
|
||||||
|
V3 设计要说明如何把 Snapshot GPU resource 从单 vector texture 扩展成:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SnapshotGpuResources
|
||||||
|
├─ scalar texture?
|
||||||
|
├─ vector texture?
|
||||||
|
└─ validity resource
|
||||||
|
```
|
||||||
|
|
||||||
|
并保持整个 Snapshot 在 rollover 时一起 reuse,Host 不参与 GPU reuse。
|
||||||
|
## 10. Canonical schema 版本决策
|
||||||
|
|
||||||
|
必须明确回答:
|
||||||
|
|
||||||
|
```text
|
||||||
|
maplibre-particle-field/v1 additive extension
|
||||||
|
还是
|
||||||
|
maplibre-particle-field/v2
|
||||||
|
```
|
||||||
|
|
||||||
|
当前已知 v1 descriptor 是单 `layout/encoding/byteLength/stride`,Snapshot 只有 `velocity`,RN `submitField()` 只有一个 payload file URI,且 adapter 严格接受 `interleaved-uv`。
|
||||||
|
|
||||||
|
因此本任务的默认判断倾向:**如果 Scalar + Vector 会导致字段重载、隐式兼容或 ambiguous payload,则应正式升 v2,而不是污染 v1。**
|
||||||
|
|
||||||
|
但最终结论必须根据完整 parser/bridge/fixture/consumer 调查给出。
|
||||||
|
|
||||||
|
如果选择 v2,设计必须说明:
|
||||||
|
|
||||||
|
- v1 Vector-only 是否继续可读;
|
||||||
|
- compatibility 应放在哪个最薄层;
|
||||||
|
- 是否允许 v1/v2 同时进入同一个 Runtime;
|
||||||
|
- 新 RN package 版本至少应升到 `13.4.1-particle.3`,不得覆盖已发布 `.2`;
|
||||||
|
- Native/AAR version 如何独立推进。
|
||||||
|
|
||||||
|
不得为了“兼容”长期维护两套 Runtime。
|
||||||
|
## 11. Validity 与业务映射冻结
|
||||||
|
|
||||||
|
V3.0 优先保持一个 Snapshot 一个共享 validity,不提前设计 channel-specific mask。
|
||||||
|
|
||||||
|
必须给出四类 WeatherV2 -> NavSea canonical -> V3 的映射表,至少明确:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Wind : vector(U/V velocity) -> field=VECTOR_MAGNITUDE, particle=VELOCITY
|
||||||
|
Tide : vector(U/V velocity) -> field=VECTOR_MAGNITUDE, particle=VELOCITY
|
||||||
|
Rain : scalar(rainRate) -> field=SCALAR, particle=unavailable
|
||||||
|
Wave : scalar(height) + vector(direction-to U/V)
|
||||||
|
-> field=SCALAR, particle=DIRECTION
|
||||||
|
```
|
||||||
|
|
||||||
|
Wave `period` 当前保留在 NavSea Weather model/cache,V3.0 不因此增加第二 Scalar Channel。
|
||||||
|
|
||||||
|
Rain `precipitation_type` 当前未进入 NSFT,因此 V3 不推断、不补造该数据。
|
||||||
|
|
||||||
|
Field/Particle visibility 必须继续独立:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Field ON / Particle OFF
|
||||||
|
Field OFF / Particle ON
|
||||||
|
Field ON / Particle ON
|
||||||
|
```
|
||||||
|
|
||||||
|
没有 vector 时 Particle unavailable / reject,不允许 scalar 伪装成 U/V。
|
||||||
|
## 12. 必须输出的调查结论 A-J
|
||||||
|
|
||||||
|
设计文档必须逐项回答:
|
||||||
|
|
||||||
|
A. 当前 Field Contract 实际结构与 Wind-only 硬编码点。
|
||||||
|
|
||||||
|
B. 当前 GPU Texture / upload / validity / shader 的实际结构。
|
||||||
|
|
||||||
|
C. 当前 Particle Integrator 对 vector 数值、单位、dt、scale 的实际假设。
|
||||||
|
|
||||||
|
D. 当前 RN -> Android -> JNI -> Core binary submission 路径与 ownership。
|
||||||
|
|
||||||
|
E. Scalar Channel 的最小扩展方案与受影响对象。
|
||||||
|
|
||||||
|
F. Wave direction-only 的最小、业务无关表达方案。
|
||||||
|
|
||||||
|
G. v1 additive extension 还是 canonical v2,并说明理由。
|
||||||
|
|
||||||
|
H. 预计受影响文件清单,按 Core / shader / Android / RN / Test Host 分类。
|
||||||
|
|
||||||
|
I. 向后兼容、性能、GPU memory、lifecycle、Timeline 风险。
|
||||||
|
|
||||||
|
J. 推荐的正式实施批次、验证门槛和 Android 最终一次综合验收范围。
|
||||||
|
## 13. 推荐实施路线必须被审查并冻结
|
||||||
|
|
||||||
|
设计结果至少应评估以下批次是否合理:
|
||||||
|
|
||||||
|
```text
|
||||||
|
V3-02 Core Contract + Scalar Snapshot/Adapter
|
||||||
|
V3-03 Scalar GPU Texture + Generic Field Renderer
|
||||||
|
V3-04 VectorSemantics VELOCITY/DIRECTION + Particle
|
||||||
|
V3-05 Android/JNI/RN generic binary submission
|
||||||
|
V3-06 Wind/Rain/Wave 三类 fixture + Tide 回归
|
||||||
|
V3-07 Linux/Core/OpenGL 精确验证与文档冻结
|
||||||
|
V3-08 用户一次 Android build/install/device 综合验收
|
||||||
|
```
|
||||||
|
|
||||||
|
可以根据当前代码合并或调整批次,但必须遵守:
|
||||||
|
|
||||||
|
- 尽量减少 Android 重复编译/安装周期;
|
||||||
|
- 在 Linux/Core 可验证的内容先集中完成;
|
||||||
|
- 不因单一小按钮/日志单独触发 Android 周期;
|
||||||
|
- 高风险接口分叉必须在 Android 实现前冻结。
|
||||||
|
|
||||||
|
最终路线要给每个批次明确“修改范围 / 不修改范围 / 完成门槛 / 下一批入口”。
|
||||||
|
|
||||||
|
## 14. Validation Fixture 规划
|
||||||
|
V3 后续至少规划三类数学能力 fixture:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A. Vector-only : 真实 WeatherV2 Wind
|
||||||
|
B. Scalar-only : 真实 WeatherV2 Rain
|
||||||
|
C. Scalar+Vector: 真实 WeatherV2 Wave
|
||||||
|
```
|
||||||
|
|
||||||
|
Tide 作为第二套 VELOCITY vector 做回归,不单独创造业务代码路径。
|
||||||
|
|
||||||
|
Wave 必须规划一个独立性 synthetic fixture:
|
||||||
|
|
||||||
|
```text
|
||||||
|
scalar: west low -> east high
|
||||||
|
vector: 全场 north
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:
|
||||||
|
|
||||||
|
```text
|
||||||
|
色场只呈东西渐变
|
||||||
|
粒子只统一向北
|
||||||
|
```
|
||||||
|
|
||||||
|
该 fixture 用于证明 Field Renderer 不偷读 vector magnitude,Particle Renderer 不偷读 scalar。
|
||||||
|
|
||||||
|
同时规划 T0/T1 scalar、T0/T1 vector、共同 MIX、window rollover、style reload、visibility、invalid/gap fail-closed 验证。
|
||||||
|
## 15. 本任务禁止事项
|
||||||
|
|
||||||
|
本任务不得:
|
||||||
|
|
||||||
|
- 修改任何生产代码;
|
||||||
|
- 修改 shader / generator;
|
||||||
|
- 修改 Android/JNI/RN API;
|
||||||
|
- 生成新的 AAR/tgz release;
|
||||||
|
- 执行 Android Gradle build、APK 安装或 ADB 验收;
|
||||||
|
- 把 NSFT decoder 放入 ParticleLab;
|
||||||
|
- 新增 Weather Provider / HTTP / cache;
|
||||||
|
- 新增 WIND/WAVE/RAIN/TIDE Core 分支;
|
||||||
|
- 实现 Wave physics;
|
||||||
|
- 设计 N-channel framework;
|
||||||
|
- 做 JSI/zero-copy 大重构;
|
||||||
|
- 加入或执行 Jest;
|
||||||
|
- 修改 NavSea 或 WeatherV2。
|
||||||
|
|
||||||
|
本任务是设计冻结任务,不是实现任务。
|
||||||
|
|
||||||
|
## 16. 必做文档输出
|
||||||
|
|
||||||
|
新增:
|
||||||
|
```text
|
||||||
|
docs/20_调查/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField现状调查_v1.md
|
||||||
|
docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md
|
||||||
|
docs/50_Step记录/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_v1.md
|
||||||
|
docs/报告/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_Codex完成报告_v1.md
|
||||||
|
```
|
||||||
|
|
||||||
|
并追加更新:
|
||||||
|
|
||||||
|
```text
|
||||||
|
粒子开发记录.md
|
||||||
|
```
|
||||||
|
|
||||||
|
所有文档必须使用中文,代码标识符、路径、schema/property 名保持实际英文。
|
||||||
|
|
||||||
|
设计文档必须包含至少:
|
||||||
|
|
||||||
|
- 当前真实架构图;
|
||||||
|
- V3 目标架构图;
|
||||||
|
- Scalar / Vector / validity 数据模型;
|
||||||
|
- Renderer config 与 Snapshot data 的边界;
|
||||||
|
- Binary plane 方案;
|
||||||
|
- Timeline/GPU reuse 迁移;
|
||||||
|
- API/schema 版本决策;
|
||||||
|
- WeatherV2 四层映射;
|
||||||
|
- 受影响文件矩阵;
|
||||||
|
- 风险与实施顺序。
|
||||||
|
## 17. 必做清单
|
||||||
|
|
||||||
|
执行结束前逐项对照,不得静默跳过:
|
||||||
|
|
||||||
|
- [ ] 读取全部项目规范与 STEP-064 冻结基线。
|
||||||
|
- [ ] 重新确认 branch / HEAD / origin / dirty worktree。
|
||||||
|
- [ ] 调查 Descriptor / Snapshot / Adapter 当前实际结构。
|
||||||
|
- [ ] 找全 WIND / GLOBAL / fieldType 等行为硬编码。
|
||||||
|
- [ ] 调查 Runtime Window / Timeline / MIX / GPU reuse。
|
||||||
|
- [ ] 调查 WeatherColorLayer value 获取和 `speedMps` 链路。
|
||||||
|
- [ ] 调查 Particle Integrator 的 velocity 假设。
|
||||||
|
- [ ] 调查 RN -> Android -> JNI -> Core binary submission。
|
||||||
|
- [ ] 只读核对 WeatherV2 NSFT 四层语义。
|
||||||
|
- [ ] 冻结 0/1 Scalar + 0/1 Vector + shared validity 模型。
|
||||||
|
- [ ] 冻结 FieldValueSource 与 VectorSemantics 方案。
|
||||||
|
- [ ] 冻结 binary payload / file-reference 方案。
|
||||||
|
- [ ] 明确 canonical v1 additive 或 v2。
|
||||||
|
- [ ] 给出 V3 分批实施路线与综合 Android 验收计划。
|
||||||
|
- [ ] 生成调查、设计、Step、完成报告。
|
||||||
|
- [ ] 追加粒子开发记录。
|
||||||
|
- [ ] 确认没有修改生产代码和无关用户文件。
|
||||||
|
## 18. Git 收尾要求
|
||||||
|
|
||||||
|
本任务会新增/修改项目文档,因此完成后必须 Git 收尾并 push 到公开远端 `particle/main`。
|
||||||
|
|
||||||
|
提交前必须再次执行 `git status --short`,只暂存本任务明确产物:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/40_任务/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_Codex任务_v1.md
|
||||||
|
docs/20_调查/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField现状调查_v1.md
|
||||||
|
docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md
|
||||||
|
docs/50_Step记录/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_v1.md
|
||||||
|
docs/报告/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_Codex完成报告_v1.md
|
||||||
|
粒子开发记录.md
|
||||||
|
```
|
||||||
|
|
||||||
|
禁止把任何既有 STEP-062 修改、历史 untracked、build/cache/android/tgz 一并提交。
|
||||||
|
|
||||||
|
完成后:
|
||||||
|
|
||||||
|
1. 创建 scoped commit;
|
||||||
|
2. push 到 `origin/particle/main`;
|
||||||
|
3. 确认远端 HEAD;
|
||||||
|
4. 在完成报告记录 commit hash、commit message、push 结果、最终远端分支/HEAD。
|
||||||
|
|
||||||
|
push 失败不得声称任务完成,必须明确报告原因。
|
||||||
|
## 19. 完成报告格式
|
||||||
|
|
||||||
|
完成报告必须按必做项逐项写:
|
||||||
|
|
||||||
|
```text
|
||||||
|
已完成 / 未完成 / 无法验证
|
||||||
|
```
|
||||||
|
|
||||||
|
并明确:
|
||||||
|
|
||||||
|
- 本任务是否修改生产代码:预期“否”;
|
||||||
|
- WeatherV2 是否被修改:预期“否”;
|
||||||
|
- Android Gradle / APK / ADB:预期“未执行,按任务边界”;
|
||||||
|
- Jest:预期“未加入、未执行”;
|
||||||
|
- 当前最终 V3 schema 推荐;
|
||||||
|
- 下一实施任务建议名称与范围;
|
||||||
|
- 所有未解决设计风险。
|
||||||
|
|
||||||
|
## 20. 最终完成标准
|
||||||
|
|
||||||
|
只有以下全部满足,STEP-065 才可标记 CLOSED / PASS:
|
||||||
|
|
||||||
|
1. 当前实际代码调查完整,并有文件/符号级依据;
|
||||||
|
2. V3 Scalar/Vector/validity Contract 已冻结;
|
||||||
|
3. Renderer data source 与 VectorSemantics 已冻结;
|
||||||
|
4. binary submission 与 schema version 路线已冻结;
|
||||||
|
5. Timeline/Runtime Window/GPU reuse 保护方案明确;
|
||||||
|
6. V3 分批实现和最终一次 Android 综合验收路线明确;
|
||||||
|
7. 四份正式文档 + 粒子开发记录完成;
|
||||||
|
8. 无生产代码、WeatherV2、NavSea 和无关工作区文件被修改;
|
||||||
|
9. scoped commit 已成功 push 到 `origin/particle/main`。
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
# STEP-066 MapLibreParticleLab V3-02 Core Contract + Scalar Snapshot/Adapter — Codex任务 v1
|
||||||
|
|
||||||
|
## 0. 任务定位
|
||||||
|
|
||||||
|
本任务承接 STEP-065 已冻结的 MapLibreParticleLab V3 架构,只实施 **Core Contract / Snapshot / Adapter 层**。
|
||||||
|
|
||||||
|
目标:把当前 Wind-only、单 U/V payload 的 `maplibre-particle-field/v1` Core 输入模型,升级为 V3 内部统一的 generic snapshot,支持:
|
||||||
|
|
||||||
|
- vector-only
|
||||||
|
- scalar-only
|
||||||
|
- scalar + vector
|
||||||
|
- shared validity
|
||||||
|
- vector semantics: `VELOCITY | DIRECTION`
|
||||||
|
- v1 vector-only 作为最薄兼容 ingress normalize 到同一套 V3 snapshot
|
||||||
|
|
||||||
|
本任务不进入 GPU、shader、Android/JNI、RN、NavSea、WeatherV2,也不做 Android 构建。
|
||||||
|
|
||||||
|
## 1. 当前基线
|
||||||
|
|
||||||
|
仓库:`/mnt/sda1/MapLibreParticleLab`
|
||||||
|
|
||||||
|
分支:`particle/main`
|
||||||
|
|
||||||
|
任务书创建时 HEAD / origin:
|
||||||
|
|
||||||
|
`d23ae2079dcfef3ce48517f734fe487207e28d23`
|
||||||
|
|
||||||
|
执行时必须重新确认 branch、HEAD、origin 和 `git status --short`。若远端已合法推进,先阅读新增提交,不得 reset、checkout 覆盖或丢弃用户修改。
|
||||||
|
|
||||||
|
当前工作区已有 STEP-062 用户修改和大量历史 untracked/build 产物,全部视为无关用户内容。禁止 `git add .`、`git add -A`、clean/reset/stash 覆盖它们。
|
||||||
|
|
||||||
|
## 2. 开工前必须阅读
|
||||||
|
|
||||||
|
必须完整阅读:
|
||||||
|
|
||||||
|
- `AGENTS.md`
|
||||||
|
- `PARTICLE_BASELINE.md`
|
||||||
|
- `docs/00_项目规范/`
|
||||||
|
- `docs/20_调查/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField现状调查_v1.md`
|
||||||
|
- `docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md`
|
||||||
|
- `docs/50_Step记录/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_v1.md`
|
||||||
|
- `docs/报告/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_Codex完成报告_v1.md`
|
||||||
|
- 当前 `field_data_adapter.*`
|
||||||
|
- 当前 `local_particle_field_feed.*`
|
||||||
|
- 当前 `raster_array_source.*` 中 host field parser / Timeline / Runtime Window 相关实现
|
||||||
|
- 与 `ParticleFieldDescriptor`、`ParticleFieldSnapshot`、`ParticleFieldTimeWindow`、`sameFieldGpuContent`、`fieldIdentityChanged` 直接相关的所有调用点
|
||||||
|
- 现有相关 C++ tests/fixtures
|
||||||
|
|
||||||
|
不得根据 STEP-065 文档示例直接猜当前源码。
|
||||||
|
|
||||||
|
## 3. 冻结架构,禁止重新讨论
|
||||||
|
|
||||||
|
STEP-065 已冻结以下方向,本任务必须按此实现,不重新发明第二套模型:
|
||||||
|
|
||||||
|
1. 新 canonical schema:`maplibre-particle-field/v2`。
|
||||||
|
2. 一个 Snapshot:0/1 scalar + 0/1 vector + 1 shared validity。
|
||||||
|
3. scalar-only、vector-only、scalar+vector 合法;neither 非法。
|
||||||
|
4. vector 为 `float32-le / interleaved-uv`,U eastward、V northward。
|
||||||
|
5. scalar 为 `float32-le` 单 plane。
|
||||||
|
6. validity 为显式 shared logical validity;V3 Core 内统一为 `std::vector<uint8_t>` 或现有等价表示。
|
||||||
|
7. `VectorSemantics = VELOCITY | DIRECTION`,只在 vector present 时合法。
|
||||||
|
8. `fieldType` / `displayDomain` 不再作为 Core capability 或 validity 判断依据。
|
||||||
|
9. Renderer 的 `FieldValueSource` 属于后续 V3-03,不在本任务实现。
|
||||||
|
10. Timeline / T0-T1 / MIX / Pause / rollover ownership 不重构。
|
||||||
|
|
||||||
|
## 4. 本任务必须形成的 Core 模型
|
||||||
|
|
||||||
|
优先扩展现有 `ParticleFieldDescriptor` / `ParticleFieldSnapshot`,不要创建 `GenericWeatherField`、`RainField`、`WaveField` 等平行模型。
|
||||||
|
|
||||||
|
目标语义至少包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ParticleFieldSnapshot
|
||||||
|
common identity/grid/time
|
||||||
|
scalar? : width*height float
|
||||||
|
vector? : width*height*2 float interleaved UV
|
||||||
|
valid : width*height uint8 shared validity
|
||||||
|
vectorSemantics? : VELOCITY | DIRECTION
|
||||||
|
```
|
||||||
|
|
||||||
|
字段命名可结合现有代码做最小调整,但必须 generic,不出现 Wind/Rain/Wave/Tide 业务枚举。
|
||||||
|
|
||||||
|
## 5. v2 Descriptor / Plane Bundle
|
||||||
|
|
||||||
|
本任务需要在 Core 层定义明确的 v2 descriptor 和 decode 输入形态。
|
||||||
|
|
||||||
|
推荐采用独立 plane bundle,而不是把三种数据重新塞回一个 ambiguous `std::vector<char>`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ParticleFieldPayloadBundle
|
||||||
|
scalar? bytes
|
||||||
|
vector? bytes
|
||||||
|
validity? bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
具体 C++ 类型可根据当前接口选择 `std::optional<std::vector<char>>`、span/view 或等价简单结构,但不能引入大型新抽象。
|
||||||
|
|
||||||
|
必须保证:
|
||||||
|
|
||||||
|
- vector plane expected bytes = `cells * 2 * sizeof(float)`
|
||||||
|
- scalar plane expected bytes = `cells * sizeof(float)`
|
||||||
|
- validity plane expected bytes = `cells`(uint8)
|
||||||
|
- descriptor 声明的 channel presence 与实际 payload presence 一致
|
||||||
|
- 所有 float 必须 finite,除明确 noData 策略外不得接受 NaN/Inf
|
||||||
|
- shared validity 为 0 的 cell 可以允许 payload 内存在 finite placeholder/noData;validity=1 的 cell 对 present channel 必须是有效 finite 数据
|
||||||
|
- 不允许 neither
|
||||||
|
- DIRECTION 但无 vector 必须 reject
|
||||||
|
- vector present 但缺 vector semantics 必须 reject(除 v1 normalize)
|
||||||
|
|
||||||
|
## 6. v1 最薄兼容 ingress
|
||||||
|
|
||||||
|
现有 `maplibre-particle-field/v1` 必须继续可读,但不要维护第二套 Runtime。
|
||||||
|
|
||||||
|
v1 只支持现有 vector-only:
|
||||||
|
|
||||||
|
- `interleaved-uv`
|
||||||
|
- `float32-le`
|
||||||
|
- validity 从当前 U/V noData 规则派生
|
||||||
|
- normalize 后:`vector present`、`scalar absent`、`VectorSemantics::VELOCITY`
|
||||||
|
|
||||||
|
兼容层应该尽量放在 parser/adapter ingress,进入 Timeline 前就成为与 v2 相同的统一 snapshot。
|
||||||
|
|
||||||
|
不得给 v1 增加 scalar 字段、不得重新定义 v1 payload。
|
||||||
|
|
||||||
|
## 7. Identity / compatibility / hashing
|
||||||
|
|
||||||
|
必须重新审查并调整:
|
||||||
|
|
||||||
|
- `ParticleFieldSnapshot::isValid()`
|
||||||
|
- `isTimeInterpolationCompatibleWith()`
|
||||||
|
- `fieldIdentityChanged()`
|
||||||
|
- `sameFieldGpuContent()`
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
### isValid
|
||||||
|
不再硬编码 `fieldType == WIND` / `displayDomain == GLOBAL`。
|
||||||
|
|
||||||
|
必须验证至少:identity、generation、validTime、grid/bounds、channel presence、plane sizes、shared validity size、vector semantics 与 channel presence 的一致性。
|
||||||
|
|
||||||
|
### Timeline compatibility
|
||||||
|
T0/T1 必须至少保持:
|
||||||
|
|
||||||
|
- width/height 相同
|
||||||
|
- bounds 相同
|
||||||
|
- scalar presence 相同
|
||||||
|
- vector presence 相同
|
||||||
|
- vector semantics 相同
|
||||||
|
- plane encoding/layout 兼容
|
||||||
|
|
||||||
|
不得允许 scalar-only 与 vector-only 被当成同一 interpolation window。
|
||||||
|
|
||||||
|
### GPU content identity
|
||||||
|
虽然本任务不改 GPU,但 `sameFieldGpuContent()` 必须能够在未来正确区分 scalar/vector/validity 内容变化。
|
||||||
|
|
||||||
|
如果当前单 `fieldSha256` 已无法无歧义表示多 plane,应在 Core contract 中明确设计并实现最小 content identity,例如 per-plane hash 或一个明确的 aggregate hash。不得靠 fieldId/dataVersion 猜内容相同。
|
||||||
|
|
||||||
|
## 8. RasterArraySource host parser 范围
|
||||||
|
|
||||||
|
本任务只允许修改 host metadata/parser 到能够构造 v1/v2 Core descriptor/plane bundle 的程度。
|
||||||
|
|
||||||
|
若当前 `RasterArraySource::submitHostField(metadataText, payload)` 是唯一入口,允许在 C++ 层增加一个 **Core-only v2 overload / typed internal entry**,用于 tests/fixtures 直接提交 plane bundle。
|
||||||
|
|
||||||
|
但本任务禁止:
|
||||||
|
|
||||||
|
- 修改 Java/Kotlin API
|
||||||
|
- 修改 JNI signature
|
||||||
|
- 修改 RN API
|
||||||
|
- 修改已发布 `.2` package
|
||||||
|
|
||||||
|
因此 Android/RN 仍只能走现有 v1 入口是允许的;v2 正式跨 JNI ingress 留到 V3-05。
|
||||||
|
|
||||||
|
## 9. Local fixture / feed
|
||||||
|
|
||||||
|
若 `local_particle_field_feed` 或现有 C++ fixture 是最合适的验证入口,可做最小扩展以生成:
|
||||||
|
|
||||||
|
1. v1 vector-only fixture
|
||||||
|
2. v2 vector-only fixture
|
||||||
|
3. v2 scalar-only fixture
|
||||||
|
4. v2 scalar+vector fixture
|
||||||
|
5. invalid/neither fixture
|
||||||
|
6. channel mismatch fixture
|
||||||
|
|
||||||
|
Fixture 只用于 Core contract 验证,不做 GPU 可视化。
|
||||||
|
|
||||||
|
## 10. 必做验证
|
||||||
|
|
||||||
|
不加入 Jest。
|
||||||
|
|
||||||
|
必须使用现有 C++ 测试体系或最小精确 executable/test,至少验证:
|
||||||
|
|
||||||
|
- v1 Wind-style vector-only decode PASS,并 normalize 为 VELOCITY vector
|
||||||
|
- v2 vector-only PASS
|
||||||
|
- v2 scalar-only PASS
|
||||||
|
- v2 scalar+vector PASS
|
||||||
|
- neither REJECT
|
||||||
|
- scalar byteLength mismatch REJECT
|
||||||
|
- vector byteLength mismatch REJECT
|
||||||
|
- validity byteLength mismatch REJECT
|
||||||
|
- missing vector semantics REJECT
|
||||||
|
- DIRECTION without vector REJECT
|
||||||
|
- NaN/Inf in valid cell REJECT
|
||||||
|
- invalid cell 可按冻结策略安全接受 finite noData/placeholder
|
||||||
|
- T0/T1 compatible:same channel shape/semantics PASS
|
||||||
|
- scalar-only vs vector-only incompatible
|
||||||
|
- VELOCITY vs DIRECTION incompatible
|
||||||
|
- changed scalar/vector/validity content 不会被 `sameFieldGpuContent()` 误判为相同
|
||||||
|
|
||||||
|
执行规定的精确编译/测试,编译输出遵循全局 `$build-output-throttle`。
|
||||||
|
|
||||||
|
不得执行 Android Gradle、AAR、APK、ADB。
|
||||||
|
|
||||||
|
## 11. 回归保护线
|
||||||
|
|
||||||
|
本任务不得破坏:
|
||||||
|
|
||||||
|
- 当前 v1 canonical Wind fixture parser
|
||||||
|
- Source-owned Timeline
|
||||||
|
- Runtime Field Window
|
||||||
|
- T0/T1/MIX
|
||||||
|
- Pause semantics
|
||||||
|
- OLD_T1→NEW_T0 reuse 的现有决策逻辑
|
||||||
|
- normal rollover 时 `simulationRebuild=false / trailCleared=false` 的架构条件
|
||||||
|
- WeatherColorLayer / RasterParticleLayer 分离
|
||||||
|
|
||||||
|
本任务不要求 renderer 能消费 scalar;在 V3-03 前 scalar snapshot 进入 renderer 应保持 fail-closed/unavailable,而不是伪造成 U/V。
|
||||||
|
|
||||||
|
## 12. 禁止事项
|
||||||
|
|
||||||
|
不得:
|
||||||
|
|
||||||
|
- 修改 shader 或 shader generator
|
||||||
|
- 修改 `render_weather_color_layer.cpp` / `render_particle_validation_layer.cpp` 的渲染语义,除非纯编译适配且不改变行为;若确需适配必须在报告逐项列出
|
||||||
|
- 增加 FieldValueSource
|
||||||
|
- 实现 DIRECTION particle movement
|
||||||
|
- 增加 Wave physics
|
||||||
|
- 修改 Android/JNI/Java/Kotlin
|
||||||
|
- 修改 RN package/API/version
|
||||||
|
- 修改 Test Host JS/TS
|
||||||
|
- 修改 WeatherV2 / NavSea
|
||||||
|
- 解析 NSFT/PMTiles
|
||||||
|
- 新增 provider/network/cache
|
||||||
|
- 设计 N scalar/N vector channel framework
|
||||||
|
- 做 JSI/zero-copy 重构
|
||||||
|
- 执行 Jest
|
||||||
|
- 执行 Android build/install/ADB
|
||||||
|
|
||||||
|
## 13. 文档输出
|
||||||
|
|
||||||
|
必须新增/更新:
|
||||||
|
|
||||||
|
- `docs/50_Step记录/STEP-066_MapLibreParticleLab_V3-02_CoreContract与ScalarSnapshotAdapter_v1.md`
|
||||||
|
- `docs/报告/STEP-066_MapLibreParticleLab_V3-02_CoreContract与ScalarSnapshotAdapter_Codex完成报告_v1.md`
|
||||||
|
- 追加 `粒子开发记录.md`
|
||||||
|
|
||||||
|
若实施中对 STEP-065 冻结设计产生必要的小修正,必须更新 `docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md` 并明确原因;禁止静默改变设计。
|
||||||
|
|
||||||
|
所有文档使用中文。
|
||||||
|
|
||||||
|
## 14. 必做清单
|
||||||
|
|
||||||
|
结束前逐项报告“已完成 / 未完成 / 无法验证”:
|
||||||
|
|
||||||
|
- [ ] 阅读规范、STEP-065 与当前实际源码
|
||||||
|
- [ ] 确认 branch/HEAD/origin/dirty worktree
|
||||||
|
- [ ] 定义 V3 Core generic snapshot/descriptor/payload bundle
|
||||||
|
- [ ] 去除 Core validity 中 WIND/GLOBAL capability hardcode
|
||||||
|
- [ ] 实现 v2 vector-only
|
||||||
|
- [ ] 实现 v2 scalar-only
|
||||||
|
- [ ] 实现 v2 scalar+vector
|
||||||
|
- [ ] 实现 shared validity
|
||||||
|
- [ ] 实现 VectorSemantics contract
|
||||||
|
- [ ] 实现 v1 vector-only thin normalize
|
||||||
|
- [ ] 调整 interpolation compatibility
|
||||||
|
- [ ] 调整 content identity / sameFieldGpuContent
|
||||||
|
- [ ] 保持 Timeline/Runtime Window ownership 不变
|
||||||
|
- [ ] 加入最小 Core fixtures/tests
|
||||||
|
- [ ] 完成精确 Linux/Core 编译/测试
|
||||||
|
- [ ] `git diff --check` PASS
|
||||||
|
- [ ] 确认无 Android/JNI/RN/WeatherV2/NavSea 修改
|
||||||
|
- [ ] 生成 Step、完成报告、追加开发记录
|
||||||
|
|
||||||
|
不得静默跳过。
|
||||||
|
|
||||||
|
## 15. Git 收尾
|
||||||
|
|
||||||
|
完成本任务规定修改与文档后,只提交本任务相关文件到当前 `particle/main` 并 push `origin/particle/main`。
|
||||||
|
|
||||||
|
禁止提交现有 STEP-062 用户修改、历史 untracked、build/cache/android/tgz。
|
||||||
|
|
||||||
|
必须使用显式文件列表 `git add -- <files...>`,禁止 `git add .` / `-A`。
|
||||||
|
|
||||||
|
完成报告必须记录:
|
||||||
|
|
||||||
|
- source/function commit hash 与 message
|
||||||
|
- docs handoff commit(如有)
|
||||||
|
- push 结果
|
||||||
|
- 最终 `HEAD`
|
||||||
|
- 最终 `origin/particle/main`
|
||||||
|
- 未完成/无法验证项
|
||||||
|
|
||||||
|
push 失败不得声称完成。
|
||||||
|
|
||||||
|
## 16. 完成门槛
|
||||||
|
|
||||||
|
STEP-066 只有同时满足以下条件才可 CLOSED / PASS:
|
||||||
|
|
||||||
|
1. v1 vector-only 继续可 decode,并 normalize 到统一 V3 snapshot;
|
||||||
|
2. v2 vector-only/scalar-only/scalar+vector 三种组合全部 Core 验证通过;
|
||||||
|
3. neither、尺寸/semantic/finite/validity 错误全部 fail-closed;
|
||||||
|
4. Timeline compatibility 已按 channel presence + vector semantics 收紧;
|
||||||
|
5. content identity 不会把 plane 内容变化误判为 GPU same-content;
|
||||||
|
6. 未改 GPU 渲染语义、Android/JNI/RN、WeatherV2/NavSea;
|
||||||
|
7. 精确 Linux/Core 编译/测试与 `git diff --check` 通过;
|
||||||
|
8. Step/报告/开发记录完成;
|
||||||
|
9. scoped commit 成功 push 到 `origin/particle/main`。
|
||||||
|
|
||||||
|
下一步仅在 STEP-066 CLOSED / PASS 后进入 V3-03:Scalar GPU Texture + Generic Field Renderer。
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
# STEP-067 MapLibreParticleLab V3-03 Scalar GPU + Generic Field Renderer — Codex任务 v1
|
||||||
|
|
||||||
|
## 0. 任务定位
|
||||||
|
|
||||||
|
本任务承接已关闭的 STEP-066,只实施 **GPU field bundle / WeatherColor generic value source**。
|
||||||
|
|
||||||
|
目标:让 V3 scalar snapshot 首次可由 WeatherColorLayer 真正渲染,并保留 vector magnitude 色场;不实现 DIRECTION 粒子推进,不做 JNI/RN v2 ingress,不做 Android 编译。
|
||||||
|
|
||||||
|
## 1. 当前基线
|
||||||
|
|
||||||
|
仓库:`/mnt/sda1/MapLibreParticleLab`
|
||||||
|
|
||||||
|
分支:`particle/main`
|
||||||
|
|
||||||
|
任务书创建时:
|
||||||
|
|
||||||
|
`HEAD == origin/particle/main == 9dabb95e4425ffbcb863c520923f5d4b537a3feb`
|
||||||
|
|
||||||
|
执行时重新确认 HEAD、origin、`git status --short`,不得 reset/clean/stash。
|
||||||
|
|
||||||
|
STEP-062 用户修改、历史 untracked、build/cache/tgz 均视为无关内容,禁止 `git add .` / `git add -A`。
|
||||||
|
## 2. 开工前必须完整阅读
|
||||||
|
|
||||||
|
- `AGENTS.md`
|
||||||
|
- `PARTICLE_BASELINE.md`
|
||||||
|
- `docs/00_项目规范/`
|
||||||
|
- `docs/20_调查/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField现状调查_v1.md`
|
||||||
|
- `docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md`
|
||||||
|
- `docs/50_Step记录/STEP-066_MapLibreParticleLab_V3-02_CoreContract与ScalarSnapshotAdapter_v1.md`
|
||||||
|
- `docs/报告/STEP-066_MapLibreParticleLab_V3-02_CoreContract与ScalarSnapshotAdapter_Codex完成报告_v1.md`
|
||||||
|
- 当前 `field_data_adapter.*`
|
||||||
|
- 当前 `render_weather_color_layer.*`
|
||||||
|
- 当前 `render_particle_validation_layer.*` 中 field texture / upload / reuse 路径
|
||||||
|
- `weather_color_layer.*`、`weather_color_stop.hpp`、相关 shader/UBO/generator 与 tests
|
||||||
|
|
||||||
|
不得按任务书猜源码;先以当前 HEAD 为准。
|
||||||
|
|
||||||
|
## 3. 冻结语义
|
||||||
|
|
||||||
|
1. `FieldValueSource = SCALAR | VECTOR_MAGNITUDE`,必须显式选择,不能根据 channel presence 猜。
|
||||||
|
2. `SCALAR` 要求 scalar present;`VECTOR_MAGNITUDE` 要求 vector present;不满足则 WeatherColor renderer fail-closed/unavailable。
|
||||||
|
3. Wind/Tide 使用 `VECTOR_MAGNITUDE`;Rain/Wave color 使用 `SCALAR`。
|
||||||
|
4. Particle renderer 本任务仍只接受 `VectorSemantics::Velocity`;DIRECTION 留 V3-04。
|
||||||
|
5. T0/T1/MIX、Pause、Timeline ownership、OLD_T1→NEW_T0 reuse、only NEW_T1 upload 的架构不变。
|
||||||
|
6. WeatherColorLayer 与 RasterParticleLayer 继续分离,但消费同一 Source-owned snapshots。
|
||||||
|
7. `FieldColorStop.value` 是 generic 值。C++ 内部 `speedMps` 命名必须在本 Step 收敛为 generic `value`/`fieldValue`;Android/JNI/RN 旧 ABI 本 Step 不修改。
|
||||||
|
## 4. 必做实现
|
||||||
|
|
||||||
|
### A. Generic FieldValueSource
|
||||||
|
|
||||||
|
在 C++ style/render 层新增最小 enum/config,推荐:`enum class FieldValueSource { Scalar, VectorMagnitude };`。
|
||||||
|
|
||||||
|
放在与 WeatherColorLayer 最贴近且不引入业务域的位置。WeatherColorLayer 必须持有明确 value source;默认值必须保持现有 Wind 行为,即 `VECTOR_MAGNITUDE`,保证旧路径兼容。
|
||||||
|
|
||||||
|
### B. WeatherColor scalar/vector GPU 输入
|
||||||
|
|
||||||
|
扩展现有 WeatherColor field texture 生成/上传,使:
|
||||||
|
|
||||||
|
- `VECTOR_MAGNITUDE`:继续从 vector U/V 得到 magnitude 语义;不得改变现有 Wind 可视结果。
|
||||||
|
- `SCALAR`:真正上传/编码 scalar 值,shader 根据 T0/T1 MIX 对 scalar 做时间插值后进入相同 generic color ramp。
|
||||||
|
- shared validity 必须进入 alpha/有效性判断;T0/T1 任一无效时按现有 fail-closed 策略不可显示错误值。
|
||||||
|
- 不允许 scalar 伪装成 U/V。
|
||||||
|
|
||||||
|
可继续复用现有 texture/UBO/shader 基础设施,但要让 field texture 的数据语义显式,不要复制第二套 WeatherColor renderer。
|
||||||
|
|
||||||
|
### C. Generic color ramp 命名
|
||||||
|
|
||||||
|
把 C++ Core/style/render/shader 中与 WeatherColor 色阶相关的 `speedMps` / 固定 speed 语义改成 generic value。至少:
|
||||||
|
|
||||||
|
- `WeatherColorStop.speedMps` → generic `value`
|
||||||
|
- 校验逻辑变成 finite、严格递增 generic value
|
||||||
|
- shader/UBO 注释和日志不再声称单位固定 m/s
|
||||||
|
- 删除/替换 `scale=0..30mps` 这类 Wind-only 日志
|
||||||
|
|
||||||
|
不得改变 RN `.2` 公共 API,也不得修改 Android/JNI/Kotlin/TS。
|
||||||
|
### D. Particle renderer 保护
|
||||||
|
|
||||||
|
本任务不得实现 DIRECTION advection。现有 RasterParticleLayer 必须显式要求:
|
||||||
|
|
||||||
|
- vector present
|
||||||
|
- `VectorSemantics::Velocity`
|
||||||
|
|
||||||
|
scalar-only 或 DIRECTION vector 必须 fail-closed,不创建/继续使用错误粒子资源。
|
||||||
|
|
||||||
|
### E. GPU reuse/content identity
|
||||||
|
|
||||||
|
复核 WeatherColor 的 T0/T1 resource reuse:
|
||||||
|
|
||||||
|
- normal boundary OLD_T1→NEW_T0 可复用相同 decoded plane bundle
|
||||||
|
- 新 T1 只上传新需要的 texture
|
||||||
|
- scalar/vector/validity 变化必须触发正确 content change
|
||||||
|
- 正常 playback 不允许因为 generic value source 改造而重建 Particle simulation 或 clear trail
|
||||||
|
|
||||||
|
如果当前 `sameFieldGpuContent()` 已满足,无需再改 Core Contract。
|
||||||
|
|
||||||
|
## 5. Shader 约束
|
||||||
|
|
||||||
|
允许修改 WeatherColor 实际使用的 shader/UBO/generator,只限完成 generic field value。必须保持:
|
||||||
|
|
||||||
|
- T0/T1 GPU 时间插值
|
||||||
|
- spatial sampling/bounds
|
||||||
|
- current vector particle shader physics 不变
|
||||||
|
- 不把 provider、WeatherV2、Rain/Wave/Tide 业务名写进 shader
|
||||||
|
- 不增加第二套专用 rain/wave shader
|
||||||
|
|
||||||
|
如果同一 shader 当前同时承载 particle/weather pass,可做最小 pass/value-source 分支,但不能借此重构整个粒子 shader 架构。
|
||||||
|
## 6. 最小验证
|
||||||
|
|
||||||
|
不加入 Jest。使用现有 C++/OpenGL 测试体系,至少覆盖:
|
||||||
|
|
||||||
|
- WeatherColor default `VECTOR_MAGNITUDE` 保持旧 Wind 行为
|
||||||
|
- scalar-only + `SCALAR` 可建立 field texture/render resources
|
||||||
|
- vector-only + `VECTOR_MAGNITUDE` 可建立资源
|
||||||
|
- scalar+vector 可由 value source 明确选择对应 channel
|
||||||
|
- scalar-only + `VECTOR_MAGNITUDE` fail-closed
|
||||||
|
- vector-only + `SCALAR` fail-closed
|
||||||
|
- T0/T1 channel/valueSource compatibility 错误不被渲染
|
||||||
|
- scalar T0/T1 MIX 数值路径正确
|
||||||
|
- vector magnitude T0/T1 MIX 不回归
|
||||||
|
- validity mask 对 scalar/vector 都生效
|
||||||
|
- DIRECTION vector 在 Particle renderer fail-closed
|
||||||
|
- existing v1 Wind fixture/source tests 回归通过
|
||||||
|
- `git diff --check` PASS
|
||||||
|
|
||||||
|
执行规定的 Linux OpenGL 精确编译/测试,遵循全局 `$build-output-throttle`。
|
||||||
|
|
||||||
|
不得执行 Android Gradle、AAR、APK、ADB。
|
||||||
|
|
||||||
|
## 7. 硬边界
|
||||||
|
|
||||||
|
本任务禁止修改 Android/JNI/Java/Kotlin、RN package/API/version、Test Host JS/TS、WeatherV2、NavSea;禁止实现 DIRECTION particle movement / wave physics;禁止解析 NSFT/PMTiles;禁止新增 provider/network/cache;禁止重构 Timeline/Playback/Runtime Window;禁止新建 RainRenderer/WaveRenderer/TideRenderer;禁止 Jest 与 Android build/install/ADB。
|
||||||
|
## 8. 文档输出
|
||||||
|
|
||||||
|
必须新增/更新:
|
||||||
|
|
||||||
|
- `docs/50_Step记录/STEP-067_MapLibreParticleLab_V3-03_ScalarGPU与GenericFieldRenderer_v1.md`
|
||||||
|
- `docs/报告/STEP-067_MapLibreParticleLab_V3-03_ScalarGPU与GenericFieldRenderer_Codex完成报告_v1.md`
|
||||||
|
- 追加 `粒子开发记录.md`
|
||||||
|
|
||||||
|
所有文档中文。报告逐项列出生产代码修改文件、Renderer/shader 语义变化、精确编译/测试结果、未执行 Android/Jest、未触碰无关工作区。
|
||||||
|
|
||||||
|
## 9. 必做清单
|
||||||
|
|
||||||
|
- [ ] 复读规范、STEP-065/066 与当前源码
|
||||||
|
- [ ] 引入显式 `FieldValueSource::Scalar / VectorMagnitude`
|
||||||
|
- [ ] 默认保持 VectorMagnitude 旧 Wind 行为
|
||||||
|
- [ ] WeatherColor scalar GPU path 可用
|
||||||
|
- [ ] WeatherColor vector magnitude path 回归
|
||||||
|
- [ ] shared validity 同时保护 scalar/vector
|
||||||
|
- [ ] C++ WeatherColor `speedMps` 语义改为 generic value
|
||||||
|
- [ ] Particle renderer 拒绝 scalar-only 和 DIRECTION
|
||||||
|
- [ ] T0/T1/MIX 与 resource reuse 不回归
|
||||||
|
- [ ] 最小精确 Linux/OpenGL tests PASS
|
||||||
|
- [ ] `git diff --check` PASS
|
||||||
|
- [ ] Step/报告/开发记录完成
|
||||||
|
- [ ] scoped Git commit + push `origin/particle/main`
|
||||||
|
## 10. Git 收尾
|
||||||
|
|
||||||
|
完成后只显式 `git add` 本任务文件与实际修改文件,严禁把 STEP-062 用户修改、历史 untracked、build/cache/tgz 带入提交。
|
||||||
|
|
||||||
|
创建合理 scoped commit;更新完成报告记录全部 commit hash/message、push 结果与最终远端分支。
|
||||||
|
|
||||||
|
必须 push 到 `origin/particle/main`,最终复核:
|
||||||
|
|
||||||
|
`HEAD == origin/particle/main`
|
||||||
|
|
||||||
|
push 失败不得声称完成。
|
||||||
|
|
||||||
|
完成后直接按必做清单报告“已完成 / 未完成 / 无法验证”,不要输出实施计划。
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
# STEP-068 MapLibreParticleLab V3-04 DIRECTION 粒子推进 — Codex 任务 v1
|
||||||
|
|
||||||
|
执行状态(2026-09-11):IMPLEMENTED / WAITING_VALIDATION。C++/shader scoped 实现与 shader generator 已完成;本沙箱对最终含新增 source test 的精确编译连续在单次 30 秒终端上限前中止,未将其或全量 Ninja 写为 PASS。详见同编号 Step 记录与完成报告。
|
||||||
|
|
||||||
|
## 0. 任务定位
|
||||||
|
|
||||||
|
本任务承接已关闭的 STEP-065/066/067,在 MapLibreParticleLab V3 Core/GPU 中实现第二种通用 VectorSemantics:`DIRECTION`。
|
||||||
|
|
||||||
|
目标不是 Wave 业务实现,而是让任意 canonical direction-only vector field 可以驱动 RasterParticleLayer。Core/renderer 仍不得理解 WIND/WAVE/RAIN/TIDE、WeatherV2、NSFT 或 provider。
|
||||||
|
|
||||||
|
本任务只做到 C++/shader/Linux OpenGL 可验证闭环;不得进入 Android/JNI/RN v2 ingress,不执行 Android Gradle/AAR/APK/ADB,不加入或执行 Jest。
|
||||||
|
|
||||||
|
## 1. 仓库与基线
|
||||||
|
|
||||||
|
仓库:`/mnt/sda1/MapLibreParticleLab`
|
||||||
|
分支:`particle/main`
|
||||||
|
任务书编写时 HEAD:`89c72554c5fb5cd469bdd8ba06ff4b03a1a67ff9`
|
||||||
|
该 HEAD 已等于 `origin/particle/main`,STEP-067 为 `CLOSED / PASS`。
|
||||||
|
|
||||||
|
开工时必须重新确认 branch / HEAD / origin / `git status --short`。若远端已合法推进,先阅读新增提交,不得 reset。
|
||||||
|
|
||||||
|
当前工作区仍有 STEP-062 两处用户修改和历史 untracked/build/cache/tgz。全部保留;禁止 `git add .` / `git add -A`,禁止恢复、删除、覆盖或提交无关内容。
|
||||||
|
|
||||||
|
## 2. 必须阅读
|
||||||
|
|
||||||
|
至少阅读:
|
||||||
|
|
||||||
|
- `AGENTS.md`
|
||||||
|
- `PARTICLE_BASELINE.md`
|
||||||
|
- `docs/00_项目规范/*.md`
|
||||||
|
- `docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md`
|
||||||
|
- `docs/50_Step记录/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_v1.md`
|
||||||
|
- `docs/50_Step记录/STEP-066_MapLibreParticleLab_V3-02_CoreContract与ScalarSnapshotAdapter_v1.md`
|
||||||
|
- `docs/50_Step记录/STEP-067_MapLibreParticleLab_V3-03_ScalarGPU与GenericFieldRenderer_v1.md`
|
||||||
|
- `include/mbgl/particle/field/field_data_adapter.hpp`
|
||||||
|
- `src/mbgl/particle/field/field_data_adapter.cpp`
|
||||||
|
- `src/mbgl/renderer/layers/render_particle_validation_layer.cpp`
|
||||||
|
- `include/mbgl/style/layers/raster_particle_layer.hpp`
|
||||||
|
- `src/mbgl/style/layers/raster_particle_layer.cpp`
|
||||||
|
- `src/mbgl/style/layers/raster_particle_layer_impl.hpp`
|
||||||
|
- `src/mbgl/style/layers/particle_render_layer_properties.hpp`
|
||||||
|
- `shaders/particle_gpu_validation.vertex.glsl`
|
||||||
|
- `shaders/particle_gpu_validation.fragment.glsl`
|
||||||
|
- `include/mbgl/shaders/particle_gpu_validation_ubo.hpp`
|
||||||
|
- 对应 generated OpenGL shader header 与现有 layer/source tests。
|
||||||
|
|
||||||
|
实现必须以实际源码为准,不得按任务书示例猜接口。
|
||||||
|
|
||||||
|
## 3. 冻结语义
|
||||||
|
|
||||||
|
V3 vector semantics 只有:
|
||||||
|
|
||||||
|
- `VELOCITY`:canonical U east / V north,物理速度,保持现有 m/s Web Mercator 推进路径。
|
||||||
|
- `DIRECTION`:canonical U east / V north,只表达方向;输入通常为单位向量,但 GPU 不可假设时间插值后仍为单位长度。
|
||||||
|
|
||||||
|
`DIRECTION` 的 GPU 规则必须是:
|
||||||
|
|
||||||
|
1. 分别解码 T0/T1 vector;
|
||||||
|
2. 按当前 `timeMix` 对 U/V component-wise 插值;
|
||||||
|
3. 检查 finite;
|
||||||
|
4. 对插值结果求长度;
|
||||||
|
5. 长度 <= epsilon 时 fail-closed;
|
||||||
|
6. 否则 normalize;
|
||||||
|
7. 乘独立的 renderer `advectionScale`;
|
||||||
|
8. 再进入现有 Web Mercator displacement 计算。
|
||||||
|
|
||||||
|
严禁:
|
||||||
|
|
||||||
|
- 先分别 normalize T0/T1 再做最终不归一化 mix;
|
||||||
|
- 使用 scalar height、period 或 vector 原 magnitude 决定推进速度;
|
||||||
|
- 将 DIRECTION 当 m/s;
|
||||||
|
- 在 Core 中出现 Wave 分支。
|
||||||
|
|
||||||
|
反向方向示例:T0=(1,0)、T1=(-1,0)、MIX=0.5,插值向量长度近零,必须 invalid/fail-closed,不得任意选择东或西。
|
||||||
|
|
||||||
|
## 4. VELOCITY 回归保护
|
||||||
|
|
||||||
|
VELOCITY 是已通过 Android 的旧正式路径。本任务必须保持默认行为:
|
||||||
|
|
||||||
|
- 不 normalize VELOCITY;
|
||||||
|
- T0/T1 仍 component-wise mix;
|
||||||
|
- 默认参数下 displacement 公式不变;
|
||||||
|
- U east / V north 与 world-y southward 的符号不变;
|
||||||
|
- latitude/cos(latitude)/Earth circumference 计算不变;
|
||||||
|
- lifecycle、respawn、Trail、T0/T1/MIX、pause、cadence、window rollover 不重写。
|
||||||
|
|
||||||
|
允许为共享 helper 重构 shader,但必须证明 VELOCITY 结果等价。
|
||||||
|
|
||||||
|
## 5. advectionScale
|
||||||
|
|
||||||
|
为 DIRECTION 增加 business-neutral renderer 控制 `advectionScale`。
|
||||||
|
|
||||||
|
优先以 RasterParticleLayer 的 C++ Core style/control property 实现,建议属性名:`raster-particle-advection-scale`,C++ API 为 `getAdvectionScale()/setAdvectionScale()`;最终命名需与现有 property 体系一致。
|
||||||
|
|
||||||
|
冻结要求:
|
||||||
|
|
||||||
|
- 默认值 `1.0`,确保不隐式引入产品级速度偏好;
|
||||||
|
- 必须 finite 且 > 0;需要上限时根据现有数值安全给出保守、文档化的界限,不得随意设为天气业务值;
|
||||||
|
- V3-04 中 `advectionScale` 只改变 DIRECTION 的推进 magnitude;VELOCITY 默认/现有运动数学不因该属性变化而改变;
|
||||||
|
- 不从 scalar、fieldId、fieldType、displayDomain 推导该值;
|
||||||
|
- RN 公共 API 暂不增加,留给 V3-05 一并接线。
|
||||||
|
|
||||||
|
如果当前 CoreOnly layer property 机制不适合新增 serialize property,Codex 可采用最小等价 C++ renderer config,但必须在报告解释原因;不得因此提前改 Android/JNI/RN。
|
||||||
|
|
||||||
|
## 6. Shader/UBO 一致性
|
||||||
|
|
||||||
|
当前 State fragment shader 和正式 particle line vertex shader都会独立读取 `u_field_texture/u_next_field_texture`。
|
||||||
|
|
||||||
|
两者必须使用相同 semantics:
|
||||||
|
|
||||||
|
`decode T0/T1 -> mix -> resolve VELOCITY or DIRECTION -> validity`
|
||||||
|
|
||||||
|
不得出现 State 按 Direction 推进、但 line vertex 仍把 raw mixed direction 当 velocity 的不一致。
|
||||||
|
|
||||||
|
建议增加明确的 UBO advection/semantics 参数,例如独立 vec4,避免挤占已有字段导致语义混乱。若修改 UBO:
|
||||||
|
|
||||||
|
- 同步 vertex GLSL;
|
||||||
|
- 同步 fragment GLSL;
|
||||||
|
- 同步 C++ UBO struct/static_assert;
|
||||||
|
- 运行官方 shader generator 更新 generated OpenGL header;
|
||||||
|
- 不手工只改 generated header。
|
||||||
|
|
||||||
|
helper 命名应从 `fieldVelocityAt` 收敛为 generic advection/vector 语义;不得继续用 velocity 名称表达 DIRECTION 分支。
|
||||||
|
|
||||||
|
## 7. Renderer acceptance
|
||||||
|
|
||||||
|
STEP-067 当前主动拒绝 `VectorSemantics::Direction`。本任务需改为:
|
||||||
|
|
||||||
|
- scalar-only:Particle unavailable/fail-closed;
|
||||||
|
- vector VELOCITY:允许;
|
||||||
|
- vector DIRECTION:允许;
|
||||||
|
- T0/T1 semantics 不一致:继续 fail-closed(正常应已被 Timeline compatibility 拦截,但 renderer 仍防御);
|
||||||
|
- invalid/NaN/near-zero mixed direction:该 sample invalid,按既有 lifecycle/respawn 安全处理;不得产生 NaN position。
|
||||||
|
|
||||||
|
切换 semantics/window 时不得错误复用与新 semantics 不兼容的 GPU resource。既有 `sameFieldGpuContent`/Timeline compatibility 的 semantics 保护必须保留。
|
||||||
|
|
||||||
|
## 8. Trail 与粒子线段
|
||||||
|
|
||||||
|
正式 particle line 必须与 State 实际位移一致:
|
||||||
|
|
||||||
|
- line segment 继续连接 previousState -> currentState;
|
||||||
|
- vertex shader 用于 `v_particle_velocity`/颜色判断的向量必须使用与 State 相同的 resolved advection vector;
|
||||||
|
- DIRECTION 下可把 `length(resolvedAdvection)` 作为纯视觉强度输入,但不得声称它是原始物理 speed;
|
||||||
|
- 不允许用 scalar 驱动 particle color;
|
||||||
|
- 本任务不重新设计 speedColorEnabled 公共能力,若命名仍带历史 speed,可记录技术债,避免扩大范围。
|
||||||
|
|
||||||
|
## 9. Timeline / GPU reuse 硬保护
|
||||||
|
|
||||||
|
不得破坏:
|
||||||
|
|
||||||
|
- Source-owned Timeline;
|
||||||
|
- T0/T1/MIX;
|
||||||
|
- PAUSE;
|
||||||
|
- 连续 MIX 时零 field upload;
|
||||||
|
- OLD_T1 -> NEW_T0 reuse;
|
||||||
|
- 正常 rollover 只上传 NEW_T1;
|
||||||
|
- window overlap 时 `simulationRebuild=false`、`trailCleared=false`;
|
||||||
|
- style/context loss 的既有重建规则;
|
||||||
|
- invalid/gap fail-closed。
|
||||||
|
|
||||||
|
改变 `advectionScale` 不应重新上传 field texture。是否清 trail/simulation 必须依据当前生命周期设计最小化,默认不允许为了纯 renderer 参数变化重新提交 field。
|
||||||
|
|
||||||
|
## 10. 必须补的验证
|
||||||
|
|
||||||
|
本任务需新增/调整最小测试,至少覆盖:
|
||||||
|
|
||||||
|
1. RasterParticleLayer advectionScale 默认值与 set/get/非法值策略;
|
||||||
|
2. VELOCITY 与 DIRECTION renderer eligibility;
|
||||||
|
3. scalar-only 仍 fail-closed;
|
||||||
|
4. T0/T1 semantics mismatch fail-closed/不绘制;
|
||||||
|
5. shader/UBO semantics 参数正确传递;
|
||||||
|
6. DIRECTION 数学:east、north、斜向;
|
||||||
|
7. T0 east + T1 north + MIX=0.5 -> normalize 后 northeast;
|
||||||
|
8. T0 east + T1 west + MIX=0.5 -> near-zero invalid;
|
||||||
|
9. advectionScale 改变 magnitude、不改变 direction;
|
||||||
|
10. VELOCITY 路径不 normalize,默认结果保持现状。
|
||||||
|
|
||||||
|
优先使用当前已有 C++ test/fixture 体系;不要为了测试引入新的大型 framework。
|
||||||
|
|
||||||
|
## 11. Linux 验证
|
||||||
|
|
||||||
|
必须执行:
|
||||||
|
|
||||||
|
- `node shaders/generate_shader_code.mjs`;
|
||||||
|
- 受影响 C++ 精确对象编译;
|
||||||
|
- 相关现有/新增过滤测试;
|
||||||
|
- 在可行时 `ninja -C build-linux-opengl -j3 mbgl-test mbgl-test-runner`;
|
||||||
|
- `git diff --check`。
|
||||||
|
|
||||||
|
构建日志遵循项目全局 build-output-throttle。
|
||||||
|
|
||||||
|
如果全量 Ninja 再受单次终端时间限制,不得伪造 PASS;但应先确保受影响精确对象与过滤测试通过,并在报告明确缺口。不得要求用户现在编 Android。
|
||||||
|
|
||||||
|
## 12. 明确禁止
|
||||||
|
|
||||||
|
本任务不得:
|
||||||
|
|
||||||
|
- 修改 Android Java/Kotlin/JNI bridge;
|
||||||
|
- 修改 RN package API/version;
|
||||||
|
- 实现 v2 multi-plane file ingress;
|
||||||
|
- 修改 WeatherV2/NavSea;
|
||||||
|
- 解析 NSFT/PMTiles;
|
||||||
|
- 添加 Wind/Wave/Rain/Tide enum 或业务分支;
|
||||||
|
- 使用 scalar height/period 控制 DIRECTION 速度;
|
||||||
|
- 增加第二 scalar/N-channel;
|
||||||
|
- 重写 Timeline/Playback/Runtime Window;
|
||||||
|
- JSI/zero-copy 重构;
|
||||||
|
- 执行 Android Gradle/AAR/APK/ADB;
|
||||||
|
- 加入或执行 Jest。
|
||||||
|
|
||||||
|
## 13. 文档与状态
|
||||||
|
|
||||||
|
新增/更新:
|
||||||
|
|
||||||
|
- 本任务书 `docs/40_任务/STEP-068_MapLibreParticleLab_V3-04_DIRECTION粒子推进_Codex任务_v1.md`
|
||||||
|
- `docs/50_Step记录/STEP-068_MapLibreParticleLab_V3-04_DIRECTION粒子推进_v1.md`
|
||||||
|
- `docs/报告/STEP-068_MapLibreParticleLab_V3-04_DIRECTION粒子推进_Codex完成报告_v1.md`
|
||||||
|
- 追加 `粒子开发记录.md`
|
||||||
|
|
||||||
|
全部文档中文。完成报告逐项写“已完成 / 未完成 / 无法验证”,明确生产代码范围、验证结果、Android/Jest 未执行、风险和下一步。
|
||||||
|
|
||||||
|
若代码与 Linux 验证满足本任务门槛,可标记 `CLOSED / PASS`;若只有实现而关键精确编译/过滤测试未通过,则只能 `IMPLEMENTED / WAITING_VALIDATION`。
|
||||||
|
|
||||||
|
## 14. Git 收尾
|
||||||
|
|
||||||
|
本任务有生产代码修改,完成后必须 scoped commit + push 到 `origin/particle/main`。
|
||||||
|
|
||||||
|
提交前再次 `git status --short`。只暂存本任务实际修改文件与上述 STEP-068 文档;禁止纳入 STEP-062 两处用户修改和所有历史 untracked/build/cache/tgz。
|
||||||
|
|
||||||
|
允许按“实现代码”与“文档收尾”做 1-2 个 scoped commit。禁止 `git add .`、`git add -A`。
|
||||||
|
|
||||||
|
push 后必须确认:
|
||||||
|
|
||||||
|
- commit hash/message;
|
||||||
|
- push 成功;
|
||||||
|
- `HEAD == origin/particle/main`;
|
||||||
|
- 最终工作区剩余 dirty 仍只属于原有用户/历史内容。
|
||||||
|
|
||||||
|
push 失败则任务不得声称完全完成。
|
||||||
|
|
||||||
|
## 15. 下一阶段边界
|
||||||
|
|
||||||
|
本任务完成后下一阶段应为 V3-05:Android/JNI/RN generic v2 multi-plane ingress。
|
||||||
|
|
||||||
|
V3-05 之前不要求 Android build。最终仍按 V3 总规划,将 Android 编译/安装/设备综合验收集中到后续大阶段,避免每个小 Step 重复一次 Android 周期。
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
# STEP-069 MapLibreParticleLab V3-05 Android/JNI/RN v2 多 Plane 入口 — Codex 任务 v1
|
||||||
|
|
||||||
|
## 1. 任务目标
|
||||||
|
|
||||||
|
基于当前 `particle/main` HEAD `9e9ea07bc74633714836aa4fff4e963109f4e3b9`,完成 `maplibre-particle-field/v2` 从 RN → Android Kotlin → Java Host facade → JNI → `RasterArraySource` → 现有 Core V3 Snapshot/Timeline 的明确多 plane 二进制入口。
|
||||||
|
|
||||||
|
本任务只做通用 Scalar/Vector Field runtime 的 Host ingress 与已经冻结的 renderer 配置桥,不引入 Wind/Wave/Rain/Tide provider/business 逻辑,不解析 NSFT/PMTiles,不改 Timeline/T0/T1/MIX/rollover/lifecycle 算法。
|
||||||
|
|
||||||
|
开始前必须完整阅读:`AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、STEP-065/066/067/068 的 Step/报告,以及当前实际源码。不得按记忆重建。
|
||||||
|
|
||||||
|
## 2. 当前已确认基线
|
||||||
|
|
||||||
|
- RN package:`packages/maplibre-particle-rn` 当前版本 `13.4.1-particle.2`。
|
||||||
|
- `.2` 公共 `FieldRuntime.submitField(reactTag, metadataJson, payloadFileUri)` 内部仍映射 `submitWindField` 单文件 ABI。
|
||||||
|
- Kotlin `WeatherRuntimeBridgeModule` 当前只读取一个本地 payload,单 payload 上限 64 MiB。
|
||||||
|
- Java `WeatherRuntime.submitWindField(metadataJson, byte[])` → `MapLibreMap.submitRasterArraySourceHostField(...)`。
|
||||||
|
- JNI `NativeMapView::submitRasterArraySourceHostField(...)` 当前只转一个 `byte[]` 为 `std::vector<char>`。
|
||||||
|
- `RasterArraySource::submitHostField(metadata, vector<char>)` 是 v1 legacy ingress。
|
||||||
|
- Core 已有 `ParticleFieldPayloadBundle { scalar?, vector?, validity? }` 与 `LocalParticleFieldFeed::load(metadata, bundle)`,但 `RasterArraySource` 尚无 v2 bundle overload。
|
||||||
|
- STEP-067 已有 `WeatherColorLayer::FieldValueSource { Scalar, VectorMagnitude }`,尚无 Android/RN setter。
|
||||||
|
- STEP-068 已有 `raster-particle-advection-scale`,尚无 Android/RN setter。
|
||||||
|
|
||||||
|
## 3. v2 RN 公共 API
|
||||||
|
|
||||||
|
将 package 版本递增到 **`13.4.1-particle.3`**,不得覆盖或继续宣称 `.2`。
|
||||||
|
|
||||||
|
新增并导出 provider-neutral 类型:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type FieldPayloadRefs = Readonly<
|
||||||
|
{ validityFileUri: string } & (
|
||||||
|
| { scalarFileUri: string; vectorFileUri?: string }
|
||||||
|
| { scalarFileUri?: string; vectorFileUri: string }
|
||||||
|
)
|
||||||
|
>;
|
||||||
|
|
||||||
|
type FieldValueSource = "SCALAR" | "VECTOR_MAGNITUDE";
|
||||||
|
```
|
||||||
|
|
||||||
|
`.3` 的主入口冻结为:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
FieldRuntime.submitField(reactTag, metadataJson, payloadRefs)
|
||||||
|
FieldRuntime.clearFields(reactTag)
|
||||||
|
FieldRuntime.setFieldValueSource(reactTag, source)
|
||||||
|
FieldRuntime.setParticleAdvectionScale(reactTag, scale)
|
||||||
|
```
|
||||||
|
|
||||||
|
`submitField` 不得再接受 JS number[]、Base64 或巨大 JSON payload。
|
||||||
|
|
||||||
|
为了保留 v1 thin compatibility ingress,可保留现有 native/internal `submitWindField(metadata, singlePayload)`,并在 `.3` 公共层提供命名明确的 legacy 入口(例如 `submitLegacyVectorField`);不得让 v1/v2 共用一个模糊单 payload 形状。README/CHANGELOG 必须明确 `.2` 与 `.3` 差异。
|
||||||
|
|
||||||
|
## 4. payloadRefs 文件规则
|
||||||
|
|
||||||
|
v2 必须使用三个语义明确的 local file reference:
|
||||||
|
|
||||||
|
- `validityFileUri`:必填;
|
||||||
|
- `scalarFileUri`:可选;
|
||||||
|
- `vectorFileUri`:可选;
|
||||||
|
- scalar/vector 至少一个存在,否则在 Kotlin bridge fail-closed。
|
||||||
|
|
||||||
|
只允许 `file://` 或绝对本地路径,沿用现有 local-file 安全规则。
|
||||||
|
|
||||||
|
在实际读文件前先完成所有路径、可读性、文件长度检查,再读取;避免读到一半才发现 aggregate 超限。
|
||||||
|
|
||||||
|
冻结本桥接层 v3 初版内存上限:
|
||||||
|
|
||||||
|
- metadata UTF-8 ≤ 1 MiB(沿用现状);
|
||||||
|
- 每个存在的 plane 必须非空;
|
||||||
|
- **三个 plane 文件总字节数 ≤ 64 MiB**;
|
||||||
|
- 单文件自然也不得超过 64 MiB;
|
||||||
|
- 总长度计算必须防溢出;
|
||||||
|
- Core 仍负责按 metadata 声明验证每 plane exact byteLength/encoding/layout/validity。
|
||||||
|
|
||||||
|
不得把缺失 scalar/vector 伪造为零数据文件。validity 必须显式存在。
|
||||||
|
|
||||||
|
## 5. Android/Kotlin bridge
|
||||||
|
|
||||||
|
在 `WeatherRuntimeBridgeModule` 中新增 generic v2 submit 方法,命名可以继续受现有 TurboModule/codegen seam 限制,但语义必须明确为 Field v2,不能叫新的 Wind-only API。
|
||||||
|
|
||||||
|
推荐流程:
|
||||||
|
|
||||||
|
1. 校验 metadata;
|
||||||
|
2. 校验 refs presence;
|
||||||
|
3. resolve 三个 local file;
|
||||||
|
4. stat 全部存在 plane,计算 aggregate;
|
||||||
|
5. aggregate ≤ 64 MiB 后才逐个 bounded read;
|
||||||
|
6. 调 Java `WeatherRuntime.submitField(...)`;
|
||||||
|
7. Promise 返回现有 `FieldSubmitResult` name。
|
||||||
|
|
||||||
|
不得保存 reactTag/runtime/timeline 第二份状态,不改变现有 per-view lookup 与生命周期。
|
||||||
|
|
||||||
|
legacy `submitWindField` 可以保留作为 thin v1 compatibility,不得删除导致旧 `.2` native seam 无法理解。
|
||||||
|
|
||||||
|
## 6. Java Host facade
|
||||||
|
|
||||||
|
`WeatherRuntime` 新增 provider-neutral:
|
||||||
|
|
||||||
|
- `submitField(metadataJson, scalarBytes?, vectorBytes?, validityBytes)`;
|
||||||
|
- `clearFields()`(旧 `clearWindFields()` 可作为 deprecated/thin alias);
|
||||||
|
- `setFieldValueSource(...)`;
|
||||||
|
- `setParticleAdvectionScale(float)`。
|
||||||
|
|
||||||
|
不得在 Java 根据 `fieldId`、Wind/Wave/Rain/Tide 名称猜 renderer 行为。
|
||||||
|
|
||||||
|
`setFieldValueSource` 只接 `SCALAR` / `VECTOR_MAGNITUDE`;`setParticleAdvectionScale` 必须 finite 且 >0。二者只控制已有 renderer config,不改 Source timeline。
|
||||||
|
|
||||||
|
WeatherColor 内部历史 `speedMps` 命名若本任务不影响功能,可保留为兼容技术债,禁止借机大规模重命名。
|
||||||
|
|
||||||
|
## 7. NativeMap / JNI
|
||||||
|
|
||||||
|
新增明确的 v2 JNI 方法,不得重载旧单 payload 产生 ABI 歧义。
|
||||||
|
|
||||||
|
优先使用三 plane 参数:`scalar byte[]?`、`vector byte[]?`、`validity byte[]`。必须先检查当前 jni.hpp 对 nullable primitive array 的实际支持;若 nullable array 在当前 binding 容易产生注册/调用风险,可以改用**明确 v2 container 参数结构**,但必须:
|
||||||
|
|
||||||
|
- 不与 v1 单 payload 混淆;
|
||||||
|
- 明确记录 offsets/lengths;
|
||||||
|
- 不在 JS 层构造巨大 container;
|
||||||
|
- Kotlin/Java → JNI 后最终恢复成 `ParticleFieldPayloadBundle`。
|
||||||
|
|
||||||
|
不允许因为参数处理困难而用“空 scalar/vector 字节数组代表缺失”去绕过语义,除非在 ABI 层有明确 presence flags 且 Core bundle 恢复为 `std::nullopt`。
|
||||||
|
|
||||||
|
JNI 必须最终调用新的 `RasterArraySource::submitHostField(metadata, ParticleFieldPayloadBundle)`。
|
||||||
|
|
||||||
|
## 8. RasterArraySource 最薄 v2 overload
|
||||||
|
|
||||||
|
新增:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
submitHostField(const std::string&, const particle::ParticleFieldPayloadBundle&)
|
||||||
|
```
|
||||||
|
|
||||||
|
或等价明确 overload。
|
||||||
|
|
||||||
|
它必须调用现有 `LocalParticleFieldFeed::load(metadata, bundle)`;v1 overload 继续调用 legacy load/normalize。
|
||||||
|
|
||||||
|
两条 ingress decode 后必须进入**同一份** duplicate / monotonic / compatibility / pending initial T0 / initial window / next snapshot 提交流程。应提取最小公共 helper 避免复制 Timeline 状态机,但不得重写 Timeline。
|
||||||
|
|
||||||
|
日志改为 provider-neutral plane presence/bytes,不得假称所有 payload 为 `interleaved-uv`。legacy v1 日志可明确标注 legacy/vector-only。
|
||||||
|
|
||||||
|
## 9. FieldValueSource Android/JNI bridge
|
||||||
|
|
||||||
|
Core 已有 `WeatherColorLayer::setFieldValueSource()`,本任务只加最薄 Android/JNI Host setter,例如以稳定 int/enum code 表达:
|
||||||
|
|
||||||
|
- `0 = SCALAR`
|
||||||
|
- `1 = VECTOR_MAGNITUDE`
|
||||||
|
|
||||||
|
非法值 fail-closed。
|
||||||
|
|
||||||
|
不得新增天气业务 enum。该 setter 不重传 Field、不改变 Timeline。
|
||||||
|
|
||||||
|
## 10. advectionScale Android/RN bridge
|
||||||
|
|
||||||
|
利用 STEP-068 已有 `raster-particle-advection-scale` style property,Java Host 可通过现有 `setStyleLayerFloatProperty` 设置;如无需新 JNI 就不要新增 JNI。
|
||||||
|
|
||||||
|
RN 公开 `setParticleAdvectionScale()`;必须 finite、>0,最终只修改 renderer property,不重传 field、不清 Timeline/Trail/State。
|
||||||
|
|
||||||
|
## 11. 必须验证的行为
|
||||||
|
|
||||||
|
至少覆盖:
|
||||||
|
|
||||||
|
1. v2 scalar-only bundle 可 decode/submit;
|
||||||
|
2. v2 vector-only VELOCITY 可 decode/submit;
|
||||||
|
3. v2 scalar+vector DIRECTION 可 decode/submit;
|
||||||
|
4. 缺 validity 拒绝;
|
||||||
|
5. scalar/vector 都缺拒绝;
|
||||||
|
6. metadata 声明与 plane presence/长度不一致由 Core 拒绝;
|
||||||
|
7. v1 legacy vector-only ingress 仍通过 thin normalize;
|
||||||
|
8. v1 与 compatible v2 vector-only 可进入同一个 Runtime/Timeline 契约;
|
||||||
|
9. T0/T1 channel presence 或 vector semantics 不同继续拒绝;
|
||||||
|
10. aggregate local file size >64 MiB 在读入前拒绝;
|
||||||
|
11. 非 file URI / 相对路径拒绝;
|
||||||
|
12. FieldValueSource 非法值拒绝;
|
||||||
|
13. advectionScale <=0 / NaN / Inf 拒绝;
|
||||||
|
14. `.3` TypeScript public type 不允许 payloadRefs 同时缺 scalar/vector;
|
||||||
|
15. legacy API 明确命名,不与 v2 `submitField` 形状混淆。
|
||||||
|
|
||||||
|
## 12. 测试与静态验证
|
||||||
|
|
||||||
|
本任务**不执行 Android Gradle、AAR、APK、ADB,也不执行 Jest**。
|
||||||
|
|
||||||
|
允许并要求:
|
||||||
|
|
||||||
|
- Core/RasterArraySource 相关 C++ 精确对象编译;
|
||||||
|
- 相关 C++ 过滤测试,补最小测试覆盖 v2 Source submit 与 legacy regression;
|
||||||
|
- `ninja -C build-linux-opengl -j3 mbgl-test mbgl-test-runner` 仅当变更触发 Core 并且已有 build 可用;
|
||||||
|
- `git diff --check`;
|
||||||
|
- RN package TypeScript `tsc --noEmit` / 项目现有等价静态检查;
|
||||||
|
- 检查 TurboModule spec 与 Kotlin override 参数一致性;
|
||||||
|
- 可运行无需 Gradle 的 codegen/文本生成检查,但不得为了验证启动 Android build。
|
||||||
|
|
||||||
|
所有 noisy build 遵守 `$build-output-throttle`。
|
||||||
|
|
||||||
|
由于 Android 真正 Java/Kotlin/JNI 编译由用户后续 V3-08 手工统一执行,本 Step 不能声称 Android compile PASS。若静态审计仍存在签名不确定性,报告必须写明。
|
||||||
|
|
||||||
|
## 13. 明确禁止
|
||||||
|
|
||||||
|
不得:
|
||||||
|
|
||||||
|
- Android Gradle/AAR/APK/ADB;
|
||||||
|
- Jest;
|
||||||
|
- 修改 WeatherV2/NavSea;
|
||||||
|
- 解析 NSFT、PMTiles 或 provider manifest;
|
||||||
|
- 加 Wind/Wave/Rain/Tide enum/if 分支;
|
||||||
|
- 从 Wave height/period 推断粒子速度;
|
||||||
|
- 引入第二 scalar 或 N-channel;
|
||||||
|
- 改 Timeline/T0/T1/MIX/rollover/playback 算法;
|
||||||
|
- JSI/zero-copy 大重构;
|
||||||
|
- 删除 `.2` 已冻结 legacy native ABI;
|
||||||
|
- 修改 STEP-062 两处用户 dirty 文档;
|
||||||
|
- 清理历史 untracked/build/cache/tgz。
|
||||||
|
|
||||||
|
## 14. 必做文档
|
||||||
|
|
||||||
|
新增/更新:
|
||||||
|
|
||||||
|
- 本任务书 `docs/40_任务/STEP-069_MapLibreParticleLab_V3-05_Android_JNI_RN_v2多Plane入口_Codex任务_v1.md`
|
||||||
|
- `docs/50_Step记录/STEP-069_MapLibreParticleLab_V3-05_Android_JNI_RN_v2多Plane入口_v1.md`
|
||||||
|
- `docs/报告/STEP-069_MapLibreParticleLab_V3-05_Android_JNI_RN_v2多Plane入口_Codex完成报告_v1.md`
|
||||||
|
- `packages/maplibre-particle-rn/README.md`
|
||||||
|
- `packages/maplibre-particle-rn/CHANGELOG.md`
|
||||||
|
- 追加 `粒子开发记录.md`
|
||||||
|
|
||||||
|
全部文档中文说明为主,代码/API/命令保留英文。
|
||||||
|
|
||||||
|
任务完成后逐项报告“已完成 / 未完成 / 无法验证”,并明确 Android 编译未执行。
|
||||||
|
|
||||||
|
## 15. 状态判定
|
||||||
|
|
||||||
|
若 Core/Linux/TS 静态验证与 contract 检查满足要求,可标记:
|
||||||
|
|
||||||
|
`IMPLEMENTED / WAITING_ANDROID_VALIDATION`
|
||||||
|
|
||||||
|
**不得在本 Step 标记 Android CLOSED/PASS**,因为按照项目规则 Android 编译/实体机验收统一留到 V3-08。
|
||||||
|
|
||||||
|
该状态允许进入 V3-06 fixtures,只要不存在已知 ABI/类型阻塞。
|
||||||
|
|
||||||
|
## 16. Git 收尾
|
||||||
|
|
||||||
|
本任务修改代码,必须 scoped commit + push `origin/particle/main`。
|
||||||
|
|
||||||
|
提交前检查 `git status --short`,只暂存 STEP-069 实际文件。严禁 `git add .` / `git add -A`。
|
||||||
|
|
||||||
|
STEP-062 两处 tracked dirty、所有历史 untracked、`.codex-build-logs/`、旧 tgz/build 目录一律不得提交。
|
||||||
|
|
||||||
|
允许 1 个实现 commit + 1 个文档 handoff commit。
|
||||||
|
|
||||||
|
完成报告必须记录:
|
||||||
|
|
||||||
|
- implementation commit hash/message;
|
||||||
|
- handoff commit hash/message(若有);
|
||||||
|
- push result;
|
||||||
|
- remote branch;
|
||||||
|
- Final HEAD;
|
||||||
|
- `HEAD == origin/particle/main`;
|
||||||
|
- 未纳入的既有 dirty 内容摘要。
|
||||||
|
|
||||||
|
push 失败不得声称任务完成。
|
||||||
|
|
||||||
|
## 17. 执行回填(2026-09-11)
|
||||||
|
|
||||||
|
- 已按本任务实现 `.3` v2 refs、Kotlin/Java/JNI/Core bundle ingress、`FieldValueSource` 与 `advectionScale` 桥接;v1 ABI 保留为明确 legacy ingress。
|
||||||
|
- 未执行 Android Gradle、AAR、APK、ADB 或 Jest;Android 编译留给 V3-08。
|
||||||
|
- 结果状态:`IMPLEMENTED / WAITING_ANDROID_VALIDATION`。最终 commit/push 与允许验证结果见本 Step 记录和完成报告。
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
# STEP-070 MapLibreParticleLab V3-06 四类场 Fixture 与 RN 通用回归 — Codex 任务 v1
|
||||||
|
|
||||||
|
## 1. 目标
|
||||||
|
基于 `particle/main` 当前 HEAD `3b012759838f1e1ecc44559faffe061d396b6cce`,完成 V3 通用 Scalar/Vector Field 的离线业务语义映射与回归资产:Wind、Rain、Wave、Tide/Current 四类场,外加固定 synthetic Wave independence fixture。
|
||||||
|
|
||||||
|
本 Step 的重点不是增加新的 Runtime 架构,而是证明 STEP-065~069 冻结的通用契约能够由真实/可追溯数据映射并通过 `.3` RN 公共 API 驱动。Android 编译/安装/ADB/设备视觉验证继续留到后续统一周期,不在本任务执行。
|
||||||
|
|
||||||
|
开始前完整阅读 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、STEP-065~069 Step/报告,以及当前实际源码。不得按记忆重建。
|
||||||
|
|
||||||
|
## 2. 必须保持的架构边界
|
||||||
|
- Core/Renderer 不得出现 Wind/Wave/Rain/Tide/provider/network/vendor 业务分支。
|
||||||
|
- Host/RN 负责业务字段到 canonical scalar/vector/validity 的映射。
|
||||||
|
- canonical schema 继续使用 `maplibre-particle-field/v2`。
|
||||||
|
- 一个 Snapshot:0/1 scalar + 0/1 vector + shared validity;neither 非法。
|
||||||
|
- Wind / Tide(Current):vector `VELOCITY`,Field=`VECTOR_MAGNITUDE`,Particle 使用 VELOCITY。
|
||||||
|
- Rain:scalar rain rate,Field=`SCALAR`,不启用 Particle。
|
||||||
|
- Wave:scalar=wave height;vector=direction-to unit vector;semantics=`DIRECTION`;Field=`SCALAR`;Particle=`DIRECTION`,推进视觉速度只来自 `advectionScale`。
|
||||||
|
- Wave period 不进入第二 scalar,不参与推进,不从 height/period 推导速度。
|
||||||
|
- Rain precipitation_type 暂不进入 canonical V3.0。
|
||||||
|
- T0/T1/MIX、Timeline、Playback、OLD_T1→NEW_T0 reuse 等 Core 逻辑不得修改。
|
||||||
|
|
||||||
|
## 3. 当前基线事实
|
||||||
|
- RN package 已是 `@navsea/maplibre-particle-rn@13.4.1-particle.3`。
|
||||||
|
- 正式 v2 API:`FieldRuntime.submitField(reactTag, metadataJson, payloadRefs)`。
|
||||||
|
- `payloadRefs` 为必填 validity + optional scalar/vector local file URI,scalar/vector 至少一个。
|
||||||
|
- `.3` 已提供 `setFieldValueSource()` 与 `setParticleAdvectionScale()`。
|
||||||
|
- Android/JNI/Core multi-plane ingress 已实现,但 Android 编译留待 V3-08。
|
||||||
|
- 现有 RN Test Host 的 REAL WEATHER 仍直接调用旧 `WeatherRuntimeBridge.submitWindField()`;本 Step 必须迁移验证入口到 package `.3` 的 `FieldRuntime`,不能继续用旧 bridge 证明新 API。
|
||||||
|
- 仓库已有真实 WeatherV2 Wind canonical 资产:`platform/android/MapLibreAndroidTestApp/src/main/assets/integration/particle-weatherv2/ecmwf-hres-wind-20260807/`。
|
||||||
|
- WeatherV2 源树只读位于 `/root/sourceserver/weatherV2`;其 NSFT v1 语义以 `src/dev/core/nsft.py` 和当前生产 pipeline 为准。不要修改 WeatherV2。
|
||||||
|
|
||||||
|
## 4. 数据来源与可追溯性
|
||||||
|
先调查 `/root/sourceserver/weatherV2` 当前实际输出,优先从已存在的 NSFT v1 `field_pmtiles`/`/fields` 等本地数值产物生成 fixture,不联网、不重新跑完整生产 pipeline。
|
||||||
|
|
||||||
|
至少为以下四类场确认一个可用 source/refTime/validTime/区域:
|
||||||
|
- Wind
|
||||||
|
- Rain
|
||||||
|
- Wave
|
||||||
|
- Tide/Current
|
||||||
|
|
||||||
|
优先选日本及周边、相邻两个实际 validTime,便于 T0/T1/MIX。若某层当前本地没有可用正式 NSFT 数值产物,不得伪称“真实 WeatherV2”;应在报告明确说明并使用最小可追溯 synthetic/decoder-derived fallback,同时保留该层的业务语义。Wind 已有真实 canonical 基线可直接复用/升级。
|
||||||
|
|
||||||
|
不得把旧 QBIN 当作正式未来 transport。若为数据核验读取旧 BIN,只能作为来源调查/对照,最终 fixture 必须输出 v2 canonical planes。
|
||||||
|
|
||||||
|
## 5. 新建通用 fixture/converter 工具
|
||||||
|
在 `tools/` 下新增 provider-neutral 或 WeatherV2-to-canonical 的离线工具,具体目录名由实际实现确定,但不得只为 Wind 特化。
|
||||||
|
|
||||||
|
工具至少应支持把已解码/NSFT 数值场规范化为:
|
||||||
|
- `metadata.json`
|
||||||
|
- `validity.u8`
|
||||||
|
- optional `scalar.f32`
|
||||||
|
- optional `vector.f32`(interleaved U/V)
|
||||||
|
- `conversion_summary.json`
|
||||||
|
|
||||||
|
约束:
|
||||||
|
- float32-le;validity uint8;NW-origin row-major;rows north→south;cols west→east。
|
||||||
|
- U eastward,V northward。
|
||||||
|
- 每个输出文件记录 byteLength、SHA-256、grid、bounds、validTime、generation、source provenance。
|
||||||
|
- vector DIRECTION 必须在 valid cell 上接近 unit length;允许原始量化误差,转换后可规范化,但近零/非 finite 必须 invalid/fail-closed,不得任造方向。
|
||||||
|
- shared validity 必须由源有效性与所有存在 channel 的有限性共同约束;invalid cell 的具体 payload 值可为 0,但 validity=0 才是权威。
|
||||||
|
- metadata 中不允许 Wind/Wave/Rain/Tide capability enum;`fieldId` 可包含可读 fixture 名称但 Core 不依赖它。
|
||||||
|
|
||||||
|
## 6. 四类 fixture 目录
|
||||||
|
新增仓库内固定测试资产,建议:
|
||||||
|
`integration/particle-v3-fields/`
|
||||||
|
|
||||||
|
至少:
|
||||||
|
- `wind-real/`:两个相邻 validTime,vector-only VELOCITY。
|
||||||
|
- `rain-real/`:两个相邻 validTime,scalar-only。
|
||||||
|
- `wave-real/`:两个相邻 validTime,scalar+vector DIRECTION。
|
||||||
|
- `tide-real/`:两个相邻 validTime,vector-only VELOCITY。
|
||||||
|
- `wave-synthetic-independence/`:两个兼容时次或至少足以验证 T0/T1 的 fixture。
|
||||||
|
|
||||||
|
若某 `*-real` 因本机数据缺失只能 fallback,目录名不得误用 real;改用 `*-decoder-fixture` 或等价明确命名,并在 dataset manifest/报告写明来源。
|
||||||
|
|
||||||
|
每组 fixture 提供 `dataset.json`,列出 frame 顺序、metadata/plane 路径、expected FieldValueSource、particle expected on/off、vector semantics、建议 advectionScale(Wave synthetic 可指定非 1 值用于验证 scale 独立性)。
|
||||||
|
|
||||||
|
## 7. Synthetic Wave Independence 固定语义
|
||||||
|
必须实现并静态验证:
|
||||||
|
- scalar(wave height)西侧低、东侧高,形成明显 E→W 或 W→E 的横向数值梯度;
|
||||||
|
- vector 在所有 valid cell 固定向北 `(u=0, v=1)`;
|
||||||
|
- `vectorSemantics=DIRECTION`;
|
||||||
|
- Field source=`SCALAR`;
|
||||||
|
- Particle 方向应只由 vector 决定为北向;scalar 改变不得改变粒子方向/推进 magnitude;
|
||||||
|
- 改 `advectionScale` 只改变 DIRECTION visual movement magnitude,不改变 field scalar。
|
||||||
|
|
||||||
|
增加无 Android 的数据 oracle/脚本验证该 fixture:scalar 的东西梯度与 vector 的北向独立,不允许只靠人工阅读 JSON。
|
||||||
|
|
||||||
|
## 8. RN Test Host 升级到 `.3` 正式 API
|
||||||
|
只修改 Test Host 的 JS/TS 与离线 assets/config,不执行 Gradle。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
- REAL WEATHER/V3 验证入口从内部 `WeatherRuntimeBridge.submitWindField` 迁移到 `@navsea/maplibre-particle-rn` 的 `FieldRuntime`。
|
||||||
|
- 不从 package 内部路径 import bridge。
|
||||||
|
- 通过 app cache/local file URI 提交 metadata + plane refs;不得 JS number[] / base64 / giant JSON。
|
||||||
|
- 提供四类场选择/加载能力,至少能在 UI/fixture runner 中选择 Wind/Rain/Wave/Tide 和 synthetic Wave。
|
||||||
|
- 每次切换 dataset 前 `clearFields()`,设置正确的 `setFieldValueSource()`、Particle visibility、`setParticleAdvectionScale()`,再顺序提交 T0/T1。
|
||||||
|
- 对每个 dataset 明确断言:T0=`ACCEPTED_PENDING_INITIAL_WINDOW`,T1=`INITIAL_WINDOW_READY`,runtime state=`READY`。
|
||||||
|
- Rain 必须 Particle OFF;Wind/Tide Particle ON;Wave Particle ON 且 DIRECTION 数据不被改成 VELOCITY。
|
||||||
|
- Field/Particle visibility 仍独立。
|
||||||
|
- 不在 JS 保存 T0/T1/MIX 第二份 Runtime 状态;JS 只保存 fixture/选择 UI 与调用结果。
|
||||||
|
- 现有 GSI/base map 与已验证 UI 可保留,不做无关重构。
|
||||||
|
|
||||||
|
## 9. Core/Linux 回归
|
||||||
|
新增最小 C++/数据 contract 测试,重点不是复制完整 Android bridge,而是验证:
|
||||||
|
- 四类 canonical shape 能被 `LocalParticleFieldFeed` / `RasterArraySource` 接受。
|
||||||
|
- Wind/Tide vector-only VELOCITY compatible T0/T1。
|
||||||
|
- Rain scalar-only compatible T0/T1。
|
||||||
|
- Wave scalar+vector DIRECTION compatible T0/T1。
|
||||||
|
- channel presence/semantics mismatch 继续 fail-closed。
|
||||||
|
- synthetic Wave 的 scalar/vector independence oracle。
|
||||||
|
- normal T0→T1 / next snapshot 不修改 Runtime 算法。
|
||||||
|
|
||||||
|
如果现有 test 已覆盖底层形状,不重复堆测试;新增 fixture-specific contract test 即可。
|
||||||
|
|
||||||
|
## 10. 本 Step 不做
|
||||||
|
- 不执行 Android Gradle/AAR/APK/ADB/设备测试。
|
||||||
|
- 不执行 Jest。
|
||||||
|
- 不修改 NavSea。
|
||||||
|
- 不修改 WeatherV2 源项目。
|
||||||
|
- 不联网下载天气数据。
|
||||||
|
- 不引入 provider/network/cache/manifest runtime 到 C++ Core。
|
||||||
|
- 不改 v2 schema、Timeline/Playback、shader 数学、DIRECTION 算法。
|
||||||
|
- 不发布 npm/tgz/tag;`.3` 仅保持源码版本。
|
||||||
|
- 不修改 STEP-062 两处现有用户 dirty 文档,不清理历史 untracked/build/cache。
|
||||||
|
|
||||||
|
## 11. 必须验证
|
||||||
|
至少执行并记录:
|
||||||
|
1. fixture converter/validator 自检 PASS,含 plane byteLength/SHA/grid/bounds/time/finite/validity。
|
||||||
|
2. synthetic Wave independence oracle PASS。
|
||||||
|
3. package `.3` TypeScript `tsc --noEmit` PASS。
|
||||||
|
4. RN Test Host TypeScript/typecheck PASS(使用项目现有命令,不跑 Jest)。
|
||||||
|
5. RN TurboModule codegen schema 检查 PASS,确保 STEP-069 ABI 未被破坏。
|
||||||
|
6. 受影响 Core 精确对象编译 PASS。
|
||||||
|
7. 相关 C++ fixture/Source 过滤测试 PASS。
|
||||||
|
8. `git diff --check` PASS。
|
||||||
|
|
||||||
|
所有 noisy build 使用 `$build-output-throttle`。若完整 `mbgl-test/runner` 已经最新可直接使用;若因改 C++ test 触发重链,允许 Linux 重链。
|
||||||
|
|
||||||
|
## 12. 文档输出
|
||||||
|
必须生成/更新:
|
||||||
|
- 本任务书。
|
||||||
|
- `docs/50_Step记录/STEP-070_MapLibreParticleLab_V3-06_四类场Fixture与RN通用回归_v1.md`
|
||||||
|
- `docs/报告/STEP-070_MapLibreParticleLab_V3-06_四类场Fixture与RN通用回归_Codex完成报告_v1.md`
|
||||||
|
- 追加 `粒子开发记录.md`
|
||||||
|
- 新工具 README / fixture dataset manifest 必须中文说明为主。
|
||||||
|
|
||||||
|
状态上限:`IMPLEMENTED / WAITING_ANDROID_VALIDATION`。不得标记 Android CLOSED/PASS。
|
||||||
|
|
||||||
|
## 13. Git 收尾
|
||||||
|
所有本任务实际修改必须 scoped commit 并 push `origin/particle/main`。
|
||||||
|
- 开始/结束都核对 branch、HEAD、origin。
|
||||||
|
- 严禁 `git add .` / `git add -A`。
|
||||||
|
- 不提交 STEP-062 两处用户 dirty、历史任务 MD、build/cache/tgz。
|
||||||
|
- 可使用 1 个实现 commit + 1 个文档 handoff commit;必要时再用一个纯 Git 元数据收尾 commit。
|
||||||
|
- 完成报告记录 implementation/handoff commit、push result、remote branch、Final HEAD、`HEAD == origin/particle/main`。
|
||||||
|
- push 失败不得声称完成。
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# STEP-071A V3-07 Linux 综合冻结:测试 Fixture 修正
|
||||||
|
|
||||||
|
## 基线
|
||||||
|
- 仓库:`/mnt/sda1/MapLibreParticleLab`
|
||||||
|
- 分支:`particle/main`
|
||||||
|
- 基线 HEAD:`fcc533dc9518395d0c25794131f53f0fae20d51c`
|
||||||
|
- 只允许修改:`test/style/source.test.cpp`
|
||||||
|
|
||||||
|
## 已确认问题 1:Host duplicate 测试数据错误
|
||||||
|
`HostFieldSubmissionRejectsInvalidWithoutChangingLastValidField` 当前用同一份 `uniform-east` payload 提交 T0/T1。
|
||||||
|
Runtime 先按 GPU content identity 返回 `Duplicate` 是正确行为;测试预期 `InitialWindowReady` 已过时。
|
||||||
|
|
||||||
|
修正要求:
|
||||||
|
- T0 使用 `uniform-east/field.f32`。
|
||||||
|
- T1 使用兼容但内容不同的 `uniform-north/field.f32`。
|
||||||
|
- 后续 invalid/non-monotonic 检查使用不会与当前 lastValid 重复的 payload;尤其 non-monotonic 必须真正到达 non-monotonic 判定,而不是先命中 Duplicate。
|
||||||
|
- 不修改 `sameFieldGpuContent`、Duplicate 顺序或任何 Runtime 生产逻辑。
|
||||||
|
|
||||||
|
## 已确认问题 2:Timeline 测试时间重复
|
||||||
|
`RasterArrayTimelineCandidatesReachReadyThroughFieldAdapter` 使用 east/north/vortex 三份旧 fixture;三份 metadata 的 `timeIndex` 均为 0。
|
||||||
|
当前 `OfflineFixtureParticleAdapter` 保留 `validTime = timeIndex + 1`,因此旧测试会形成重复时间,Timeline 正确 fail-closed,而测试无限等待。
|
||||||
|
修正要求:
|
||||||
|
- 不改共享 fixture 文件。
|
||||||
|
- 仅在该测试内部把 north/vortex metadata 的 `timeIndex` 调整为严格递增值,例如 east=0、north=1、vortex=2。
|
||||||
|
- 保持 grid/bounds/vector semantics/encoding 不变。
|
||||||
|
- 保持测试仍通过真实 `OfflineFixtureParticleAdapter` / Source preload / Timeline 路径验证,不改成绕过适配器的伪测试。
|
||||||
|
- 若需要小型局部 helper,只能放在 `test/style/source.test.cpp`,不得扩散生产 API。
|
||||||
|
|
||||||
|
## 禁止事项
|
||||||
|
- 不修改 `src/`、`include/`、shader、JNI、Android、RN、WeatherV2、NavSea。
|
||||||
|
- 不修改 STEP-062 用户 dirty 文件。
|
||||||
|
- 不执行编译、测试、代码审计、搜索、Android Gradle、ADB、Jest。
|
||||||
|
- 不执行 git add/commit/push。
|
||||||
|
- 不创建额外完成报告;验证与 Git 收尾由 ChatGPT 在 tmux `rdc` 中直接完成。
|
||||||
|
|
||||||
|
## 完成输出
|
||||||
|
修改完成后只报告:
|
||||||
|
1. 修改了 `test/style/source.test.cpp` 哪两个测试;
|
||||||
|
2. 每处如何避免 Duplicate / 重复 validTime;
|
||||||
|
3. 是否修改了任何生产代码(必须为“否”)。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# STEP-071A V3-07 Linux 综合冻结:测试 Fixture 修正 v2
|
||||||
|
|
||||||
|
## 基线与现状
|
||||||
|
- 当前基线:`fcc533dc9518395d0c25794131f53f0fae20d51c` + 未提交的 v1 测试修正。
|
||||||
|
- v1 的 Host duplicate 修正已实测 PASS,不得回退。
|
||||||
|
- 只允许修改:`test/style/source.test.cpp`。
|
||||||
|
|
||||||
|
## 唯一问题
|
||||||
|
`RasterArrayTimelineCandidatesReachReadyThroughFieldAdapter` 中 v1 使用:
|
||||||
|
`northMetadata.find("\"timeIndex\":0")`
|
||||||
|
实际共享 fixture metadata 格式是:
|
||||||
|
`"timeIndex": 0`
|
||||||
|
因此 `find()` 返回 `npos`。
|
||||||
|
|
||||||
|
## 必做修正
|
||||||
|
- 保留 v1 已完成的 east/north distinct payload 修正。
|
||||||
|
- 将 north/vortex 的测试内 `timeIndex` 替换逻辑改为匹配实际 JSON 文本格式。
|
||||||
|
- north 必须得到 `timeIndex = 1`,vortex 必须得到 `timeIndex = 2`。
|
||||||
|
- 不修改共享 fixture 文件。
|
||||||
|
- 不修改任何生产代码或 Runtime 行为。
|
||||||
|
|
||||||
|
## 禁止
|
||||||
|
- 不编译、不测试、不搜索/审计、不运行 Git。
|
||||||
|
- 不修改其他文件。
|
||||||
|
- 不运行 Android/ADB/Jest。
|
||||||
|
|
||||||
|
完成后只报告修改内容和是否修改生产代码。
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# STEP-072A V3-08 RN Test Host 移除 Legacy 双 Bridge
|
||||||
|
|
||||||
|
## 基线
|
||||||
|
- 仓库:`/mnt/sda1/MapLibreParticleLab`
|
||||||
|
- 分支:`particle/main`
|
||||||
|
- 已提交 HEAD:`dd356a277db05ce0075bee43f431ddeb2aba19cc`
|
||||||
|
- 当前工作区已有 STEP-072 未提交修改,必须保留,不得回退或覆盖。
|
||||||
|
- 只处理 RN Test Host legacy bridge 重复 autolink 问题。
|
||||||
|
|
||||||
|
## 已确认事实
|
||||||
|
- App 已只从 `@navsea/maplibre-particle-rn` 使用 `FieldRuntime`。
|
||||||
|
- Expo autolinking 当前同时链接:
|
||||||
|
- `@maplibre-particle/weather-runtime-bridge`
|
||||||
|
- `@navsea/maplibre-particle-rn`
|
||||||
|
- 两者都注册 Native module `WeatherRuntimeBridge`,codegen library 都是 `WeatherRuntimeBridgeSpec`。
|
||||||
|
- Test Host 根 `react-native.config.js` 主动注册 `./modules/weather-runtime-bridge`。
|
||||||
|
- `tsconfig.json` 仍 include `modules/weather-runtime-bridge/src`。
|
||||||
|
- V3-08 必须只保留正式 package `.3` 的 bridge。
|
||||||
|
|
||||||
|
## 必做修改
|
||||||
|
1. 删除整个 tracked legacy 目录:
|
||||||
|
`integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/`
|
||||||
|
- 只删除其中 Git tracked 源码/配置;不要清理、提交或处理其 untracked `android/build/`。
|
||||||
|
2. 删除 Test Host 根:
|
||||||
|
`integration/react-native/MapLibreParticleRNTestHost/react-native.config.js`
|
||||||
|
- 该文件目前唯一用途是注册 legacy bridge;删除后不要新造替代配置。
|
||||||
|
3. 修改:
|
||||||
|
`integration/react-native/MapLibreParticleRNTestHost/tsconfig.json`
|
||||||
|
- include 收敛为 `App.tsx` 与 `src`,移除 legacy module src。
|
||||||
|
4. 更新 Test Host README 当前 V3-08 章节:
|
||||||
|
- 明确 legacy local `@maplibre-particle/weather-runtime-bridge` 已移除。
|
||||||
|
- `@navsea/maplibre-particle-rn@13.4.1-particle.3` 是唯一 `WeatherRuntimeBridge` 提供者。
|
||||||
|
- 历史 STEP-061/062 章节可保留为历史说明,不得改写历史事实。
|
||||||
|
## 禁止事项
|
||||||
|
- 不得运行 Android Gradle、AAR/APK build、publish、install 或 ADB。
|
||||||
|
- 不得运行或加入 Jest。
|
||||||
|
- 不得修改 `packages/maplibre-particle-rn/`、MapLibre Core/JNI/WeatherRuntime、fixture、NavSea 或 release manifest。
|
||||||
|
- 不得删除/清理任何 untracked/generated 文件;尤其不得处理 legacy 目录下现有 `android/build/`。
|
||||||
|
- 不得执行 `git add .`、`git add -A`、commit、push、reset、clean、checkout 或 stash。
|
||||||
|
- 不得修改 STEP-062 dirty 文档和其它历史 dirty/untracked 文件。
|
||||||
|
|
||||||
|
## 完成后必做报告
|
||||||
|
逐项报告“已完成 / 未完成 / 无法验证”,并说明原因:
|
||||||
|
1. tracked legacy bridge 源码/配置是否全部删除。
|
||||||
|
2. 根 `react-native.config.js` 是否删除。
|
||||||
|
3. `tsconfig.json` 是否只包含 `App.tsx` 与 `src`。
|
||||||
|
4. README 是否明确正式 package 为唯一 bridge provider。
|
||||||
|
5. 明确说明未执行 Android/ADB/Jest/Git。
|
||||||
|
|
||||||
|
不要自行做 Android 验证;完成文件修改后停止。
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# STEP-072B V3-08 RN Kotlin ReadableMap 编译修复 Codex 任务 v1
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
V3-08 Win11 正式 npm 消费链已验证到 Android Kotlin 编译阶段。`@navsea/maplibre-particle-rn@13.4.1-particle.3` 的 `WeatherRuntimeBridgeModule.kt` 在 `setWeatherColorStops()` 中使用 `ReadableMap`,但源码 import 区缺少 `com.facebook.react.bridge.ReadableMap`。
|
||||||
|
|
||||||
|
Win11 错误:
|
||||||
|
- `Unresolved reference 'ReadableMap'`
|
||||||
|
- 后续多个 `getDouble` unresolved 均为级联错误。
|
||||||
|
|
||||||
|
## 唯一允许的代码修改
|
||||||
|
仅修改:
|
||||||
|
`packages/maplibre-particle-rn/android/src/main/java/org/navsea/maplibre/particle/weather/WeatherRuntimeBridgeModule.kt`
|
||||||
|
|
||||||
|
在 React Native bridge import 区加入:
|
||||||
|
`import com.facebook.react.bridge.ReadableMap`
|
||||||
|
|
||||||
|
除此之外不得修改任何生产代码、接口、行为或格式。
|
||||||
|
## 禁止项
|
||||||
|
- 不得运行 Android Gradle、APK/AAR build、ADB、Jest。
|
||||||
|
- 不得修改版本号、Maven 坐标、npm package metadata、TurboModule spec、README、CHANGELOG 或 release manifest。
|
||||||
|
- 不得修改 Win11 临时 worktree。
|
||||||
|
- 不得执行 Git add/commit/push/reset/clean。
|
||||||
|
- 不得触碰 STEP-062 dirty 文件和任何无关 untracked/build 目录。
|
||||||
|
|
||||||
|
## Codex 完成要求
|
||||||
|
1. 读取目标 Kotlin 文件并确认缺失 import。
|
||||||
|
2. 只增加 `ReadableMap` import。
|
||||||
|
3. 输出 `git diff -- <目标文件>` 供人工审计。
|
||||||
|
4. 不执行任何编译或测试。
|
||||||
|
|
||||||
|
## 后续由 ChatGPT 执行
|
||||||
|
ChatGPT 将直接完成 Linux 静态检查、重新 npm pack/publish、Win11 dependency/build 验证、文档与 Git 收尾。
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# STEP-072 V3-08 Android Native v2 与单次综合验收准备
|
||||||
|
|
||||||
|
## 基线
|
||||||
|
- 仓库:`/mnt/sda1/MapLibreParticleLab`
|
||||||
|
- 分支:`particle/main`
|
||||||
|
- 基线 HEAD:`dd356a277db05ce0075bee43f431ddeb2aba19cc`
|
||||||
|
- 当前 RN 包:`13.4.1-particle.3`
|
||||||
|
- 当前旧 Native AAR:`13.4.1-particle.1`
|
||||||
|
|
||||||
|
## 已确认事实
|
||||||
|
- Maven Local 仅存在 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1`。
|
||||||
|
- 该 AAR 的 `WeatherRuntime` 只有 v1 `submitWindField(String, byte[])`,不含 STEP-069 的 v2 multi-plane `submitField(...)`。
|
||||||
|
- 当前源码已包含 v2 scalar/vector/shared validity、`clearFields`、`setFieldValueSource`、`setParticleAdvectionScale`。
|
||||||
|
- 因此 V3-08 不得继续消费 Native `.1`,也不得覆盖 `.1`。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
准备新的 Android Native artifact 坐标 `13.4.1-particle.2`,并让 RN `.3` 与 RN Test Host 全部指向该新坐标;只做源码/配置/文档准备,不执行 Android 构建或发布。
|
||||||
|
|
||||||
|
## 必做修改
|
||||||
|
1. `platform/android/VERSION`
|
||||||
|
- 改为 `13.4.1-particle.2`。
|
||||||
|
2. `packages/maplibre-particle-rn/android/build.gradle`
|
||||||
|
- Native dependency 改为 `.2`。
|
||||||
|
3. `packages/maplibre-particle-rn/app.plugin.js`
|
||||||
|
- MLRN dependency substitution 改为 `.2`。
|
||||||
|
4. `integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle`
|
||||||
|
- Native dependency 改为 `.2`。
|
||||||
|
5. `integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js`
|
||||||
|
- dependency substitution 改为 `.2`。
|
||||||
|
6. 不修改或提交生成目录 `integration/react-native/MapLibreParticleRNTestHost/android/`。
|
||||||
|
|
||||||
|
## RN `.3` 文档关系
|
||||||
|
更新 `packages/maplibre-particle-rn/README.md` 与 `CHANGELOG.md`:
|
||||||
|
- RN/TS 为 `13.4.1-particle.3`。
|
||||||
|
- 对应目标 Native artifact 为 `13.4.1-particle.2`。
|
||||||
|
- `.2` Native 是 STEP-069 v2 multi-plane 所需的新 artifact,不能复用/覆盖 `.1`。
|
||||||
|
- 当前只完成坐标准备;Android AAR 尚未由用户手工构建/发布/验收,因此不得写成已发布或 PASS。
|
||||||
|
|
||||||
|
## Release manifest
|
||||||
|
更新 `release/maplibre-particle/release-manifest.json` 为当前 `.3` 开发/验收准备状态:
|
||||||
|
- release:`13.4.1-particle.3`
|
||||||
|
- releaseState:明确为等待 Android 手工构建/发布/验收,不得使用 `released`
|
||||||
|
- reactNativePackage.version:`13.4.1-particle.3`
|
||||||
|
- nativeArtifact.version:`13.4.1-particle.2`
|
||||||
|
- canonicalField.schema:`maplibre-particle-field/v2`
|
||||||
|
- transport 描述为 metadataJson + validity local ref + optional scalar/vector local refs
|
||||||
|
- 不伪造 AAR SHA、发布时间、release tag、Android PASS。
|
||||||
|
- 保留可追溯到 `.2` 已发布 RN 版本的 previousBaseline 信息;不要抹掉历史验证语义。
|
||||||
|
|
||||||
|
## Test Host README
|
||||||
|
更新 `integration/react-native/MapLibreParticleRNTestHost/README.md` 的当前版本/构建说明:
|
||||||
|
- 当前 V3-08 目标为 RN `.3` + Native `.2`。
|
||||||
|
- 用户必须先手工 build/publish `.2` Native AAR,再 prebuild/build Test Host。
|
||||||
|
- 历史 STEP-061/062 可保留为历史章节,但不能让顶部“当前版本”继续写旧 `.1`/step061。
|
||||||
|
- Metro 当前 binary asset extension 为 `.f32` + `.u8`。
|
||||||
|
|
||||||
|
## 新建 Android 单次验收文档
|
||||||
|
新建:`docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md`。
|
||||||
|
只写用户手工执行流程,不执行命令。必须使用 `--console=plain`,ADB 命令固定显式 `adb -s 192.168.1.146 ...`。
|
||||||
|
|
||||||
|
验收文档至少覆盖:
|
||||||
|
- Native `.2` Release OpenGL AAR build + Maven Local publish;核对 AAR SHA 与 Maven Local artifact 一致。
|
||||||
|
- `javap` 核对 `.2` 的 `WeatherRuntime.submitField`、`clearFields`、`setFieldValueSource`、`setParticleAdvectionScale` 存在。
|
||||||
|
- Test Host clean prebuild/build/install/start,一次 Android 周期完成。
|
||||||
|
- Wind real、Rain scalar-only、Wave scalar+DIRECTION、Tide VELOCITY、Wave synthetic independence 五组数据逐组加载。
|
||||||
|
- 每组要求 T0=`ACCEPTED_PENDING_INITIAL_WINDOW`、T1=`INITIAL_WINDOW_READY`、最终 Runtime=`READY`。
|
||||||
|
- PLAY/PAUSE、HALF/NORMAL/DOUBLE、seek T0/T1、AUTO/30/60 FPS、Field/Particle 独立显隐。
|
||||||
|
- Wave synthetic 检查 scalar 色场与 direction particle 独立;Rain 不显示粒子。
|
||||||
|
- logcat 观察 v2 submit / initial window ready / 无 fatal bridge/JNI 错误。
|
||||||
|
|
||||||
|
## 禁止事项
|
||||||
|
- 不修改 Core/Runtime/JNI/renderer 业务实现。
|
||||||
|
- 不执行 Gradle Android build、AAR/APK build、Maven publish、Expo Android build、ADB、安装或真机操作。
|
||||||
|
- 不执行 Jest。
|
||||||
|
- 不修改 STEP-062 用户 dirty 文件或历史 untracked 文件。
|
||||||
|
- 不执行 git add/commit/push。
|
||||||
|
- 不修改生成的 `integration/react-native/MapLibreParticleRNTestHost/android/`。
|
||||||
|
- 不创建或覆盖 Native `.1` artifact。
|
||||||
|
|
||||||
|
## 完成后报告
|
||||||
|
只报告修改文件、Native `.2` 坐标同步情况、验收文档路径,以及“Android build/publish/ADB 未执行”。
|
||||||
170
docs/40_任务/STEP-077_QBIN2_V4_Android_JNI_RN桥接只读调查_Codex任务_v1.md
Normal file
170
docs/40_任务/STEP-077_QBIN2_V4_Android_JNI_RN桥接只读调查_Codex任务_v1.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# STEP-077 — QBIN2 V4 Android / JNI / RN 桥接只读调查 Codex 任务 v1
|
||||||
|
|
||||||
|
状态:完成
|
||||||
|
日期:2026-09-13
|
||||||
|
|
||||||
|
## 1. 任务性质
|
||||||
|
|
||||||
|
本任务是只读架构调查,不实现桥接,不修改 production code。
|
||||||
|
|
||||||
|
当前 `particle/main` / `origin/particle/main` 基线:
|
||||||
|
|
||||||
|
`0759a06104e1fb30f036db2efb6ef34ca5859759`
|
||||||
|
|
||||||
|
STEP-073~076 已完成 V4 Core、Decoder/Ingress、Binding/Projection Identity、`RasterArraySource` C++ Host API。
|
||||||
|
## 2. 调查目标
|
||||||
|
|
||||||
|
回答:V4 的以下能力应如何最小、稳定地穿过现有 Android/JNI/RN 链路:
|
||||||
|
|
||||||
|
```text
|
||||||
|
submitEncodedField
|
||||||
|
setColorBinding
|
||||||
|
setParticleBinding
|
||||||
|
setRequestedForecastTime
|
||||||
|
setForecastPlaybackState
|
||||||
|
setForecastPlaybackSpeed
|
||||||
|
clearEncodedField / clearAllEncodedFields
|
||||||
|
```
|
||||||
|
|
||||||
|
Host 仍不得控制 T0/T1/MIX。
|
||||||
|
## 3. 固定架构边界
|
||||||
|
|
||||||
|
必须保护:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Test Host / NavSea JS/TS:下载、缓存、重试、gzip 解压、Manifest/Time Index
|
||||||
|
MapLibreParticle:只从 Host 已准备好的 raw QBIN bytes 开始
|
||||||
|
Core:选择 per-field T0/T1/MIX
|
||||||
|
Renderer:不解析 QBIN,不知道 provider/network/profile 业务
|
||||||
|
```
|
||||||
|
|
||||||
|
真实服务器 QBIN 不在本调查中下载或使用。
|
||||||
|
## 4. 必查实际链路
|
||||||
|
|
||||||
|
必须基于当前真实代码逐层记录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
packages/maplibre-particle-rn/src/FieldRuntime.ts
|
||||||
|
packages/maplibre-particle-rn/src/NativeWeatherRuntimeBridge.ts
|
||||||
|
packages/maplibre-particle-rn/src/types.ts
|
||||||
|
WeatherRuntimeBridgeModule.kt
|
||||||
|
WeatherRuntime.java
|
||||||
|
MapLibreMap.java
|
||||||
|
NativeMapView.java
|
||||||
|
native_map_view.hpp / native_map_view.cpp
|
||||||
|
RasterArraySource V4 Host API
|
||||||
|
```
|
||||||
|
|
||||||
|
不得只引用旧 STEP 文档替代源码调查。
|
||||||
|
## 5. QBIN 传输形态调查
|
||||||
|
|
||||||
|
重点比较并给出推荐:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A. RN 传 qbinFileUri,本地 Android 层读取后交给 Java/JNI
|
||||||
|
B. RN 直接传 byte[] / number[]
|
||||||
|
C. 其他现有可复用的本地 binary seam
|
||||||
|
```
|
||||||
|
|
||||||
|
必须结合当前 TurboModule/codegen、Kotlin 文件读取、Java byte[]、JNI array copy 与 64 MiB 限制分析。
|
||||||
|
|
||||||
|
默认倾向不得由 JS 构造大 number array / Base64;但最终结论必须以现有代码和成本为依据。
|
||||||
|
## 6. RuntimeMetadata / ProfileContract / Binding ABI 调查
|
||||||
|
|
||||||
|
分别给出跨层最小 ABI 推荐:
|
||||||
|
|
||||||
|
```text
|
||||||
|
EncodedFieldRuntimeMetadata
|
||||||
|
ResolvedFieldProfileContract + requiredPlanes[]
|
||||||
|
ColorBinding
|
||||||
|
ParticleBinding
|
||||||
|
Forecast playback state/speed/time
|
||||||
|
```
|
||||||
|
|
||||||
|
比较:typed primitive parameters、primitive arrays、JSON string、ReadableMap/ReadableArray 等可行形态。
|
||||||
|
|
||||||
|
结论必须说明每一层在哪里解析、在哪里只做边界校验、如何避免 Server/SDK/Host/Native 多处重复业务规则。
|
||||||
|
## 7. 兼容性与命名调查
|
||||||
|
|
||||||
|
必须明确:
|
||||||
|
|
||||||
|
```text
|
||||||
|
现有 V3 FieldRuntime.submitField 是否保留
|
||||||
|
V4 是否新增独立 submitEncodedField,而不是替换旧 submitField
|
||||||
|
WeatherRuntime 现有 setForecastTime / setPlaybackState / setPlaybackSpeed 是否可复用或必须新增 V4 语义 API
|
||||||
|
clearFields 与 clearEncodedFields 是否必须分离
|
||||||
|
旧 Renderer/V3 timeline 是否可能被 V4 命令误操作
|
||||||
|
```
|
||||||
|
|
||||||
|
不得为了表面统一而让 V4 clear/time/playback 复用会修改 V3 timeline 的旧实现。
|
||||||
|
## 8. 生命周期与线程模型
|
||||||
|
|
||||||
|
必须调查并说明:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WeatherRuntime 与 MapLibreMap / MapView 的所有权
|
||||||
|
Fabric reactTag → MLRNMapView → MapLibreMap 的现有解析路径
|
||||||
|
Java 主线程约束
|
||||||
|
NativeMapView / Style Source 查找与生命周期
|
||||||
|
style reload / map detach / runtime dispose 后 V4 状态如何失效
|
||||||
|
```
|
||||||
|
|
||||||
|
不得引入第二套 Map registry 或长期保存 reactTag/map pointer 的新机制。
|
||||||
|
## 9. 错误模型调查
|
||||||
|
|
||||||
|
必须把 C++ `RasterArrayEncodedFieldSubmitResult` 与 RN 可见结果的推荐映射列全:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AcceptedInserted
|
||||||
|
AcceptedReplacedRevision
|
||||||
|
Duplicate
|
||||||
|
RejectedDecode
|
||||||
|
RejectedContract
|
||||||
|
RejectedInvalidFieldKey
|
||||||
|
RejectedIdentityConflict
|
||||||
|
RejectedStaleRevision
|
||||||
|
RejectedIncompatible
|
||||||
|
RejectedCapacity
|
||||||
|
```
|
||||||
|
|
||||||
|
同时说明文件不可读、参数非法、runtime disposed、source missing/style not ready 等桥接层错误与 Core submit result 如何分层,不得混成一个 UNKNOWN。
|
||||||
|
## 10. 验证能力调查
|
||||||
|
|
||||||
|
不执行 Android 编译、APK、ADB 或实体机操作。
|
||||||
|
|
||||||
|
调查当前可在 Linux 完成的静态验证入口,包括:
|
||||||
|
|
||||||
|
```text
|
||||||
|
TurboModule codegen schema
|
||||||
|
TypeScript typecheck
|
||||||
|
JNI C++ 精确编译或 Linux 可编译部分
|
||||||
|
Java/Kotlin 是否有无需 Android Gradle 的现成静态验证入口
|
||||||
|
现有 WeatherRuntime JVM tests 的边界
|
||||||
|
```
|
||||||
|
|
||||||
|
给出后续实现 Step 的分层验收清单,不在本任务实际执行大规模 build。
|
||||||
|
## 11. 输出文档
|
||||||
|
|
||||||
|
必须生成:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/20_调查/STEP-077_QBIN2_V4_Android_JNI_RN桥接调查报告_v1.md
|
||||||
|
docs/报告/STEP-077_QBIN2_V4_Android_JNI_RN桥接调查_Codex完成报告_v1.md
|
||||||
|
```
|
||||||
|
|
||||||
|
并追加 `粒子开发记录.md`。
|
||||||
|
|
||||||
|
调查报告必须包含当前链路图、推荐 V4 API 表、每层参数形态、错误映射、生命周期、兼容策略、实施文件清单、风险与后续实现拆分建议。
|
||||||
|
## 12. 禁止项
|
||||||
|
|
||||||
|
不得修改 production code、不得新增桥接 API、不得修改 package version、不得修改 Test Host、不得进入真实 QBIN 联调、不得修改 Renderer/GPU。
|
||||||
|
|
||||||
|
任务开始前已有 dirty/untracked 内容必须保持原样,不得清理、覆盖、暂存或提交。
|
||||||
|
|
||||||
|
## 13. Git 收尾
|
||||||
|
|
||||||
|
本任务会生成调查文档,因此完成后只提交本任务新增/修改的文档,commit 后 push `origin/particle/main`。
|
||||||
|
|
||||||
|
完成报告必须记录 commit hash、commit message、push result、remote branch、Final HEAD;push 失败不得声称任务完成。
|
||||||
|
|
||||||
|
完成后立即停止,不进入 STEP-077 实现。
|
||||||
254
docs/40_任务/STEP-081_iOS_Metal_Renderer与真机Smoke入口_Codex任务_v1.md
Normal file
254
docs/40_任务/STEP-081_iOS_Metal_Renderer与真机Smoke入口_Codex任务_v1.md
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# STEP-081 — iOS Metal Renderer 与真机 Smoke 入口 Codex 任务 v1
|
||||||
|
|
||||||
|
状态:计划
|
||||||
|
日期:2026-09-17
|
||||||
|
|
||||||
|
## 1. 任务性质与执行机器
|
||||||
|
|
||||||
|
本任务在 `Mac-mini-tei.lan` 的 `/Users/tei/dev/MapLibreParticleLab` 执行,是 Android 粒子系统完成后的第一张 iOS/Metal 实现任务。
|
||||||
|
|
||||||
|
当前代码事实:
|
||||||
|
|
||||||
|
- branch:`particle/main`
|
||||||
|
- 起点 HEAD:`f23f4143b9642de0e8d6661dd567e791494a0a0b`
|
||||||
|
- `origin/particle/main`:同一 commit
|
||||||
|
- 当前 Android/RN 代码版本:`13.4.1-particle.5`
|
||||||
|
- iOS 官方基线:`ios-v6.28.0`,与 Android 共用 core `18545ca10d296704b2cba675b182ff2c8da24588`
|
||||||
|
- iOS 当前 renderer:Metal
|
||||||
|
- `ParticleGpuValidationShader` 目前只有 OpenGL ShaderSource,Metal 尚未实现/注册。
|
||||||
|
|
||||||
|
Codex 是本任务的大规模代码实现者;**Codex 禁止执行任何 iOS/Xcode/CMake 编译、链接、安装、Simulator 或真机操作**。Codex 完成后停止,由 RDC 把结果交给 GPT 审查;审查通过后,实际编译和真机验证由 GPT + RDC 完成。
|
||||||
|
## 2. 前置必读
|
||||||
|
|
||||||
|
开始修改前必须读取并遵守:
|
||||||
|
|
||||||
|
1. `AGENTS.md`
|
||||||
|
2. `PARTICLE_BASELINE.md`
|
||||||
|
3. `docs/00_项目规范/` 全部当前文件
|
||||||
|
4. `docs/00_项目总则/README.md` 及其列出的当前有效总则
|
||||||
|
5. `docs/00_项目总则/当前基线与开发路线_v2.md`
|
||||||
|
6. `packages/maplibre-particle-rn/CHANGELOG.md` 与 `README.md`
|
||||||
|
7. 当前 `RasterParticleLayer`、`WeatherColorLayer`、QBIN V4 Projection/Materialization 代码
|
||||||
|
8. Metal backend、Metal shader group/program、iOS app CMake 与 `MLNMapView` private Core seam
|
||||||
|
|
||||||
|
若旧总则中出现“Simulator 验收”或“Codex 负责构建”的描述,以本任务和用户最新规则为准:**iOS 不做 Simulator 测试;Codex 不编译;RDC 编译;运行验收只用真机。**
|
||||||
|
|
||||||
|
## 3. 本 Step 目标
|
||||||
|
|
||||||
|
实现第一条可被 iOS 真机触发的 Metal 粒子渲染路径:
|
||||||
|
|
||||||
|
```text
|
||||||
|
QBIN V4 / canonical field
|
||||||
|
→ RasterArraySource V4 renderer projection
|
||||||
|
→ WeatherColorLayer + RasterParticleLayer
|
||||||
|
→ ParticleGpuValidationShader (Metal)
|
||||||
|
→ MapLibre Metal gfx backend
|
||||||
|
→ iOS 原生 Test App 真机 Smoke 入口
|
||||||
|
```
|
||||||
|
|
||||||
|
本 Step 不接 React Native iOS bridge。
|
||||||
|
## 4. 固定架构边界
|
||||||
|
|
||||||
|
必须保持 Android 已冻结的核心语义不变:
|
||||||
|
|
||||||
|
- 不改变 QBIN V4 文件格式、Decoder、FieldProfile/Binding 语义。
|
||||||
|
- 不改变 `RasterArraySource` 对 T0/T1/MIX、Forecast Clock、boundary reuse 的所有权。
|
||||||
|
- 不改变 `WeatherColorLayer` 与 `RasterParticleLayer` 分离原则。
|
||||||
|
- 不新增 iOS 专用 Field 数据模型,不复制一套 Particle Runtime。
|
||||||
|
- 不把 provider、HTTP、manifest、下载、缓存或 NavSea 业务逻辑放进 Native/iOS。
|
||||||
|
- 粒子颜色继续独立于 WeatherColor ramp;不得为了 Metal 顺手改变视觉产品语义。
|
||||||
|
- iOS/Metal 应消费与 Android/OpenGL 相同的 C++ RenderLayer、UBO、field snapshot 和 renderer-facing V4 projection。
|
||||||
|
|
||||||
|
后端差异只允许落在 Metal Shader/资源绑定/必要的 backend thin-hook 与 iOS Test App smoke seam。
|
||||||
|
|
||||||
|
## 5. 必做项 A — Metal Particle Shader
|
||||||
|
|
||||||
|
新增 `ParticleGpuValidationShader` 的 Metal `ShaderSource`,语义必须与当前 OpenGL 版本逐 pass 对齐,至少覆盖:
|
||||||
|
|
||||||
|
- State A/B update
|
||||||
|
- Trail fade
|
||||||
|
- Trail particle line draw
|
||||||
|
- Composite / FrozenComposite
|
||||||
|
- WeatherColor pass(当前 `u_pass.x > 4.5`)
|
||||||
|
- T0/T1 `timeMix`
|
||||||
|
- field bounds / respawn bounds
|
||||||
|
- VELOCITY 与 DIRECTION 两种 advection semantics
|
||||||
|
- particleColor / trailColor / speed-color optional path
|
||||||
|
- WeatherColor stops/ramp
|
||||||
|
- invalid alpha / invalid sample 的 fail-closed 行为
|
||||||
|
- world/map matrix projection
|
||||||
|
Metal 实现必须复用现有 `ParticleGpuValidationUBO`,不得另建 iOS 专用 UBO。必须核对 Metal `alignas(16)`/buffer layout 与当前 `sizeof(ParticleGpuValidationUBO) == 31 * 16` 契约。
|
||||||
|
|
||||||
|
必须从当前 shader IDs / texture IDs / attribute IDs 推导 Metal binding,不得凭 OpenGL location 猜测。
|
||||||
|
|
||||||
|
建议文件形态遵循现有 Metal shader 规范,例如:
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/mbgl/shaders/mtl/particle_gpu_validation.hpp
|
||||||
|
src/mbgl/shaders/mtl/particle_gpu_validation.cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
具体文件名可按仓库现有命名契约调整,但不得把 MSL 字符串塞进 renderer layer 源文件。
|
||||||
|
|
||||||
|
## 6. 必做项 B — Metal backend 注册与构建图接线
|
||||||
|
|
||||||
|
必须完成:
|
||||||
|
|
||||||
|
- 在 Metal renderer backend include 新 shader。
|
||||||
|
- 将 `BuiltIn::ParticleGpuValidationShader` 注册进 Metal shader registry。
|
||||||
|
- 补齐 Metal shader `attributes`、`textures` 与 UBO/buffer binding。
|
||||||
|
- 如当前 CMake/Bazel/source list 不是自动 glob,补齐最小构建图接线。
|
||||||
|
- 不修改 OpenGL shader 语义来“迁就”Metal;若共享 ID 定义确有缺口,只做 backend-neutral 最小修正并在报告中单列原因。
|
||||||
|
|
||||||
|
必须静态检查当前 `RenderRasterParticleLayer` / `RenderWeatherColorLayer` 使用的 gfx API 是否存在明显 OpenGL-only 假设。能通过现有 gfx 抽象工作的代码保持不动;只有明确阻断 Metal 的点才允许最小修改。
|
||||||
|
## 7. 必做项 C — iOS 原生真机 Smoke 入口
|
||||||
|
|
||||||
|
在现有 `platform/ios/app` 内增加一个最小、隔离、可明确触发的 Particle/QBIN V4 Smoke 场景,用于后续 RDC 编译安装后在真实 iPhone/iPad 上触发 Metal shader runtime compilation。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 使用现有 iOS Test App,不新建第二个产品工程。
|
||||||
|
- 场景应能从 App 内明确进入,名称应能识别为 Particle / QBIN V4 Smoke。
|
||||||
|
- 使用仓库现有 Wind Golden QBIN T0/T1 作为本地 fixture;不得联网下载。
|
||||||
|
- fixture 可通过 CMake resource copy 或其他现有 iOS app 资源机制进入 bundle;不得复制出新的数据格式。
|
||||||
|
- 通过 iOS Objective-C++ / private test seam 取得当前 `mbgl::Map` / Style,并调用现有 C++ Host API;不得新增另一套 runtime。
|
||||||
|
- 建立 `raster-array` source、`weather-color` layer、`raster-particle` layer,并提交 Wind T0/T1。
|
||||||
|
- Smoke 默认应同时显示 WeatherColor 与 Particle,便于真机确认 Metal 两种 pass 都被实际触发。
|
||||||
|
- 至少提供 T0 / midpoint / T1 三个固定时间状态或等价的可操作入口,使后续真机能验证 `mix`。
|
||||||
|
- 提供 clear/reload 最小入口或可重复进入场景的方式,以便观察资源释放/重建。
|
||||||
|
|
||||||
|
Smoke 代码只能存在于测试 App/测试 seam,不得进入公开 iOS SDK API,也不得成为 NavSea 正式接口。
|
||||||
|
|
||||||
|
## 8. 非目标
|
||||||
|
|
||||||
|
本 Step 明确不做:
|
||||||
|
|
||||||
|
- React Native iOS bridge / TurboModule
|
||||||
|
- Expo plugin iOS 接线
|
||||||
|
- NavSea iOS 接入
|
||||||
|
- npm/iOS artifact 发布
|
||||||
|
- Android AAR/RN package 版本升级
|
||||||
|
- Vulkan 实现
|
||||||
|
- 新粒子算法、视觉重设计、性能调优
|
||||||
|
|
||||||
|
## 9. 允许修改范围
|
||||||
|
|
||||||
|
优先限制在:
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/mbgl/shaders/mtl/
|
||||||
|
src/mbgl/shaders/mtl/
|
||||||
|
src/mbgl/mtl/renderer_backend.cpp
|
||||||
|
必要的 CMake/Bazel/source list
|
||||||
|
src/mbgl/renderer/layers/(仅 Metal 阻断的 backend-neutral 最小修正)
|
||||||
|
platform/ios/app/
|
||||||
|
integration/react-native/MapLibreParticleRNTestHost/assets/qbin-golden/wind/(只读 fixture 来源)
|
||||||
|
docs/30_设计/
|
||||||
|
docs/50_Step记录/
|
||||||
|
docs/报告/
|
||||||
|
粒子开发记录.md
|
||||||
|
```
|
||||||
|
|
||||||
|
若必须超出以上 production 范围,先判断是否仍属于明确的 Metal thin-hook;若需要改 QBIN/Core contract、RasterArraySource API 或 Android bridge,立即停止并在报告标为 BLOCKED,不得自行扩大任务。
|
||||||
|
|
||||||
|
## 10. 禁止修改范围
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
- `docs/00_项目总则/`
|
||||||
|
- `docs/00_项目规范/`
|
||||||
|
- QBIN V4 格式/Decoder/Profile/Binding contract
|
||||||
|
- Android JNI/Kotlin/RN bridge
|
||||||
|
- Android 版本号与公开 artifact coordinate
|
||||||
|
- `packages/maplibre-particle-rn` 公共 API
|
||||||
|
- upstream remote/tag/history
|
||||||
|
- 与本 Step 无关的已有文件/未跟踪文件
|
||||||
|
|
||||||
|
## 11. Codex 验证规则 — 严禁编译
|
||||||
|
|
||||||
|
Codex 本任务只允许静态验证,例如:
|
||||||
|
|
||||||
|
```text
|
||||||
|
git diff --check
|
||||||
|
git status --short
|
||||||
|
git grep / rg / find
|
||||||
|
必要的文本/结构一致性检查
|
||||||
|
```
|
||||||
|
|
||||||
|
**禁止 Codex 执行任何可能进入 iOS/Native 构建、链接、shader runtime 编译、安装或运行的命令**,包括但不限于:
|
||||||
|
|
||||||
|
```text
|
||||||
|
xcodebuild
|
||||||
|
cmake / ninja / make / bazel build
|
||||||
|
clang / clang++ / metal / metallib 的编译调用
|
||||||
|
pod install / swift build
|
||||||
|
simctl
|
||||||
|
任何 iOS Simulator 命令
|
||||||
|
任何真机安装/启动命令
|
||||||
|
```
|
||||||
|
|
||||||
|
也不要用 Android/Linux build 代替 iOS 验证。本 Step 的编译状态在 Codex 报告中必须写为 `NOT_RUN_BY_DESIGN`。
|
||||||
|
|
||||||
|
不加入 Jest,不执行 Jest。
|
||||||
|
|
||||||
|
## 12. GPT/RDC 后续验收接口
|
||||||
|
|
||||||
|
Codex 必须在完成报告中明确列出,供 GPT/RDC 后续逐项执行:
|
||||||
|
|
||||||
|
1. iPhoneOS device build 所需的推荐 CMake/Xcode 入口与 target 名称。
|
||||||
|
2. Smoke 场景在 iOS Test App 中的进入方法。
|
||||||
|
3. 真机预期出现的关键日志事件,至少覆盖 shader/layer creation、field ready、particle resource create。
|
||||||
|
4. T0 / midpoint / T1 与 clear/reload 的人工操作步骤。
|
||||||
|
5. 若 Metal shader runtime compile 失败,应优先检查的源文件/函数/绑定表。
|
||||||
|
|
||||||
|
## 13. 必做输出
|
||||||
|
|
||||||
|
Codex 必须实际生成/更新:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/30_设计/STEP-081_iOS_Metal_Renderer与真机Smoke入口设计_v1.md
|
||||||
|
docs/50_Step记录/STEP-081_iOS_Metal_Renderer与真机Smoke入口_v1.md
|
||||||
|
docs/报告/STEP-081_iOS_Metal_Renderer与真机Smoke入口_Codex完成报告_v1.md
|
||||||
|
粒子开发记录.md
|
||||||
|
```
|
||||||
|
|
||||||
|
设计文档必须记录 OpenGL → Metal pass 对照、Metal UBO/attribute/texture binding 表、Metal 注册位置、Smoke 场景数据链和后续 RN iOS bridge 的明确边界。
|
||||||
|
|
||||||
|
Step 状态在 Codex 结束时只能根据事实写:代码实现完成但未编译时,写“进行中 / WAITING_RDC_BUILD_REVIEW”或等价明确表述;**不得写 iOS PASS、Metal PASS 或真机 PASS**。
|
||||||
|
|
||||||
|
完成报告必须至少包含:
|
||||||
|
|
||||||
|
- 执行结论:PASS/PARTIAL/BLOCKED/FAIL(仅针对 Codex 代码任务本身)
|
||||||
|
- branch、Start HEAD、End HEAD、工作树状态
|
||||||
|
- 实际修改/新增文件
|
||||||
|
- Metal shader/pass/binding 实现摘要
|
||||||
|
- iOS Smoke 入口说明
|
||||||
|
- 与任务书差异
|
||||||
|
- 构建记录:明确 `NOT_RUN_BY_DESIGN — Codex prohibited from building`
|
||||||
|
- 静态检查记录
|
||||||
|
- 已知风险/未完成项
|
||||||
|
- 给 GPT/RDC 的编译与真机验收清单
|
||||||
|
- commit hash、commit message、push result、remote branch、Final HEAD
|
||||||
|
|
||||||
|
## 14. Git 收尾
|
||||||
|
|
||||||
|
本任务会修改项目文件,因此 Codex 必须:
|
||||||
|
|
||||||
|
1. 只提交本任务相关文件,不得夹带既有无关 dirty/untracked。
|
||||||
|
2. 使用 scoped commit message,例如:`feat: bring up particle renderer on ios metal`。
|
||||||
|
3. commit 后 push 到 `origin/particle/main`。
|
||||||
|
4. push 失败不得声称完成。
|
||||||
|
5. 完成后不得继续下一 Step,不得自行开始 RN iOS bridge。
|
||||||
|
|
||||||
|
## 15. 停止条件
|
||||||
|
|
||||||
|
出现以下任一情况必须停止扩大修改并在报告中写明 BLOCKED/PARTIAL:
|
||||||
|
|
||||||
|
- Metal 实现需要改变 QBIN V4/Core contract 才能继续。
|
||||||
|
- 需要建立 iOS 独立 Particle Runtime 或复制 Android Runtime 逻辑。
|
||||||
|
- 必须修改 Android bridge/API 才能让 iOS smoke 工作。
|
||||||
|
- 发现现有 gfx RenderTarget/Texture/Lines 在 Metal 根本不具备所需能力,且修复将变成大规模 gfx backend 重构。
|
||||||
|
- 需要升级 Xcode、MapLibre baseline、CMake、SDK 或第三方依赖。
|
||||||
|
- 无法以现有 iOS Test App/private test seam 触发 Core Host API,且需要新增公开 SDK API。
|
||||||
|
|
||||||
|
完成本任务后立即停止,等待 RDC 将 Codex 结果交给 GPT 审查。GPT 审查通过后,才由 RDC 执行 iPhoneOS device build,并只在真实 iPhone/iPad 上做运行验收。
|
||||||
113
docs/40_任务/STEP-082_XCFramework_Bazel_Codegen阻塞收尾_Codex任务_v1.md
Normal file
113
docs/40_任务/STEP-082_XCFramework_Bazel_Codegen阻塞收尾_Codex任务_v1.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# STEP-082 XCFramework Bazel Codegen 阻塞收尾 Codex 任务 v1
|
||||||
|
|
||||||
|
## 1. 当前基线
|
||||||
|
|
||||||
|
- 项目:`/Users/tei/dev/MapLibreParticleLab`
|
||||||
|
- 分支:`particle/main`
|
||||||
|
- 起始 HEAD:`dd9687f5ed6bb9be4b58b73f2efd39f54faef7d6`
|
||||||
|
- `origin/particle/main` 应与起始 HEAD 一致。
|
||||||
|
- STEP-082 当前状态:`IMPLEMENTED / WAITING_MAC_XCFRAMEWORK_VALIDATION`。
|
||||||
|
- 本任务仍属于 STEP-082 收尾,不创建 STEP-083,不进入 RN/JS/NavSea。
|
||||||
|
|
||||||
|
开始前必须读取:
|
||||||
|
- `docs/报告/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex完成报告_v1.md`
|
||||||
|
- `docs/50_Step记录/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_v1.md`
|
||||||
|
- `bazel/core.bzl`
|
||||||
|
- `BUILD.bazel`
|
||||||
|
- `scripts/generate-style-code.mjs`
|
||||||
|
- `CMakeLists.txt`
|
||||||
|
|
||||||
|
## 2. 已知阻塞与调查结论
|
||||||
|
|
||||||
|
当前 `bazel build //platform/ios:MapLibre.dynamic` 在 `//:generated_style_code` 阶段失败:
|
||||||
|
`src/mbgl/style/layers/particle_validation_layer.cpp` 被声明为 generator 输出,但 `scripts/generate-style-code.mjs` 不生成它。
|
||||||
|
|
||||||
|
当前只读核对显示:
|
||||||
|
- `particle_validation_layer.cpp` 是仓库中已存在的手写源文件;
|
||||||
|
- CMake 在 `CMakeLists.txt` 中把它作为普通源文件直接编译;
|
||||||
|
- `bazel/core.bzl` 目前把它放在 `MLN_GENERATED_STYLE_SOURCE`;
|
||||||
|
- style generator 只遍历 style spec 中的正式 layer type 并生成对应标准 style layer 文件。
|
||||||
|
|
||||||
|
这说明高概率是 Bazel 文件分类错误,而不是 generator 缺功能。Codex 必须先自行核对真实代码和 Git 历史后再修改。## 3. 本次唯一目标
|
||||||
|
|
||||||
|
用最小改动修复 Bazel 对 `particle_validation_layer.cpp` 的源文件分类,使:
|
||||||
|
1. 它不再作为 `generated_style_code` 的声明输出;
|
||||||
|
2. 它仍然作为 Core 普通源文件参与 Bazel 构建;
|
||||||
|
3. 不改变现有 CMake 编译行为;
|
||||||
|
4. 不改变 Particle Validation Layer、RasterParticle、WeatherColor、QBIN、Metal 或 Runtime 语义。
|
||||||
|
|
||||||
|
优先方案是:若调查确认该文件确属手写源文件,则从 `MLN_GENERATED_STYLE_SOURCE` 移出并纳入正确的普通 Core source 集合。不得为了通过构建而让 generator 复制、伪造或空生成该文件。
|
||||||
|
|
||||||
|
## 4. 必做清单
|
||||||
|
|
||||||
|
- [ ] 核对起始 branch / HEAD / remote / dirty。
|
||||||
|
- [ ] 用 Git 历史确认 `particle_validation_layer.cpp` 的来源和预期性质。
|
||||||
|
- [ ] 最小修复 Bazel source classification。
|
||||||
|
- [ ] `bazel build //:generated_style_code` 通过。
|
||||||
|
- [ ] `bazel build //platform/ios:MapLibre.dynamic` 通过。
|
||||||
|
- [ ] 验证生成的 `MapLibre.dynamic.xcframework` 中包含 public `MLNParticleFieldRuntime.h`。
|
||||||
|
- [ ] 验证 umbrella/public header 链可见 `MLNParticleFieldRuntime`。
|
||||||
|
- [ ] 验证 binary 中存在该 facade 的 Objective-C public symbol/class。
|
||||||
|
- [ ] 验证 `MLNMapView_Private.h` 没有被导出为 public header。
|
||||||
|
- [ ] 若正式 XCFramework 可压缩,则生成临时 zip,记录真实文件大小与 SHA-256;大二进制默认不提交 Git。
|
||||||
|
- [ ] 更新 `release/maplibre-particle/ios/release-manifest.json` 中可由本次真实构建确认的字段;URL 尚未发布时保持明确占位,不伪造远端地址。
|
||||||
|
- [ ] `git diff --check` PASS。
|
||||||
|
- [ ] 更新 STEP-082 Step 记录和 `粒子开发记录.md`。
|
||||||
|
- [ ] 生成 `docs/报告/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex完成报告_v2.md`。
|
||||||
|
- [ ] scoped commit、push 到 `origin/particle/main`,记录 Final HEAD。
|
||||||
|
|
||||||
|
## 5. 严格禁止
|
||||||
|
|
||||||
|
不得:
|
||||||
|
- 修改 `scripts/generate-style-code.mjs` 去伪造 particle validation 输出,除非调查证明它本应由 style spec 生成;若出现这种相反证据,先在报告中说明,不要扩大实现。
|
||||||
|
- 修改 Particle renderer、Metal shader、QBIN decoder、FieldProfile、BindingProjection、粒子 physics。
|
||||||
|
- 修改 `packages/maplibre-particle-rn/`、`integration/react-native/` 或 NavSea。
|
||||||
|
- 重做 STEP-081 真机 smoke。
|
||||||
|
- 开始 STEP-083。
|
||||||
|
- 为通过构建删除或停用 Particle Validation Layer。
|
||||||
|
- 使用 `git add -A`、`git add .`、`git reset --hard`、`git clean`。## 6. 用户已有 dirty,必须原样保护
|
||||||
|
|
||||||
|
以下路径不得修改、暂存、提交:
|
||||||
|
- `docs/doxygen/doxygen-awesome-css`
|
||||||
|
- `platform/ios/app/MBXAppDelegate.m`
|
||||||
|
- `platform/windows/vendor/vcpkg`
|
||||||
|
|
||||||
|
另外,现有未跟踪任务书:
|
||||||
|
- `docs/40_任务/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex任务_v1.md`
|
||||||
|
不得为了清理工作树而删除或改写。
|
||||||
|
|
||||||
|
本任务书自身也不要求提交;提交时只显式 stage 本次实现与正式记录文件。
|
||||||
|
|
||||||
|
## 7. 构建与验证要求
|
||||||
|
|
||||||
|
macOS 上使用仓库当前真实工具链。编译输出遵循全局 `$build-output-throttle`。
|
||||||
|
|
||||||
|
至少执行:
|
||||||
|
- `bazel build //:generated_style_code`
|
||||||
|
- `bazel build //platform/ios:MapLibre.dynamic`
|
||||||
|
|
||||||
|
若第二条成功,必须直接检查实际 XCFramework,不允许只根据 Bazel exit code 判定:
|
||||||
|
- public Headers;
|
||||||
|
- umbrella import;
|
||||||
|
- Objective-C class/symbol;
|
||||||
|
- private header 排除;
|
||||||
|
- artifact 路径、大小;
|
||||||
|
- zip SHA-256(若生成 zip)。
|
||||||
|
|
||||||
|
如果修复第一个 blocker 后暴露新的、与本次 Bazel/iOS packaging 直接相关的小型问题,可以继续最小修复并验证;如果新问题涉及 renderer/Core 语义或较大架构改动,停止并报告,不得扩大范围。
|
||||||
|
|
||||||
|
## 8. 关闭判定
|
||||||
|
|
||||||
|
只有以下全部成立,STEP-082 才可改为 `CLOSED / PASS`:
|
||||||
|
- Bazel generated style target PASS;
|
||||||
|
- `MapLibre.dynamic` XCFramework build PASS;
|
||||||
|
- `MLNParticleFieldRuntime.h` 确认存在于 public Headers;
|
||||||
|
- public ObjC facade symbol/class 确认存在;
|
||||||
|
- `MLNMapView_Private.h` 未 public export;
|
||||||
|
- artifact size 与真实 SHA-256 已记录;
|
||||||
|
- `git diff --check` PASS;
|
||||||
|
- v2 完成报告、Step 记录、粒子开发记录更新;
|
||||||
|
- scoped commit 已 push,local HEAD == `origin/particle/main`;
|
||||||
|
- 用户三处 dirty 和旧任务书保持未触碰。
|
||||||
|
|
||||||
|
若任一门槛未满足,保持 `IMPLEMENTED / WAITING_MAC_XCFRAMEWORK_VALIDATION` 或明确 BLOCKED,不得写 PASS。
|
||||||
@@ -0,0 +1,801 @@
|
|||||||
|
# STEP-082 iOS Native FieldRuntime Facade 与 XCFramework 发行基线 — Codex 任务 v1
|
||||||
|
|
||||||
|
状态:READY FOR IMPLEMENTATION
|
||||||
|
项目:MapLibreParticleLab / 粒子系统
|
||||||
|
目标分支:`particle/main`
|
||||||
|
任务基线 HEAD:`3e03949d3411c830cfff4bf320cd350f5b6a7527`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 开始前必须确认
|
||||||
|
|
||||||
|
先完整阅读本任务文件,再开始修改。
|
||||||
|
|
||||||
|
开始时只读执行并记录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git branch --show-current
|
||||||
|
git rev-parse HEAD
|
||||||
|
git rev-parse origin/particle/main
|
||||||
|
git status --short
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:
|
||||||
|
|
||||||
|
```text
|
||||||
|
branch = particle/main
|
||||||
|
HEAD = origin/particle/main = 3e03949d3411c830cfff4bf320cd350f5b6a7527
|
||||||
|
```
|
||||||
|
|
||||||
|
已知且必须保持不动的用户无关修改:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/doxygen/doxygen-awesome-css
|
||||||
|
platform/ios/app/MBXAppDelegate.m
|
||||||
|
platform/windows/vendor/vcpkg
|
||||||
|
```
|
||||||
|
|
||||||
|
若还有新的未知 dirty:
|
||||||
|
|
||||||
|
- 不自动清理;
|
||||||
|
- 不 reset;
|
||||||
|
- 不 checkout 覆盖;
|
||||||
|
- 不纳入本任务 commit;
|
||||||
|
- 在完成报告中说明。
|
||||||
|
|
||||||
|
### 执行清单
|
||||||
|
|
||||||
|
开始实施前建立简短勾选清单,至少包含:
|
||||||
|
|
||||||
|
- [ ] iOS public Native FieldRuntime facade
|
||||||
|
- [ ] MapView-owned 生命周期
|
||||||
|
- [ ] V4 submit / binding / forecast / clear
|
||||||
|
- [ ] fail-closed / style reload 安全
|
||||||
|
- [ ] public header 无 Core 类型泄漏
|
||||||
|
- [ ] XCFramework public header / symbol 验证
|
||||||
|
- [ ] distribution-ready 元数据
|
||||||
|
- [ ] 中文设计/Step/完成报告
|
||||||
|
- [ ] git diff --check
|
||||||
|
- [ ] scoped commit / push
|
||||||
|
- [ ] 最终逐项对照
|
||||||
|
|
||||||
|
不得静默跳过;无法完成必须明确写明。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1. 本 Step 的定位
|
||||||
|
|
||||||
|
STEP-081 已正式 `CLOSED / PASS`。
|
||||||
|
|
||||||
|
本任务禁止重新做:
|
||||||
|
|
||||||
|
- Uniform East
|
||||||
|
- Uniform North
|
||||||
|
- Vortex
|
||||||
|
- QBIN T0 / MID / T1
|
||||||
|
- Clear / Clear 后重建
|
||||||
|
- STEP081 readback
|
||||||
|
- 固定绿线
|
||||||
|
- Point fallback
|
||||||
|
- STEP-081 Metal pipeline cache 根因复验
|
||||||
|
|
||||||
|
STEP-082 是新的产品化前置层:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RN / Consumer(后续 STEP-083)
|
||||||
|
↓
|
||||||
|
MapLibre iOS public ObjC API
|
||||||
|
↓
|
||||||
|
MLNParticleFieldRuntime
|
||||||
|
↓
|
||||||
|
MLNMapView private implementation seam
|
||||||
|
↓
|
||||||
|
mbgl::Style / RasterArraySource / QBIN V4
|
||||||
|
↓
|
||||||
|
WeatherColor / RasterParticle / Metal
|
||||||
|
```
|
||||||
|
|
||||||
|
本 Step 只做到 **公开 Native facade + 正式 XCFramework 导出基线**。
|
||||||
|
|
||||||
|
不做 RN TurboModule。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2. 已确认的当前真实事实
|
||||||
|
|
||||||
|
实施前只需快速复核,不得重新做大范围架构调查。
|
||||||
|
|
||||||
|
## 2.1 当前 RN package 是 Android-only Native
|
||||||
|
|
||||||
|
当前:
|
||||||
|
|
||||||
|
```text
|
||||||
|
packages/maplibre-particle-rn/package.json
|
||||||
|
version = 13.4.1-particle.5
|
||||||
|
```
|
||||||
|
|
||||||
|
`files` 没有 `ios`。
|
||||||
|
|
||||||
|
`react-native.config.js` 仅有 Android。
|
||||||
|
|
||||||
|
这是 STEP-083 的工作,不在本 Step 修改。
|
||||||
|
|
||||||
|
## 2.2 STEP-081 private seam 不能作为正式 SDK API
|
||||||
|
|
||||||
|
当前:
|
||||||
|
|
||||||
|
```text
|
||||||
|
platform/ios/src/MLNMapView_Private.h
|
||||||
|
```
|
||||||
|
|
||||||
|
暴露:
|
||||||
|
|
||||||
|
```objc
|
||||||
|
- (mbgl::Map &)mbglMap;
|
||||||
|
```
|
||||||
|
|
||||||
|
但该 header 属于:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MLN_IOS_PRIVATE_HEADERS
|
||||||
|
```
|
||||||
|
|
||||||
|
不在 `MapLibre.dynamic.xcframework` 正式 public headers 中。
|
||||||
|
|
||||||
|
因此本任务必须新增一个正式 public facade,而不是把 `MLNMapView_Private.h` 暴露出去。
|
||||||
|
|
||||||
|
## 2.3 Particle/Weather 类型继续保持 Core-only
|
||||||
|
|
||||||
|
当前 Darwin LayerManager 已注册:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RasterParticleLayerFactory
|
||||||
|
WeatherColorLayerFactory
|
||||||
|
```
|
||||||
|
|
||||||
|
为 Core-only layer。
|
||||||
|
|
||||||
|
不要新建:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MLNRasterParticleLayer
|
||||||
|
MLNWeatherColorLayer
|
||||||
|
MLNRasterArraySource
|
||||||
|
```
|
||||||
|
|
||||||
|
RN / Host 仍通过 Style JSON 创建:
|
||||||
|
|
||||||
|
```text
|
||||||
|
raster-array
|
||||||
|
weather-color
|
||||||
|
raster-particle
|
||||||
|
```
|
||||||
|
|
||||||
|
Native facade 只负责找到这些对象并提交/控制数据。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3. 目标架构
|
||||||
|
|
||||||
|
## 3.1 新增公开 iOS Runtime facade
|
||||||
|
|
||||||
|
推荐类型:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MLNParticleFieldRuntime
|
||||||
|
MLNParticleFieldRuntimeConfiguration
|
||||||
|
MLNMapView+ParticleFieldRuntime
|
||||||
|
```
|
||||||
|
|
||||||
|
如果现有 ObjC 命名规范要求轻微调整,可以调整文件名,但必须在设计文档说明。
|
||||||
|
|
||||||
|
禁止使用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
STEP081*
|
||||||
|
Smoke*
|
||||||
|
Fixture*
|
||||||
|
NavSea*
|
||||||
|
```
|
||||||
|
|
||||||
|
作为公开 Native 类型名。
|
||||||
|
|
||||||
|
## 3.2 Public API 绝不能暴露 Core 类型
|
||||||
|
|
||||||
|
公开 header 只能依赖:
|
||||||
|
|
||||||
|
- Foundation
|
||||||
|
- MapLibre public Objective-C 类型
|
||||||
|
|
||||||
|
禁止出现在 public header:
|
||||||
|
|
||||||
|
```text
|
||||||
|
mbgl::Map
|
||||||
|
mbgl::Style
|
||||||
|
mbgl::style::RasterArraySource
|
||||||
|
std::vector
|
||||||
|
std::unique_ptr
|
||||||
|
void * core handle
|
||||||
|
raw Core pointer
|
||||||
|
MLNMapView_Private.h
|
||||||
|
```
|
||||||
|
|
||||||
|
QBIN payload 使用:
|
||||||
|
|
||||||
|
```objc
|
||||||
|
NSData *
|
||||||
|
```
|
||||||
|
|
||||||
|
而不是文件 URL。
|
||||||
|
|
||||||
|
“本地文件读取”属于后续 RN bridge 边界,不进入 Native SDK facade。
|
||||||
|
|
||||||
|
## 3.3 MapView-owned 生命周期
|
||||||
|
|
||||||
|
目标语义与 Android map-owned `WeatherRuntime` 一致:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MLNMapView
|
||||||
|
owns
|
||||||
|
MLNParticleFieldRuntime
|
||||||
|
```
|
||||||
|
|
||||||
|
必须提供等价的:
|
||||||
|
|
||||||
|
- replace/create
|
||||||
|
- current runtime
|
||||||
|
- dispose
|
||||||
|
|
||||||
|
行为。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
```objc
|
||||||
|
- replaceParticleFieldRuntimeWithConfiguration:
|
||||||
|
- particleFieldRuntime
|
||||||
|
- disposeParticleFieldRuntime
|
||||||
|
```
|
||||||
|
|
||||||
|
可使用 category + associated object,或符合当前 iOS SDK 结构的等价方式。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
1. `MLNParticleFieldRuntime` 不得强引用 MapView 形成循环;
|
||||||
|
2. MapView 释放后 runtime 不得访问悬空 Core;
|
||||||
|
3. replace 必须先 dispose 旧 runtime;
|
||||||
|
4. dispose 幂等;
|
||||||
|
5. runtime 不保存 `RasterArraySource *` 等跨 style 生命周期 Core pointer;
|
||||||
|
6. 每次命令通过当前 MapView/current style 重新查找目标 source/layer。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4. Configuration
|
||||||
|
|
||||||
|
Configuration 至少包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sourceId
|
||||||
|
weatherColorLayerId
|
||||||
|
particleLayerId
|
||||||
|
```
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 初始化时字符串非空;
|
||||||
|
- Runtime 不硬编码 RN package ID;
|
||||||
|
- RN package 后续仍可使用当前固定值:
|
||||||
|
- `rn-weather-fields`
|
||||||
|
- `rn-weather-color`
|
||||||
|
- `rn-weather-particles`
|
||||||
|
- Native SDK 保持 Host-neutral。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5. attach / replace 时的对象验证
|
||||||
|
|
||||||
|
创建 runtime 时必须检查当前 style 中:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sourceId -> RasterArraySource
|
||||||
|
weatherColorLayerId -> WeatherColor layer
|
||||||
|
particleLayerId -> RasterParticle layer
|
||||||
|
```
|
||||||
|
|
||||||
|
验证必须基于 Core 真实类型,而不是只检查字符串 ID。
|
||||||
|
|
||||||
|
失败时:
|
||||||
|
|
||||||
|
- 不创建“半可用” runtime;
|
||||||
|
- 返回明确错误;
|
||||||
|
- 不创建第二套 source/layer;
|
||||||
|
- 不偷偷替换 style;
|
||||||
|
- 不吞掉错误后返回成功。
|
||||||
|
|
||||||
|
Style 尚未 ready 时也必须明确失败。
|
||||||
|
|
||||||
|
后续 Host 会在 style ready 后重试/attach。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6. STEP-082 必须实现的 QBIN V4 API
|
||||||
|
|
||||||
|
本 Step 只冻结新的 V4 产品主链。
|
||||||
|
|
||||||
|
至少实现:
|
||||||
|
|
||||||
|
```text
|
||||||
|
submitEncodedField
|
||||||
|
setEncodedColorBinding
|
||||||
|
setEncodedParticleBinding
|
||||||
|
setEncodedForecastTime
|
||||||
|
setEncodedForecastPlaybackState
|
||||||
|
setEncodedForecastPlaybackSpeed
|
||||||
|
clearEncodedField
|
||||||
|
clearAllEncodedFields
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6.1 submitEncodedField
|
||||||
|
|
||||||
|
输入语义与当前 Android/Core 保持一致:
|
||||||
|
|
||||||
|
```text
|
||||||
|
fieldKey
|
||||||
|
fieldID
|
||||||
|
generation
|
||||||
|
dataVersion
|
||||||
|
contractJson
|
||||||
|
NSData * qbin
|
||||||
|
```
|
||||||
|
|
||||||
|
必须继续复用现有:
|
||||||
|
|
||||||
|
```text
|
||||||
|
parseResolvedFieldProfileContractJson(...)
|
||||||
|
RasterArraySource::submitEncodedField(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
禁止在 ObjC facade 中重新实现 QBIN decoder。
|
||||||
|
|
||||||
|
必须继续遵守:
|
||||||
|
|
||||||
|
- `generation > 0`
|
||||||
|
- `dataVersion >= 0`
|
||||||
|
- ID UTF-8 边界与当前 V4 contract 一致
|
||||||
|
- contract JSON 有明确大小上限
|
||||||
|
- QBIN transport 有明确上限
|
||||||
|
- 空 payload 拒绝
|
||||||
|
- invalid contract fail-closed
|
||||||
|
|
||||||
|
建议继续保持当前 64 MiB payload 上限与 256 KiB contract 上限;若 Core 当前权威 guard 已定义同一值,应优先复用,不复制新的魔法数字。
|
||||||
|
|
||||||
|
## 6.2 结果映射
|
||||||
|
|
||||||
|
必须提供稳定 ObjC enum / typed result,能够无损表达当前 Core:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ACCEPTED_INSERTED
|
||||||
|
ACCEPTED_REPLACED_REVISION
|
||||||
|
DUPLICATE
|
||||||
|
REJECTED_DECODE
|
||||||
|
REJECTED_CONTRACT
|
||||||
|
REJECTED_INVALID_FIELD_KEY
|
||||||
|
REJECTED_IDENTITY_CONFLICT
|
||||||
|
REJECTED_STALE_REVISION
|
||||||
|
REJECTED_INCOMPATIBLE
|
||||||
|
REJECTED_CAPACITY
|
||||||
|
```
|
||||||
|
|
||||||
|
“source missing / runtime disposed / style not ready”属于 facade/lifecycle 错误,不得伪装成某个 Core submit result。
|
||||||
|
|
||||||
|
## 6.3 Binding
|
||||||
|
|
||||||
|
Color Binding 必须保持:
|
||||||
|
|
||||||
|
```text
|
||||||
|
fieldKey
|
||||||
|
semanticID
|
||||||
|
groupID
|
||||||
|
expectedValueClass
|
||||||
|
expectedUnitID
|
||||||
|
```
|
||||||
|
|
||||||
|
支持:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SCALAR
|
||||||
|
VELOCITY_COMPONENT
|
||||||
|
DIRECTION_ANGLE
|
||||||
|
```
|
||||||
|
|
||||||
|
Particle Binding 必须保持:
|
||||||
|
|
||||||
|
```text
|
||||||
|
VECTOR_COMPONENTS
|
||||||
|
DIRECTION_ANGLE
|
||||||
|
```
|
||||||
|
|
||||||
|
inactive selector 仍必须严格为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
semanticID = 0
|
||||||
|
groupID = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
不得按“是否为空”猜模式。
|
||||||
|
|
||||||
|
## 6.4 Forecast
|
||||||
|
|
||||||
|
保留 Core-owned:
|
||||||
|
|
||||||
|
```text
|
||||||
|
requested forecast time
|
||||||
|
play / pause
|
||||||
|
playback speed
|
||||||
|
T0 / T1 / mix
|
||||||
|
boundary reuse
|
||||||
|
```
|
||||||
|
|
||||||
|
ObjC runtime 只转发命令。
|
||||||
|
|
||||||
|
禁止复制 Forecast Clock。
|
||||||
|
|
||||||
|
## 6.5 Clear
|
||||||
|
|
||||||
|
实现:
|
||||||
|
|
||||||
|
```text
|
||||||
|
clearEncodedField(fieldKey)
|
||||||
|
clearAllEncodedFields()
|
||||||
|
```
|
||||||
|
|
||||||
|
Clear 后必须触发必要 rerender / resource release 路径,但不得增加 STEP081 临时 readback。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7. Renderer / Style 边界
|
||||||
|
|
||||||
|
本任务不得改变:
|
||||||
|
|
||||||
|
- QBIN V2 格式
|
||||||
|
- FieldProfile contract
|
||||||
|
- BindingProjection
|
||||||
|
- V4 materialization
|
||||||
|
- T0/T1 interpolation
|
||||||
|
- Metal particle physics
|
||||||
|
- 1 px current-endpoint Metal coverage
|
||||||
|
- WeatherColor/RasterParticle shader 语义
|
||||||
|
|
||||||
|
如果 facade 接入暴露出 Core 缺陷:
|
||||||
|
|
||||||
|
- 先停止扩大修改;
|
||||||
|
- 在完成报告中列出 blocker;
|
||||||
|
- 不以“顺便重构 renderer”解决。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8. Public Header / XCFramework 接线
|
||||||
|
|
||||||
|
必须把新 public API 正式加入 iOS framework 导出链。
|
||||||
|
|
||||||
|
至少检查并按真实结构更新:
|
||||||
|
|
||||||
|
```text
|
||||||
|
platform/ios/bazel/files.bzl
|
||||||
|
platform/ios/src/Mapbox.h
|
||||||
|
platform/ios/BUILD.bazel
|
||||||
|
```
|
||||||
|
|
||||||
|
若实现文件属于现有 source list 自动包含机制,按真实情况处理,不得重复注册。
|
||||||
|
|
||||||
|
最终产出的:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MapLibre.dynamic.xcframework
|
||||||
|
```
|
||||||
|
|
||||||
|
必须包含新 public header。
|
||||||
|
|
||||||
|
同时确认:
|
||||||
|
|
||||||
|
- `MLNMapView_Private.h` 仍然不是 public header;
|
||||||
|
- 没有把任何 Core header 整包暴露给 Consumer;
|
||||||
|
- 新 public header 可以单独被 Objective-C Consumer import;
|
||||||
|
- public symbol 存在于 framework binary。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 9. Distribution-ready 基线
|
||||||
|
|
||||||
|
当前 MLRN 11.3.6 iOS 通过 SwiftPM distribution 消费 MapLibre Native。
|
||||||
|
|
||||||
|
本 Step **不创建远端 Git 仓库,不擅自打 release tag,不发布公网 Swift Package**。
|
||||||
|
|
||||||
|
但必须在仓库中建立一个最小、可审计的 iOS Particle binary distribution 说明/模板,建议放在:
|
||||||
|
|
||||||
|
```text
|
||||||
|
release/maplibre-particle/ios/
|
||||||
|
```
|
||||||
|
|
||||||
|
至少包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
README.md
|
||||||
|
Package.swift.template
|
||||||
|
release-manifest.json
|
||||||
|
```
|
||||||
|
|
||||||
|
或与现有 release 目录规范等价的结构。
|
||||||
|
|
||||||
|
`Package.swift.template` 的目标是后续独立 distribution repo 可以直接使用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
binaryTarget(name: "MapLibre", url: "...MapLibre.dynamic.xcframework.zip", checksum: "...")
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- product/module 名必须保持 `MapLibre`,以满足 MLRN import;
|
||||||
|
- 不允许再生成第二个不同 module 名并要求 MLRN 同时链接两份 MapLibre;
|
||||||
|
- URL 可以先用明确占位符;
|
||||||
|
- checksum 必须在真实 zip 生成后记录真实值;
|
||||||
|
- 若当前 Step 尚不能生成正式 zip,则 manifest 明确标记 `NOT_BUILT`,禁止伪造 hash。
|
||||||
|
|
||||||
|
建议 external distribution version 独立采用语义版本,例如:
|
||||||
|
|
||||||
|
```text
|
||||||
|
6.28.0-particle.1
|
||||||
|
```
|
||||||
|
|
||||||
|
但不要直接修改 `platform/ios/VERSION=6.28.0`,除非通过构建系统审查确认不会破坏 upstream-compatible framework version。
|
||||||
|
|
||||||
|
本 Step 的核心是建立“MapLibre module 不变 + 自定义 binary distribution version 独立管理”的边界。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 10. MLRN 11.3.6 后续兼容性记录
|
||||||
|
|
||||||
|
STEP-082 不写 RN module,但设计文档必须记录下一 Step 已确认的 iOS 视图链:
|
||||||
|
|
||||||
|
```text
|
||||||
|
reactTag
|
||||||
|
-> React View Registry
|
||||||
|
-> Fabric ComponentView
|
||||||
|
-> contentView
|
||||||
|
-> MLNMapView
|
||||||
|
-> public MLNParticleFieldRuntime
|
||||||
|
```
|
||||||
|
|
||||||
|
同时记录:
|
||||||
|
|
||||||
|
- MLRN 11.3.6 是当前 peer dependency;
|
||||||
|
- 后续 STEP-083 不建立第二套 Map registry;
|
||||||
|
- 不依赖 MLRN private ObjC header 作为长期 API;
|
||||||
|
- 后续通过 `$MLRN_SPM_SPEC` 让 MLRN 与 Particle Bridge 使用同一份自定义 `MapLibre` binary。
|
||||||
|
|
||||||
|
不要在本 Step 修改:
|
||||||
|
|
||||||
|
```text
|
||||||
|
packages/maplibre-particle-rn/
|
||||||
|
integration/react-native/
|
||||||
|
NavSea
|
||||||
|
```
|
||||||
|
|
||||||
|
除非仅为文档引用且任务确实需要;默认都不改。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 11. 测试与验证
|
||||||
|
|
||||||
|
## 11.1 必做静态检查
|
||||||
|
|
||||||
|
必须执行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
并检查:
|
||||||
|
|
||||||
|
1. public header 没有 `mbgl::`;
|
||||||
|
2. public header 没有 STL 类型;
|
||||||
|
3. `MLNMapView_Private.h` 未进入 public header list;
|
||||||
|
4. Core-only layer/source wrapper 没有被新增;
|
||||||
|
5. Android/RN 文件没有变化;
|
||||||
|
6. STEP-081 临时诊断代码没有恢复。
|
||||||
|
|
||||||
|
## 11.2 Objective-C / Objective-C++ 编译
|
||||||
|
|
||||||
|
如果执行环境是 macOS:
|
||||||
|
|
||||||
|
- 使用当前仓库真实 iOS build 工具链;
|
||||||
|
- 编译输出遵循全局 `$build-output-throttle`;
|
||||||
|
- 构建包含新 facade 的 iPhoneOS arm64 或正式 XCFramework target;
|
||||||
|
- 不需要重新做 STEP-081 真机视觉 smoke。
|
||||||
|
|
||||||
|
如果执行环境不是 macOS:
|
||||||
|
|
||||||
|
- 不得伪造 iOS build PASS;
|
||||||
|
- 完成源码/构建图静态接线;
|
||||||
|
- 完成报告标记 `WAITING_MAC_XCFRAMEWORK_VALIDATION`。
|
||||||
|
|
||||||
|
## 11.3 XCFramework 验证
|
||||||
|
|
||||||
|
如果成功生成 XCFramework,必须验证:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Headers 中存在新 public header
|
||||||
|
Mapbox.h 可导入该 API
|
||||||
|
framework binary 中存在对应 public ObjC symbol
|
||||||
|
MLNMapView_Private.h 不在导出 Headers
|
||||||
|
```
|
||||||
|
|
||||||
|
并记录 zip:
|
||||||
|
|
||||||
|
```text
|
||||||
|
文件名
|
||||||
|
size
|
||||||
|
SHA-256
|
||||||
|
```
|
||||||
|
|
||||||
|
可将临时大产物保留在 build/release 临时目录,但是否提交大二进制必须遵守仓库现有规则;默认不要把 XCFramework zip 直接提交进 Git。
|
||||||
|
|
||||||
|
## 11.4 本 Step 不做
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
- Android Gradle
|
||||||
|
- APK
|
||||||
|
- ADB
|
||||||
|
- Android 设备
|
||||||
|
- STEP-081 iPad 视觉复测
|
||||||
|
- RN iOS build
|
||||||
|
- NavSea build
|
||||||
|
- provider/network 测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 12. 文档输出
|
||||||
|
|
||||||
|
所有文档使用中文。
|
||||||
|
|
||||||
|
必须新增/更新:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/30_设计/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行设计_v1.md
|
||||||
|
docs/50_Step记录/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_v1.md
|
||||||
|
粒子开发记录.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Codex 完成报告必须固定生成到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/报告/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex完成报告_v1.md
|
||||||
|
```
|
||||||
|
|
||||||
|
若同名报告已存在,递增 v2/v3,不覆盖旧报告。
|
||||||
|
|
||||||
|
完成报告至少写明:
|
||||||
|
|
||||||
|
- 起始 HEAD
|
||||||
|
- 结束 HEAD
|
||||||
|
- 修改文件
|
||||||
|
- 新 public API
|
||||||
|
- 生命周期模型
|
||||||
|
- V4 API 覆盖
|
||||||
|
- public/private header 检查
|
||||||
|
- XCFramework build 结果
|
||||||
|
- artifact/hash(若有)
|
||||||
|
- 未完成/阻塞
|
||||||
|
- Git commit hash
|
||||||
|
- commit message
|
||||||
|
- push result
|
||||||
|
- remote branch
|
||||||
|
- Final HEAD
|
||||||
|
- 无关 dirty 未触碰确认
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 13. Git 边界
|
||||||
|
|
||||||
|
只提交本 STEP 相关修改。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
```text
|
||||||
|
git add -A
|
||||||
|
git add .
|
||||||
|
git reset --hard
|
||||||
|
git checkout -- <用户无关文件>
|
||||||
|
git clean
|
||||||
|
```
|
||||||
|
|
||||||
|
必须显式按文件/目录暂存。
|
||||||
|
|
||||||
|
提交前再次检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status --short
|
||||||
|
git diff --check
|
||||||
|
git diff --cached --stat
|
||||||
|
git diff --cached
|
||||||
|
```
|
||||||
|
|
||||||
|
建议实现提交 message:
|
||||||
|
|
||||||
|
```text
|
||||||
|
feat: add ios particle field runtime facade
|
||||||
|
```
|
||||||
|
|
||||||
|
若文档收尾需要第二个 scoped commit,可使用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs: record STEP-082 ios runtime distribution baseline
|
||||||
|
```
|
||||||
|
|
||||||
|
提交后:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push origin particle/main
|
||||||
|
```
|
||||||
|
|
||||||
|
若 push 失败,不得宣称成功。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 14. STEP-082 关闭门槛
|
||||||
|
|
||||||
|
只有同时满足以下条件,才可判定本 Step `CLOSED / PASS`:
|
||||||
|
|
||||||
|
- [ ] public iOS FieldRuntime facade 已实现;
|
||||||
|
- [ ] MapView-owned lifecycle 已实现;
|
||||||
|
- [ ] V4 submit/binding/forecast/clear 已接通现有 Core;
|
||||||
|
- [ ] public API 无 Core/STL 类型泄漏;
|
||||||
|
- [ ] style reload / source missing fail-closed;
|
||||||
|
- [ ] `MLNMapView_Private.h` 仍为 private;
|
||||||
|
- [ ] XCFramework public header 接线完成;
|
||||||
|
- [ ] macOS 环境下 XCFramework/arm64 compile 已通过;
|
||||||
|
- [ ] 若产出 zip,SHA-256 已记录;
|
||||||
|
- [ ] distribution-ready 模板/manifest 已建立;
|
||||||
|
- [ ] 没有修改 RN/Android/NavSea;
|
||||||
|
- [ ] 没有重新验证 STEP-081;
|
||||||
|
- [ ] 3 处用户无关 dirty 未触碰;
|
||||||
|
- [ ] `git diff --check` PASS;
|
||||||
|
- [ ] 中文 Step 记录与完成报告已更新;
|
||||||
|
- [ ] scoped commit 已完成;
|
||||||
|
- [ ] push `origin/particle/main` 成功;
|
||||||
|
- [ ] Final HEAD 已记录。
|
||||||
|
|
||||||
|
如果 macOS/XCFramework 构建条件不可用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IMPLEMENTED / WAITING_MAC_XCFRAMEWORK_VALIDATION
|
||||||
|
```
|
||||||
|
|
||||||
|
不得写 `CLOSED / PASS`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 15. 下一 Step 边界
|
||||||
|
|
||||||
|
STEP-082 通过后,下一任务才是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
STEP-083
|
||||||
|
iOS RN TurboModule + Fabric reactTag/MLNMapView Bridge + Custom MapLibre SwiftPM Consumer
|
||||||
|
```
|
||||||
|
|
||||||
|
STEP-083 预计负责:
|
||||||
|
|
||||||
|
```text
|
||||||
|
packages/maplibre-particle-rn/ios
|
||||||
|
Podspec / codegen
|
||||||
|
React View Registry
|
||||||
|
contentView -> MLNMapView
|
||||||
|
本地 QBIN file:// -> NSData
|
||||||
|
调用 STEP-082 public facade
|
||||||
|
$MLRN_SPM_SPEC 指向自定义 MapLibre binary distribution
|
||||||
|
RN Test Host iOS
|
||||||
|
V4 T0 / MID / T1 / Clear
|
||||||
|
```
|
||||||
|
|
||||||
|
本 STEP 不提前进入该范围。
|
||||||
@@ -0,0 +1,609 @@
|
|||||||
|
# STEP-083 iOS RN 统一 FieldRuntime / TurboModule / Fabric 与 XCFramework 消费 Codex 任务 v1
|
||||||
|
|
||||||
|
## 0. 任务定位
|
||||||
|
|
||||||
|
本任务是 STEP-082 之后的一个完整实现批次,不再拆成多个很小的 Step。
|
||||||
|
|
||||||
|
目标是让当前已经在 Android 使用的同一套 React Native / JS FieldRuntime 公共 API,在 iOS 上获得真实 native 实现,并通过 React Native Fabric 的 reactTag 找到当前 MLRN MapView,最终调用 STEP-082 已冻结并验证的 iOS Native FieldRuntime / MapLibre Core。
|
||||||
|
|
||||||
|
完成后应形成:
|
||||||
|
|
||||||
|
JS/TS FieldRuntime
|
||||||
|
→ 同一 NativeWeatherRuntimeBridge TurboModule ABI
|
||||||
|
→ iOS ObjC++ TurboModule
|
||||||
|
→ 当前 Fabric / MLRN MapView
|
||||||
|
→ public MLNMapView / MLNParticleFieldRuntime
|
||||||
|
→ Shared Core / Renderer / Metal
|
||||||
|
|
||||||
|
不得建立 iOS 第二套 JS API、第二套 Map registry、第二套 QBIN/Field contract。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 正式跨项目依据
|
||||||
|
|
||||||
|
开始前必须以以下正式约束为准,不以聊天记忆替代:
|
||||||
|
|
||||||
|
- Integration repo HEAD:`39ad8415fbdb76c1822460bb09f3a3f35fe141e0`
|
||||||
|
- Contract:`C-001`
|
||||||
|
- `contracts/PARTICLE_NAVSEA_BOUNDARY.md`
|
||||||
|
- `rfc/RFC-001_RN公共API与接入基线.md`
|
||||||
|
- `coordination/TEAM_MODEL.md`
|
||||||
|
- `WORKFLOW.md`
|
||||||
|
|
||||||
|
RFC-001 已接受的关键事实:
|
||||||
|
|
||||||
|
1. 当前公共 RN 包为 `@navsea/maplibre-particle-rn@13.4.1-particle.5`。
|
||||||
|
2. Map / Camera 继续由 `@maplibre/maplibre-react-native@11.3.6` 提供,Particle 包不得转导出或复制 MapView。
|
||||||
|
3. Particle 是纯 Field Renderer 控制面,不负责 provider、HTTP、缓存、Route/ETA 或 NavSea 业务。
|
||||||
|
4. Android 当前已有 RN bridge + Native + QBIN V4;iOS RN 在本任务前未实现。
|
||||||
|
5. Android / iOS 必须共享同一 Shared Core 与同一公共 RN 语义;不得平台分叉。
|
||||||
|
|
||||||
|
Integration 的 `PARTICLE_IOS_STATUS.md` / compatibility 在本任务创建时仍保留 STEP-082 验证前的旧状态,但 Particle 仓库实际 STEP-082 已于当前 HEAD 正式 CLOSED / PASS。不得因为 Integration 状态文件滞后而重新打开 STEP-082。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 起始 Git 基线
|
||||||
|
|
||||||
|
仓库:
|
||||||
|
|
||||||
|
`/Users/tei/dev/MapLibreParticleLab`
|
||||||
|
|
||||||
|
分支:
|
||||||
|
|
||||||
|
`particle/main`
|
||||||
|
|
||||||
|
起始 HEAD / origin:
|
||||||
|
|
||||||
|
`44af5e68b8569d90c26e37c2a5ae5493a5b485eb`
|
||||||
|
|
||||||
|
STEP-082 关键提交:
|
||||||
|
|
||||||
|
- implementation blocker close:
|
||||||
|
`136ed726cf749c24b3673c046581a38ef48be390`
|
||||||
|
- STEP-082 final closeout:
|
||||||
|
`44af5e68b8569d90c26e37c2a5ae5493a5b485eb`
|
||||||
|
|
||||||
|
STEP-082 已验证发行产物:
|
||||||
|
|
||||||
|
- `bazel-bin/platform/ios/MapLibre.dynamic.xcframework.zip`
|
||||||
|
- distributionVersion:`6.28.0-particle.1`
|
||||||
|
- module:`MapLibre`
|
||||||
|
- size:`24639200` bytes
|
||||||
|
- SHA-256:
|
||||||
|
`64faa174c5c0dc0a2da3e289f0beaf2dedf7298665e3345f39836729b668e239`
|
||||||
|
- device:arm64
|
||||||
|
- simulator:arm64 + x86_64
|
||||||
|
- public `MLNParticleFieldRuntime.h`:两个 slice 均存在
|
||||||
|
- `Mapbox.h`:两个 slice 均导入该 header
|
||||||
|
- `MLNMapView_Private.h`:public export count = 0
|
||||||
|
- ObjC class symbols:device/simulator 均存在
|
||||||
|
|
||||||
|
必须先阅读:
|
||||||
|
|
||||||
|
- `docs/50_Step记录/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_v1.md`
|
||||||
|
- `docs/报告/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex完成报告_v2.md`
|
||||||
|
- `release/maplibre-particle/ios/release-manifest.json`
|
||||||
|
- `platform/ios/src/MLNParticleFieldRuntime.h`
|
||||||
|
- `platform/ios/src/MLNParticleFieldRuntime.mm`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 当前 RN 公共 API 是冻结基线
|
||||||
|
|
||||||
|
以下文件是本任务必须兼容的公共面:
|
||||||
|
|
||||||
|
- `packages/maplibre-particle-rn/src/NativeWeatherRuntimeBridge.ts`
|
||||||
|
- `packages/maplibre-particle-rn/src/FieldRuntime.ts`
|
||||||
|
- `packages/maplibre-particle-rn/src/types.ts`
|
||||||
|
- `packages/maplibre-particle-rn/src/EncodedFieldGuards.ts`
|
||||||
|
|
||||||
|
当前 Android reference implementation:
|
||||||
|
|
||||||
|
- `packages/maplibre-particle-rn/android/src/main/java/org/navsea/maplibre/particle/weather/WeatherRuntimeBridgeModule.kt`
|
||||||
|
- `WeatherRuntimeBridgePackage.kt`
|
||||||
|
- 当前 JNI / WeatherRuntime Android 实现与 guard/error 语义
|
||||||
|
|
||||||
|
### 3.1 硬性一致性要求
|
||||||
|
|
||||||
|
本任务原则上不得改变:
|
||||||
|
|
||||||
|
- TurboModule 名称:`WeatherRuntimeBridge`
|
||||||
|
- `NativeWeatherRuntimeBridge.ts` 方法名
|
||||||
|
- 参数顺序与 primitive ABI
|
||||||
|
- TypeScript public `FieldRuntime` 方法名
|
||||||
|
- `types.ts` public union / enum 字符串
|
||||||
|
- V4 submit result 字符串
|
||||||
|
- V3 submit result 字符串
|
||||||
|
- `CommandResult = ACCEPTED | REJECTED`
|
||||||
|
- JS 层 encoded guard 语义
|
||||||
|
- V4 contract JSON field names
|
||||||
|
- inactive selector `(0,0)` ABI
|
||||||
|
- `reactTag` 作为 MapView 入口的调用方式
|
||||||
|
|
||||||
|
不得让 NavSea / Test Host 写:
|
||||||
|
|
||||||
|
`Platform.OS === "ios" ? iOS专用API : AndroidAPI`
|
||||||
|
|
||||||
|
也不得新增第二套 iOS JS module。
|
||||||
|
|
||||||
|
### 3.2 JS 文案允许修正
|
||||||
|
|
||||||
|
`FieldRuntime.ts` 当前 `nativeBridge()` 的错误文本仍写死 “requires the Android WeatherRuntimeBridge native module”。
|
||||||
|
|
||||||
|
iOS 模块完成后必须改成平台中性的错误文本,但不得改变 API 类型或成功路径。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 第一阶段:真实代码调查,禁止凭猜测实现
|
||||||
|
|
||||||
|
在写 iOS bridge 前必须完成一次短而完整的只读调查,并在 v1 完成报告中记录结果。
|
||||||
|
|
||||||
|
### 4.1 调查当前 MLRN 11.3.6
|
||||||
|
|
||||||
|
必须取得并检查 `@maplibre/maplibre-react-native@11.3.6` 的真实 iOS source / podspec / codegen 配置。
|
||||||
|
|
||||||
|
可通过当前 Test Host 安装依赖、npm pack 或其他不会改变产品版本锁的方式取得源码。
|
||||||
|
|
||||||
|
必须确认:
|
||||||
|
|
||||||
|
1. Fabric 下 MapView ComponentView 的实际 ObjC/ObjC++ 类名。
|
||||||
|
2. `reactTag` 在 RN 0.85.2 / Fabric 中如何从当前 surface/UIManager 安全 resolve ComponentView。
|
||||||
|
3. MLRN ComponentView 内实际持有/暴露的 content view 类型。
|
||||||
|
4. 如何从该 content view 获得真正的 public `MLNMapView`。
|
||||||
|
5. 哪些接口是 MLRN public/稳定 seam,哪些是私有实现。
|
||||||
|
6. MLRN 11.3.6 的 MapLibre iOS binary dependency 方式。
|
||||||
|
7. 当前版本是否支持 `$MLRN_SPM_SPEC` 或等价官方/仓库已有 override;必须以真实 podspec/source 为准,不得凭记忆发明变量。
|
||||||
|
|
||||||
|
### 4.2 调查 RN 0.85.2 TurboModule iOS codegen
|
||||||
|
|
||||||
|
必须检查当前 RN 0.85.2 codegen 生成的 iOS Spec / base class pattern,确认:
|
||||||
|
|
||||||
|
- ObjC++ module class声明/继承方式;
|
||||||
|
- Promise resolve/reject ABI;
|
||||||
|
- module provider / codegen registration;
|
||||||
|
- 新架构 Fabric / TurboModule 的实际编译入口;
|
||||||
|
- package podspec 需要包含哪些 generated files / dependency。
|
||||||
|
|
||||||
|
### 4.3 做一张 parity matrix
|
||||||
|
|
||||||
|
在完成报告中列出当前 `NativeWeatherRuntimeBridge.ts` 每一个方法:
|
||||||
|
|
||||||
|
- Android 当前实现
|
||||||
|
- iOS 可映射到哪个 public Native/Core seam
|
||||||
|
- 是否需要扩展 `MLNParticleFieldRuntime`
|
||||||
|
- 最终 parity 状态
|
||||||
|
|
||||||
|
不得只实现 Wind V4 后声称“接口一致”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 第二阶段:iOS TurboModule 与 Fabric reactTag → MLNMapView
|
||||||
|
|
||||||
|
### 5.1 新增 iOS package 实现
|
||||||
|
|
||||||
|
在 `packages/maplibre-particle-rn/` 中增加正式 iOS native implementation,并补齐 RN package iOS autolinking 所需文件。
|
||||||
|
|
||||||
|
预期包括但不限于:
|
||||||
|
|
||||||
|
- iOS ObjC++ TurboModule implementation
|
||||||
|
- 必要的 package podspec / codegen 接线
|
||||||
|
- `package.json files` 纳入 iOS / podspec
|
||||||
|
- `react-native.config.js` 正确支持 iOS(若当前 RN autolinking 实际需要)
|
||||||
|
- README / CHANGELOG 更新
|
||||||
|
|
||||||
|
实际文件名必须遵循当前 RN 0.85.2 codegen 生成基类和模块注册要求,不得照旧版 RN 模板硬套。
|
||||||
|
|
||||||
|
### 5.2 reactTag 规则
|
||||||
|
|
||||||
|
每一个需要 Map 的 native command 都必须:
|
||||||
|
|
||||||
|
1. 验证 reactTag 为正整数;
|
||||||
|
2. 在 Main/UI thread 执行 view lookup;
|
||||||
|
3. 从当前 Fabric surface/UIManager 重新 resolve reactTag;
|
||||||
|
4. 确认它确实是当前 MLRN MapView ComponentView/content view;
|
||||||
|
5. 获得当前真实 `MLNMapView`;
|
||||||
|
6. 再调用 MapView-owned Particle runtime。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
- 缓存 reactTag → map 指针;
|
||||||
|
- 建立全局/静态 Map registry;
|
||||||
|
- 缓存跨 style / surface 生命周期的 `MLNMapView *`;
|
||||||
|
- 通过 private Core 指针绕过 public MLN facade;
|
||||||
|
- 用 Test App private seam 作为 RN 正式 API。
|
||||||
|
|
||||||
|
### 5.3 Map 生命周期
|
||||||
|
|
||||||
|
`attach` 必须对应 Android 现有语义:
|
||||||
|
|
||||||
|
- 当前 Map 存在;
|
||||||
|
- style ready;
|
||||||
|
- 配置要求的 `raster-array` source / `weather-color` / `raster-particle` 已存在并类型正确;
|
||||||
|
- attach/re-attach 的 replace/dispose 语义稳定;
|
||||||
|
- runtime 由 MapView 持有;
|
||||||
|
- bridge 不持有第二份 runtime owner。
|
||||||
|
|
||||||
|
`detach`:
|
||||||
|
|
||||||
|
- 对当前 MapView 的 runtime 执行 dispose;
|
||||||
|
- 不销毁 MapView;
|
||||||
|
- 不缓存 stale runtime。
|
||||||
|
|
||||||
|
后续每条 command 都必须针对当前 MapView/current runtime执行,style reload 后按 Native facade fail-closed。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 第三阶段:Android ↔ iOS 公共行为完整对齐
|
||||||
|
|
||||||
|
### 6.1 V4 必须完整实现
|
||||||
|
|
||||||
|
至少包括:
|
||||||
|
|
||||||
|
- `submitEncodedField`
|
||||||
|
- `setEncodedColorBinding`
|
||||||
|
- `setEncodedParticleBinding`
|
||||||
|
- `setEncodedForecastTime`
|
||||||
|
- `setEncodedForecastPlaybackState`
|
||||||
|
- `setEncodedForecastPlaybackSpeed`
|
||||||
|
- `clearEncodedField`
|
||||||
|
- `clearAllEncodedFields`
|
||||||
|
|
||||||
|
QBIN 必须从本地绝对 path / `file://` URI读取为 bytes,再交给 `MLNParticleFieldRuntime`;JS 不解码 QBIN。
|
||||||
|
|
||||||
|
文件 guard 必须与 Android 当前 contract 等价:
|
||||||
|
|
||||||
|
- local file only;
|
||||||
|
- readable regular file;
|
||||||
|
- 1..64 MiB;
|
||||||
|
- read 时防文件长度变化;
|
||||||
|
- contract JSON 上限与 encoded ID UTF-8 byte guard保持一致;
|
||||||
|
- 不使用 Base64 / JS number[]。
|
||||||
|
|
||||||
|
### 6.2 V3 与当前公共控制面也必须审计并实现 parity
|
||||||
|
|
||||||
|
当前 RN public Spec 不只有 V4。
|
||||||
|
|
||||||
|
必须逐项处理:
|
||||||
|
|
||||||
|
- `submitWindField`
|
||||||
|
- `submitField`
|
||||||
|
- `clearWindFields`
|
||||||
|
- `clearFields`
|
||||||
|
- `getState`
|
||||||
|
- `setFieldValueSource`
|
||||||
|
- `setParticleAdvectionScale`
|
||||||
|
- `setWeatherEnabled`
|
||||||
|
- `setWeatherColorVisible`
|
||||||
|
- `setParticleVisible`
|
||||||
|
- `setTargetFps`
|
||||||
|
- `setForecastTime`
|
||||||
|
- `setPlaybackState`
|
||||||
|
- `setPlaybackSpeed`
|
||||||
|
- `setWeatherColorStops`
|
||||||
|
- `setParticleColor`
|
||||||
|
- `setTrailColor`
|
||||||
|
- `setParticleCount`
|
||||||
|
- `setParticleLineWidth`
|
||||||
|
- `setParticleLength`
|
||||||
|
- `setParticleOpacity`
|
||||||
|
- `setTrailOpacity`
|
||||||
|
- `setTrailFade`
|
||||||
|
- `setWeatherColorOpacity`
|
||||||
|
|
||||||
|
原因:RFC-001 当前 NavSea 事实仍包含 Wind V4 + Wave/Rain/Tide V3;不能靠 iOS-specific JS branch规避。
|
||||||
|
|
||||||
|
### 6.3 如果 082 facade 覆盖不足
|
||||||
|
|
||||||
|
当前 `MLNParticleFieldRuntime` 主要冻结 V4 submit/binding/forecast/clear。
|
||||||
|
|
||||||
|
如 parity audit 证明 V3 / state / visual control 在 iOS 已有 Shared Core 能力但没有 public ObjC facade:
|
||||||
|
|
||||||
|
- 允许在本 STEP-083 内最小扩展 `MLNParticleFieldRuntime.h/.mm`;
|
||||||
|
- 仅做现有 Core 能力的薄 public facade;
|
||||||
|
- Android API/Shared Core contract不改变;
|
||||||
|
- 不把 provider、NavSea、RN 类型写进 Core;
|
||||||
|
- 仍保持每次 command current-style lookup 与 fail-closed;
|
||||||
|
- public header 不泄漏 C++/STL/private Core 类型。
|
||||||
|
|
||||||
|
如某个现有 RN public method 在 iOS 根本没有对应 Shared Core 语义,且实现它必须新增/改变跨平台公共协议:
|
||||||
|
|
||||||
|
**停止扩大该部分并在报告中明确 BLOCKED。不得:**
|
||||||
|
|
||||||
|
- 返回假的 ACCEPTED;
|
||||||
|
- 写 no-op stub;
|
||||||
|
- iOS 永久返回 REJECTED 却仍宣称 parity;
|
||||||
|
- 在 JS 用 Platform.OS 绕过;
|
||||||
|
- 私自创建 iOS-only public protocol。
|
||||||
|
|
||||||
|
需要新的跨平台语义时必须回到 Integration RFC/Contract。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Error / Promise 语义对齐
|
||||||
|
|
||||||
|
以 Android `WeatherRuntimeBridgeModule.kt` 为 reference。
|
||||||
|
|
||||||
|
iOS 必须尽量保持相同 reject code 和触发条件:
|
||||||
|
|
||||||
|
- `INVALID_ARGUMENT`
|
||||||
|
- `MAP_VIEW_NOT_FOUND`
|
||||||
|
- `MAP_NOT_READY`
|
||||||
|
- `STYLE_NOT_READY`
|
||||||
|
- `RUNTIME_DISPOSED`
|
||||||
|
- `SOURCE_MISSING`
|
||||||
|
- `NATIVE_REJECTED`
|
||||||
|
- 以及当前 attach / weather-object readiness 已有的稳定错误码
|
||||||
|
|
||||||
|
Core V4 submit rejection 仍作为 **resolved `EncodedFieldSubmitResult`** 返回,不得错误地全部转成 Promise reject。
|
||||||
|
|
||||||
|
Facade/lifecycle/file/ABI 错误使用 Promise reject。
|
||||||
|
|
||||||
|
同类输入在 Android 与 iOS 的成功/reject类别不得无理由分叉。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. XCFramework / MapLibre 单实例消费
|
||||||
|
|
||||||
|
这是本任务的关键发行门槛之一。
|
||||||
|
|
||||||
|
### 8.1 只能有一个 MapLibre.framework
|
||||||
|
|
||||||
|
MLRN 与 Particle iOS bridge 必须消费 **同一个** STEP-082 custom MapLibre module/product。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
- MLRN 拉官方 MapLibre,而 Particle bridge再静态/动态链接另一份 custom MapLibre;
|
||||||
|
- App 中出现两个 `MapLibre.framework`;
|
||||||
|
- 两份 ObjC class runtime;
|
||||||
|
- 私自 rename module 绕开 duplicate symbol。
|
||||||
|
|
||||||
|
### 8.2 使用真实 MLRN 11.3.6 支持的 override
|
||||||
|
|
||||||
|
先调查 MLRN 11.3.6 podspec / SPM consumer。
|
||||||
|
|
||||||
|
如果真实支持 `MLRN_SPM_SPEC`,按其实际格式接 STEP-082 custom distribution。
|
||||||
|
|
||||||
|
如果实际版本使用其他机制,按真实机制实现,并在报告记录。
|
||||||
|
|
||||||
|
不能凭聊天记忆写一个未被 MLRN 读取的环境变量。
|
||||||
|
|
||||||
|
### 8.3 本地验证允许使用 STEP-082 artifact
|
||||||
|
|
||||||
|
当前 artifact 尚未远端发布,manifest URL 为 `LOCAL_BUILD`。
|
||||||
|
|
||||||
|
本任务测试阶段允许用本地 `MapLibre.dynamic.xcframework.zip` / 解包后的 XCFramework 做可重复的 local consumer 验证。
|
||||||
|
|
||||||
|
不得伪造远端 URL。
|
||||||
|
|
||||||
|
远端正式发布可以后续单独处理;但本 STEP 必须证明 iOS RN/Test Host 能消费同一 custom MapLibre binary。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. RN package 版本规则
|
||||||
|
|
||||||
|
本任务首先完成实现与验证,不为了“有 iOS 文件”立即发布新 npm。
|
||||||
|
|
||||||
|
当前 `13.4.1-particle.5` 与 Android Native `.5` 是 RFC-001 正式发布基线。
|
||||||
|
|
||||||
|
除非任务过程中发现已有正式 release 流程明确要求同步 bump,否则:
|
||||||
|
|
||||||
|
- 不擅自发布 `.6`;
|
||||||
|
- 不让 RN package version 与 Android Native strict lock失配;
|
||||||
|
- 可以在工作树/Test Host 用 source package验证 iOS。
|
||||||
|
|
||||||
|
最终若需要新跨平台发行版本,在 STEP-083 验证通过后单独记录 release decision。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Test Host:同一 JS 代码验证 Android/iOS
|
||||||
|
|
||||||
|
优先复用:
|
||||||
|
|
||||||
|
`integration/react-native/MapLibreParticleRNTestHost/`
|
||||||
|
|
||||||
|
当前它没有生成 iOS 工程。
|
||||||
|
|
||||||
|
本任务应让它具备 iOS validation 能力,但不要复制第二份业务 App。
|
||||||
|
|
||||||
|
### 10.1 JS 层不得另写 iOS 页面
|
||||||
|
|
||||||
|
同一个 `App.tsx` / `FieldRuntime` 调用路径用于 Android 与 iOS。
|
||||||
|
|
||||||
|
允许平台专属的 build/config/native files,不允许平台专属业务调用。
|
||||||
|
|
||||||
|
### 10.2 iOS 最小验收内容
|
||||||
|
|
||||||
|
至少准备并尽可能实际执行:
|
||||||
|
|
||||||
|
1. dependency install;
|
||||||
|
2. RN codegen;
|
||||||
|
3. TypeScript typecheck;
|
||||||
|
4. iOS project/prebuild/pods 或实际当前工具链对应生成流程;
|
||||||
|
5. simulator 或 device Debug build;
|
||||||
|
6. module link/launch smoke;
|
||||||
|
7. Map reactTag attach;
|
||||||
|
8. V4 Wind Golden T0/T1 submit;
|
||||||
|
9. color/particle binding;
|
||||||
|
10. forecast time midpoint;
|
||||||
|
11. PLAY/PAUSE/speed;
|
||||||
|
12. clear selected / clear all;
|
||||||
|
13. detach/re-attach;
|
||||||
|
14. style/source not ready fail-closed;
|
||||||
|
15. 验证没有 duplicate MapLibre framework/class。
|
||||||
|
|
||||||
|
如真实运行需要用户手工设备操作,Codex必须把代码、测试入口和明确验收步骤准备完整;不得把未运行项目写 PASS。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 允许修改范围
|
||||||
|
|
||||||
|
主要允许:
|
||||||
|
|
||||||
|
- `packages/maplibre-particle-rn/src/`(仅为平台中性错误文本/必要 codegen 修正;不得分叉 public API)
|
||||||
|
- `packages/maplibre-particle-rn/ios/` 新增
|
||||||
|
- `packages/maplibre-particle-rn/*.podspec` / package/autolinking配置
|
||||||
|
- `packages/maplibre-particle-rn/package.json`(仅 package files/codegen/build必要项;默认不 bump version)
|
||||||
|
- `packages/maplibre-particle-rn/react-native.config.js`
|
||||||
|
- `packages/maplibre-particle-rn/README.md`
|
||||||
|
- `packages/maplibre-particle-rn/CHANGELOG.md`
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/` 的 iOS build/config/test入口
|
||||||
|
- 如 parity 必需:`platform/ios/src/MLNParticleFieldRuntime.h/.mm` 的薄 facade 扩展
|
||||||
|
- 必要 iOS Bazel/public header build graph,前提是不改变 Shared Core contract
|
||||||
|
- STEP-083 设计/记录/报告文件
|
||||||
|
|
||||||
|
不得修改:
|
||||||
|
|
||||||
|
- NavSea 仓库
|
||||||
|
- Android public RN API签名
|
||||||
|
- Android Kotlin/JNI行为,除非只是无语义的测试/编译兼容且报告说明;默认不改
|
||||||
|
- QBIN/FieldProfile/BindingProjection contract
|
||||||
|
- Renderer/Metal particle physics
|
||||||
|
- provider/network/cache/Route/ETA
|
||||||
|
- STEP-081/082 已关闭事实
|
||||||
|
- unrelated submodule / user dirty
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 必须保护的现有 dirty / untracked
|
||||||
|
|
||||||
|
以下必须原样保护,不得暂存或提交:
|
||||||
|
|
||||||
|
- `docs/doxygen/doxygen-awesome-css`
|
||||||
|
- `platform/ios/app/MBXAppDelegate.m`
|
||||||
|
- `platform/windows/vendor/vcpkg`
|
||||||
|
|
||||||
|
现有未跟踪任务书也不得删除、改写、提交:
|
||||||
|
|
||||||
|
- `docs/40_任务/STEP-082_XCFramework_Bazel_Codegen阻塞收尾_Codex任务_v1.md`
|
||||||
|
- `docs/40_任务/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex任务_v1.md`
|
||||||
|
|
||||||
|
本 STEP-083 任务书自身默认也不要求提交。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
- `git add -A`
|
||||||
|
- `git add .`
|
||||||
|
- `git reset --hard`
|
||||||
|
- `git clean`
|
||||||
|
|
||||||
|
只允许 scoped stage。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 验证要求
|
||||||
|
|
||||||
|
### 13.1 静态/代码
|
||||||
|
|
||||||
|
至少:
|
||||||
|
|
||||||
|
- package TypeScript typecheck
|
||||||
|
- RN Codegen 成功生成 iOS/Android Spec,且 Android ABI未意外变化
|
||||||
|
- iOS ObjC++ compile / Xcode build能证明 module与 generated spec匹配
|
||||||
|
- podspec lint/解析或等价 CocoaPods 集成检查
|
||||||
|
- `git diff --check`
|
||||||
|
- package tarball 内容检查:必须包含 iOS native files / podspec,不丢现有 Android/src
|
||||||
|
|
||||||
|
编译输出遵循全局 `$build-output-throttle`。
|
||||||
|
|
||||||
|
### 13.2 公共 API parity
|
||||||
|
|
||||||
|
完成报告必须逐项证明:
|
||||||
|
|
||||||
|
- public TS API无 iOS新增/删减
|
||||||
|
- Native Spec无平台分叉
|
||||||
|
- Android既有 API仍可 codegen
|
||||||
|
- iOS module实现同一 Spec
|
||||||
|
- 关键 error code/submit result映射一致
|
||||||
|
- Test Host JS 不需要 Platform.OS分支
|
||||||
|
|
||||||
|
### 13.3 MapLibre binary identity
|
||||||
|
|
||||||
|
必须检查最终 iOS build graph:
|
||||||
|
|
||||||
|
- 只解析一个 MapLibre module/product;
|
||||||
|
- 实际链接的是 STEP-082 custom binary;
|
||||||
|
- `MLNParticleFieldRuntime` symbol可见;
|
||||||
|
- 无 duplicate framework/class warning;
|
||||||
|
- MLRN 与 Particle bridge看到同一 ObjC runtime class。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. 执行策略
|
||||||
|
|
||||||
|
这是复杂任务,可以在一个 STEP 内分“调查 → 实现 → 构建/验证 → 收尾”四个阶段,但不要人为拆成 STEP-083A/B/C/D。
|
||||||
|
|
||||||
|
遇到小型编译/Pod/codegen/headers问题,Codex继续最小修复并验证。
|
||||||
|
|
||||||
|
只有遇到以下情况才停止:
|
||||||
|
|
||||||
|
1. 需要改变当前 RN public API;
|
||||||
|
2. 需要改变 C-001 Shared Core contract;
|
||||||
|
3. 需要新增跨平台 semantic/error/lifecycle协议;
|
||||||
|
4. MLRN 11.3.6 无法在不双链接 MapLibre 的情况下消费 custom binary,需要新的架构决定。
|
||||||
|
|
||||||
|
停止时写明确 blocker,不得用 stub/Platform.OS/第二 registry绕过。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Git 与文档输出
|
||||||
|
|
||||||
|
实质进展后更新:
|
||||||
|
|
||||||
|
- `粒子开发记录.md`
|
||||||
|
- 新建:
|
||||||
|
`docs/50_Step记录/STEP-083_iOS_RN统一FieldRuntime_TurboModule_Fabric与XCFramework消费_v1.md`
|
||||||
|
|
||||||
|
Codex完成报告固定生成:
|
||||||
|
|
||||||
|
`docs/报告/STEP-083_iOS_RN统一FieldRuntime_TurboModule_Fabric与XCFramework消费_Codex完成报告_v1.md`
|
||||||
|
|
||||||
|
报告至少写:
|
||||||
|
|
||||||
|
- 调查结论
|
||||||
|
- parity matrix
|
||||||
|
- MLRN 11.3.6 Fabric lookup真实路径
|
||||||
|
- RN 0.85.2 TurboModule registration真实路径
|
||||||
|
- package/podspec改动
|
||||||
|
- Native facade扩展(如有)
|
||||||
|
- custom MapLibre single-binary消费方式
|
||||||
|
- typecheck/codegen/build/test结果
|
||||||
|
- 未实际运行项
|
||||||
|
- user dirty保护情况
|
||||||
|
- commit hash / message
|
||||||
|
- push result
|
||||||
|
- remote branch
|
||||||
|
- Final HEAD
|
||||||
|
|
||||||
|
在合适检查点做 scoped commit;完成范围才提交,不要带入 unrelated dirty。
|
||||||
|
|
||||||
|
最终 push:
|
||||||
|
|
||||||
|
`origin/particle/main`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. CLOSED / PASS 门槛
|
||||||
|
|
||||||
|
只有全部成立才能标记 STEP-083 `CLOSED / PASS`:
|
||||||
|
|
||||||
|
- iOS native module 实现现有同一 `NativeWeatherRuntimeBridge` Spec;
|
||||||
|
- JS/TS public API 不分叉;
|
||||||
|
- reactTag → 当前 Fabric/MLRN view → public MLNMapView 链真实成立;
|
||||||
|
- bridge 不缓存 Map/Core pointer,不建立第二 registry;
|
||||||
|
- V4 入口完整;
|
||||||
|
- V3 + 当前公共控制面 parity audit 完成,所有方法真实实现或已由正式跨平台决定处理;
|
||||||
|
- iOS package autolinking/codegen可用;
|
||||||
|
- MLRN 与 bridge只消费一个 STEP-082 custom MapLibre;
|
||||||
|
- iOS compile/link成功;
|
||||||
|
- Test Host 至少完成真实 module/link/attach smoke;QBIN/Field功能未实际运行的项目不得伪报 PASS;
|
||||||
|
- `git diff --check` PASS;
|
||||||
|
- 完成报告与 Step记录更新;
|
||||||
|
- scoped commit/push成功;
|
||||||
|
- local HEAD == `origin/particle/main`;
|
||||||
|
- 三处用户 dirty和历史任务书未触碰。
|
||||||
|
|
||||||
|
若代码完整但真实 iOS runtime/device验收尚未完成,应写:
|
||||||
|
|
||||||
|
`IMPLEMENTED / WAITING_IOS_VALIDATION`
|
||||||
|
|
||||||
|
不得提前写 CLOSED/PASS。
|
||||||
198
docs/40_任务/STEP-087_QBIN_V3解码与13.4.1-particle.9升级_Codex任务_v1.md
Normal file
198
docs/40_任务/STEP-087_QBIN_V3解码与13.4.1-particle.9升级_Codex任务_v1.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# STEP-087 QBIN V3 解码与 13.4.1-particle.9 升级 Codex 任务 v1
|
||||||
|
|
||||||
|
## 1. 任务目标
|
||||||
|
|
||||||
|
在 Mac 专用工作树 `/Users/tei/dev/MapLibreParticleLab-qbv3-p9` 上完成 QBIN V3(Magic=`QBV3`, version=3)正式解码支持,并将统一 Native / RN 源码版本升级到 `13.4.1-particle.9`。
|
||||||
|
|
||||||
|
本任务必须以以下设计稿为权威依据:
|
||||||
|
- `docs/30_设计/WeatherV2_QBIN_V3通用天气场二进制格式与React独立Render_Binding设计_v2.2_最终冻结稿.md`
|
||||||
|
|
||||||
|
该设计稿是用户提供原文,必须保留,不得改写、覆盖或删减。实现中如发现规范真实矛盾,只能在完成报告中记录并停止相关 ABI 决策,不得自行修改规范来迁就代码。
|
||||||
|
|
||||||
|
## 2. 当前基线
|
||||||
|
|
||||||
|
执行前必须核对:
|
||||||
|
- 工作树分支:`particle/qbv3-p9`
|
||||||
|
- 基线必须包含 `release/particle-8` 的两笔发布提交,至少到 `77744dd9f`
|
||||||
|
- `platform/android/VERSION` 与 `packages/maplibre-particle-rn/package.json` 当前均应为 `13.4.1-particle.8`
|
||||||
|
- 不得修改其他 Mac 工作树
|
||||||
|
- 主线目标仍为 `origin/particle/main`
|
||||||
|
|
||||||
|
先阅读项目根目录 AGENTS.md(如存在)、docs/00_项目总则、docs/00_项目规范,以及 STEP-073~085 中与 QBIN ingress、FieldProfile、发布和 iOS device-arm64 相关的最新记录。## 3. Phase 0:只读现状调查
|
||||||
|
|
||||||
|
在写代码前完成一次只读调查并在报告中记录结论,至少确认:
|
||||||
|
- `QbinV2Decoder`、`EncodedFieldIngress::decodeAndValidate()`、`DecodedFieldFrame`、`FieldProfileValidator` 的真实接口。
|
||||||
|
- QBV2 source/test 在 Bazel/CMake/平台构建中的注册位置。
|
||||||
|
- RN / Android / iOS 当前 encoded-field 入口是否只是透传 bytes,确认 QBV3 不需要新增 JS/TS 公共业务 API。
|
||||||
|
- `.8` 版本号实际出现在哪些“当前版本”文件中;禁止全仓替换历史文档中的旧版本号。
|
||||||
|
- 现有 Wind / Rain / Wave / Current QBIN Golden Fixture 的来源、尺寸与可复用性。
|
||||||
|
|
||||||
|
结论应坚持:
|
||||||
|
- V2 与 V3 只在 Encoded Field Ingress 前分流。
|
||||||
|
- Decoder 后继续输出同一 `DecodedFieldFrame / Plane Registry`。
|
||||||
|
- Runtime / Binding / Renderer 不感知 QBV2/QBV3。
|
||||||
|
- 不把 Provider、Weather Service、HTTP、Manifest、缓存、React 业务逻辑放进 Native Decoder。
|
||||||
|
|
||||||
|
## 4. Block Geometry 冻结
|
||||||
|
|
||||||
|
v2.2 规范只剩 `BLOCK_WIDTH / BLOCK_HEIGHT` 未冻结,本任务必须在正式 QBV3 Decoder 固化前完成选择。
|
||||||
|
|
||||||
|
使用现有四类真实 Golden 数据:
|
||||||
|
- `integration/react-native/MapLibreParticleRNTestHost/assets/qbin-golden/wind/`
|
||||||
|
- `rain/`
|
||||||
|
- `wave/`
|
||||||
|
- `current/`
|
||||||
|
|
||||||
|
优先复用 QBV2 Decoder 得到 canonical quantized integer grid + validity,再用测试/benchmark 专用 deterministic QBV3 encoder/prototype 比较 `16×16` 与 `32×32`。该 encoder 仅用于测试、fixture 和 benchmark,不得演变成 Weather Provider/生产 Writer。比较必须使用同一 predictor / ZigZag / Rice 规则,至少记录:
|
||||||
|
- encoded bytes
|
||||||
|
- block count / non-empty block count
|
||||||
|
- encode time
|
||||||
|
- entropy decode time
|
||||||
|
- total decode time
|
||||||
|
- CRC time(若可独立测)
|
||||||
|
- peak temporary bytes(若现有工具不能可靠测量,报告中明确说明,不得伪造)
|
||||||
|
|
||||||
|
选择原则:
|
||||||
|
1. 首先保证规范一致和 bit-exact round-trip。
|
||||||
|
2. 压缩率差异很小时,优先更低 decode/encode 开销、更少 block、实现更简单的一项。
|
||||||
|
3. 最终必须只留下一个 production Block Geometry。
|
||||||
|
4. production Decoder 不得同时“猜测”16×16/32×32,也不得根据解码是否成功试两遍。
|
||||||
|
5. 测试代码可以保留两个候选参数用于 benchmark,但正式 QBV3 ABI 常量必须唯一。
|
||||||
|
|
||||||
|
把选择依据写入:
|
||||||
|
- `docs/30_设计/MapLibreParticle_QBIN_V3_Decoder与BlockGeometry实施决策_v1.md`
|
||||||
|
|
||||||
|
该文档是粒子侧实施补充,不改写用户提供的 WeatherV2 v2.2 原文。
|
||||||
|
|
||||||
|
## 5. 正式 QBV3 Decoder
|
||||||
|
|
||||||
|
新增清晰独立的 QBV3 decoder 文件,优先采用:
|
||||||
|
- `include/mbgl/particle/ingress/qbin_v3_decoder.hpp`
|
||||||
|
- `src/mbgl/particle/ingress/qbin_v3_decoder.cpp`
|
||||||
|
|
||||||
|
如现有构建结构要求轻微调整可据实处理,但禁止把 V3 逻辑塞成 `qbin_v2_decoder.cpp` 内的大量 version 分支。实现必须逐项遵守 v2.2 normative 主体,重点包括:
|
||||||
|
- 112-byte Header、32-byte Descriptor、Little Endian。
|
||||||
|
- Magic=`QBV3`、version=3;FRAME 固定 layout/projection/sampleOrder/gridRegistration/tile 字段。
|
||||||
|
- Version 3 limits:planeCount 32、cellCount 2,097,152、file 64 MiB、decoded frame 32 MiB。
|
||||||
|
- Header bounds finite、Web Mercator 范围、binary64 数值零禁止 -0.0。
|
||||||
|
- Numeric dtype 只允许 INT16/UINT16/UINT8/INT8;FLOAT32 与 Numeric BITSET reject。
|
||||||
|
- `semantic==3 ⇔ valueClass==VALIDITY ⇔ dtype==BITSET`,且只允许一个 Shared Validity Plane。
|
||||||
|
- Validity LSB-first;tail unused high bits 必须为 0。
|
||||||
|
- unknown fieldType(包括 0)只作为 opaque hint,不得单独 reject。
|
||||||
|
- Numeric `scale > 0` finite,offset finite;数值为 0 时只接受 binary32 +0.0。
|
||||||
|
- Parser 顺序保持:结构与 budget → CRC → padding/Validity → Rice decode。
|
||||||
|
- CRC-32/ISO-HDLC,CRC field 76..79 计算时视为 0。
|
||||||
|
- all-invalid 合法:Validity 全 0、Numeric byteLength=0、decoded raw 全 0。
|
||||||
|
- zero-length Plane 可以共享 canonical byteOffset;只对真实非零 range 做 overlap 判断。
|
||||||
|
- Numeric Plane byteLength 是各 non-empty block encoded bytes 之和,不含 align16 padding。
|
||||||
|
|
||||||
|
Rice / predictor 必须严格实现:
|
||||||
|
- Predictor 在全 Grid 连续,不在 block 边界 reset。
|
||||||
|
- invalid cell 不消费 Rice symbol,materialize raw=0。
|
||||||
|
- ZigZag 使用无 UB 的 widened fixed-width arithmetic。
|
||||||
|
- Rice bitstream 与 remainder 都按 byte 内 LSB-first。
|
||||||
|
- riceK 允许 0..31;Decoder permissive,不因非最优 k 单独 reject。
|
||||||
|
- unary quotient、MAX_Q、residual、raw reconstruction 全部 checked;非法立即 fail-closed。
|
||||||
|
- block padding high unused bits 必须为 0,symbolCount 必须精确。
|
||||||
|
- Direction 使用 canonical float32(360/N)、N=floor(360/scale+0.5)、raw∈[0,N)。
|
||||||
|
- Direction predictor 必须 normalize;half-circle tie 固定 `-N/2`。## 6. Ingress 与兼容性
|
||||||
|
|
||||||
|
`EncodedFieldIngress::decodeAndValidate()` 必须按 bytes 中的 magic/version 做显式 dispatch:
|
||||||
|
- `QBV2 / version=2 → QbinV2Decoder`
|
||||||
|
- `QBV3 / version=3 → QbinV3Decoder`
|
||||||
|
- 其他组合 fail-closed
|
||||||
|
|
||||||
|
要求:
|
||||||
|
- QBV2 frozen behavior 不得改变。
|
||||||
|
- V3 decode 成功后继续走现有 `FieldProfileValidator`。
|
||||||
|
- Binary-valid 与 Profile-valid 继续分层;V3 Decoder 不得根据 wind/wave/rain/current 名称要求业务 Plane。
|
||||||
|
- V2/V3 decoded Plane 的 ContentIdentity 必须继续使用项目既有 canonical identity 语义,避免 Runtime 因容器版本产生无意义 identity 漂移。
|
||||||
|
- 如需要抽取共享 helper,可做小范围重构,但不得把公共 API 扩大到 Renderer/Binding。
|
||||||
|
|
||||||
|
## 7. 测试与 Fixture
|
||||||
|
|
||||||
|
新增 C++ 定向测试,不新增或运行 Jest。
|
||||||
|
|
||||||
|
至少覆盖:
|
||||||
|
- Wind / Rain / Wave / Current integer + validity bit-exact round-trip。
|
||||||
|
- INT16 / UINT16 / INT8 / UINT8。
|
||||||
|
- all-valid、部分 invalid、all-invalid。
|
||||||
|
- zero-length shared offset。
|
||||||
|
- bad magic/version/endian、reserved、bounds、negative zero、descriptor order/duplicate、packing、CRC。
|
||||||
|
- malformed Rice、unterminated unary、riceK>31、symbolCount 少/多、nonzero block padding、MAX_Q/residual/raw overflow。
|
||||||
|
- non-optimal but otherwise-valid riceK 必须 accept。
|
||||||
|
- fieldType=0 与 unknown fieldType 必须 accept。
|
||||||
|
- Direction 359↔0、N-1↔0、half-circle tie、predictor wrap。
|
||||||
|
- WEB_MERCATOR + NODE sample 公式的定位 Golden Test。
|
||||||
|
|
||||||
|
最终冻结 Geometry 后生成唯一正式 QBV3 Golden Fixture 集,至少覆盖四类场;实验 16/32 双候选 fixture 不得混入 production fixture 目录。## 8. 构建与验证
|
||||||
|
|
||||||
|
本任务在 Mac 上执行 Native/C++ 开发和精确编译验证。
|
||||||
|
|
||||||
|
必须:
|
||||||
|
- 运行与 QBV2/QBV3 decoder 直接相关的 C++ 定向测试。
|
||||||
|
- 运行必要的 Mac 原生精确编译,确认新增 source/header 被真实构建目标消费。
|
||||||
|
- 对 iOS 只验证 iPhone/iPad device arm64 路径;不得恢复 simulator slice。
|
||||||
|
- 如执行 noisy compile/test,遵循当前 Codex 全局输出节流规则。
|
||||||
|
- `git diff --check` 必须 PASS。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
- Android Gradle build
|
||||||
|
- AAR/APK 构建或安装
|
||||||
|
- ADB
|
||||||
|
- 启动 Android App / 清理数据
|
||||||
|
- Android 实机/虚拟机冒烟
|
||||||
|
|
||||||
|
Android 最终构建安装仍由用户手工执行。本任务应在报告中给出最少的用户手工验证入口,但不要代替用户执行。
|
||||||
|
|
||||||
|
## 9. 升级到 13.4.1-particle.9
|
||||||
|
|
||||||
|
以已纳入的 `.8` 为基线,将当前有效版本升级为:
|
||||||
|
`13.4.1-particle.9`
|
||||||
|
|
||||||
|
按 `.8` 的真实发布结构更新所有“当前版本”源文件/配置,包括 Native VERSION、RN package、插件/Android 依赖引用、CHANGELOG/README 中的当前安装示例,以及 release pipeline 需要的当前版本元数据。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
- 全仓替换历史 STEP / 报告中的 `.1~.8`。
|
||||||
|
- 覆盖既有 `.8` artifact、tag 或发布目录。
|
||||||
|
- 未经用户明确要求发布外部 Maven/npm/CDN artifact。
|
||||||
|
- 未经用户明确要求创建正式 release tag。## 10. 文档输出
|
||||||
|
|
||||||
|
除本任务书和用户提供设计稿外,任务必须生成/更新:
|
||||||
|
- `docs/30_设计/MapLibreParticle_QBIN_V3_Decoder与BlockGeometry实施决策_v1.md`
|
||||||
|
- `docs/50_Step记录/STEP-087_QBIN_V3解码与13.4.1-particle.9升级_v1.md`
|
||||||
|
- `docs/报告/STEP-087_QBIN_V3解码与13.4.1-particle.9升级_Codex完成报告_v1.md`
|
||||||
|
|
||||||
|
完成报告必须写清:
|
||||||
|
- 起始 HEAD / 最终 HEAD
|
||||||
|
- 选定的 Block Geometry 与 benchmark 证据
|
||||||
|
- 新增/修改的主要代码文件
|
||||||
|
- QBV2 回归结果
|
||||||
|
- QBV3 测试结果
|
||||||
|
- Mac 原生精确编译结果
|
||||||
|
- 未执行的 Android 手工门槛
|
||||||
|
- 版本号变更位置
|
||||||
|
- 仍存在的风险或未完成项
|
||||||
|
|
||||||
|
如同主题报告已存在,按 v2/v3 递增,不覆盖旧报告。
|
||||||
|
|
||||||
|
## 11. Git 收尾
|
||||||
|
|
||||||
|
完成规定代码、测试、版本和文档后:
|
||||||
|
1. 只提交本任务相关变更。
|
||||||
|
2. 不提交无关 untracked 文件或其他工作树内容。
|
||||||
|
3. commit message 使用清晰英文,例如:`feat: add QBIN V3 decoder for particle.9`。
|
||||||
|
4. 将最终结果 push 到公开远端 `origin/particle/main`。
|
||||||
|
5. push 失败不得声称完成。
|
||||||
|
6. 完成报告必须记录最终 commit hash、commit message、push result、remote branch、Final HEAD。
|
||||||
|
7. 最终再次确认本地 HEAD 与 `origin/particle/main` 一致。
|
||||||
|
|
||||||
|
## 12. 停止条件
|
||||||
|
|
||||||
|
如果实际 benchmark 无法用现有真实 fixture 对 16×16/32×32 做可信选择,或 v2.2 存在导致合法文件无法唯一解释的真实矛盾:
|
||||||
|
- 不得猜测 production Geometry。
|
||||||
|
- 完成可独立验证的代码/调查部分后停止。
|
||||||
|
- 在 `docs/报告/` 生成接口/规范阻塞说明,明确缺什么数据或决定。
|
||||||
|
- 不得把支持两种 production Geometry 的“兼容 decoder”当成完成方案。
|
||||||
|
|
||||||
|
除此之外,应完成实现,不要因为普通工程细节反复向用户确认。
|
||||||
357
docs/40_任务/STEP-088_静态底图缓存PoC与Renderer观测_Codex任务_v1.md
Normal file
357
docs/40_任务/STEP-088_静态底图缓存PoC与Renderer观测_Codex任务_v1.md
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
# STEP-088 静态底图缓存 PoC 与 Renderer 可观测性 Codex 任务 v1
|
||||||
|
|
||||||
|
## 1. 任务目标
|
||||||
|
基于最新 origin/particle/main 实际代码,实现第一阶段 Retained Static Underlay PoC。
|
||||||
|
|
||||||
|
核心目标:
|
||||||
|
- Camera 固定、Style/Tile 稳定、存在 WeatherColor/Particle 时,把动态层之前连续普通 LayerGroup 前缀 render 到 offscreen texture。
|
||||||
|
- 后续 Particle Tick 继续走当前 onUpdate/createRenderTree,但不再重复 draw 静态前缀,而复用 cached underlay texture。
|
||||||
|
- Particle simulation/render 继续按 cadence 正常。
|
||||||
|
- 增加 Renderer 可观测性。
|
||||||
|
- 本任务只执行 STEP-088,不得执行 STEP-089/090。
|
||||||
|
|
||||||
|
## 2. 执行基线
|
||||||
|
直接在主仓库:
|
||||||
|
/Users/tei/dev/MapLibreParticleLab
|
||||||
|
|
||||||
|
目标主线:
|
||||||
|
origin/particle/main
|
||||||
|
|
||||||
|
执行前:
|
||||||
|
1. git fetch origin particle/main。
|
||||||
|
2. 确认本地 HEAD、origin/particle/main、git status。
|
||||||
|
3. 不 reset/clean,不覆盖用户既有未提交修改。
|
||||||
|
4. 如存在无关本地修改,只修改、stage、commit STEP-088 相关文件。
|
||||||
|
5. 以当前实际代码为准,不得凭旧版本猜接口。
|
||||||
|
|
||||||
|
必须阅读:
|
||||||
|
- docs/40_任务/粒子连续渲染静态底图缓存与动画快路径优化规划_v1.md
|
||||||
|
- 本任务书
|
||||||
|
- docs/20_调查/STEP-086_Particle连续渲染唤醒MapLibre整图成本调查_v1.md
|
||||||
|
- docs/00_项目总则/
|
||||||
|
- docs/00_项目规范/
|
||||||
|
- 根目录 AGENTS.md(如存在)
|
||||||
|
|
||||||
|
## 3. 开工前只读确认
|
||||||
|
必须确认并在完成报告记录:
|
||||||
|
- RenderRasterParticleLayer::needsRepaint()
|
||||||
|
- RenderWeatherColorLayer::needsRepaint()
|
||||||
|
- Renderer::render()
|
||||||
|
- RenderOrchestrator::createRenderTree()
|
||||||
|
- RenderOrchestrator::hasTransitions()
|
||||||
|
- renderer_impl.cpp 的 updateLayers/processChanges
|
||||||
|
- LayerGroup tweaker/upload
|
||||||
|
- visitRenderTargets
|
||||||
|
- main clear
|
||||||
|
- opaque/translucent pass
|
||||||
|
- present
|
||||||
|
- RenderTarget / OffscreenTexture
|
||||||
|
- Heatmap/Hillshade offscreen + texture composite 样板
|
||||||
|
|
||||||
|
涉及文件至少:
|
||||||
|
- src/mbgl/renderer/layers/render_particle_validation_layer.cpp
|
||||||
|
- src/mbgl/renderer/layers/render_weather_color_layer.cpp
|
||||||
|
- src/mbgl/renderer/renderer.cpp
|
||||||
|
- src/mbgl/renderer/render_orchestrator.cpp/.hpp
|
||||||
|
- src/mbgl/renderer/renderer_impl.cpp/.hpp
|
||||||
|
- include/mbgl/renderer/render_target.hpp
|
||||||
|
- src/mbgl/renderer/render_target.cpp
|
||||||
|
- src/mbgl/renderer/layers/render_heatmap_layer.cpp
|
||||||
|
- src/mbgl/renderer/layers/render_hillshade_layer.cpp
|
||||||
|
|
||||||
|
## 4. 修改一:retained dynamic boundary
|
||||||
|
重点修改:
|
||||||
|
- src/mbgl/renderer/render_layer.hpp
|
||||||
|
- src/mbgl/renderer/layers/render_weather_color_layer.hpp
|
||||||
|
- src/mbgl/renderer/layers/render_raster_particle_layer.hpp
|
||||||
|
|
||||||
|
增加仅 Renderer 内部使用的虚函数,语义类似:
|
||||||
|
virtual bool startsRetainedDynamicRange() const { return false; }
|
||||||
|
|
||||||
|
WeatherColor 与 RasterParticle override=true。
|
||||||
|
|
||||||
|
规则:
|
||||||
|
- 两者同时存在取最小实际 layerIndex。
|
||||||
|
- only Particle 使用 Particle index。
|
||||||
|
- 无 candidate 时完全走原 Renderer。
|
||||||
|
- 禁止根据 React layer ID/name 字符串硬编码。
|
||||||
|
- 禁止让 NavSea/Host 传 boundary。
|
||||||
|
- 不新增 public API。
|
||||||
|
|
||||||
|
## 5. 修改二:RenderOrchestrator LayerGroup 分段访问
|
||||||
|
重点修改:
|
||||||
|
- src/mbgl/renderer/render_orchestrator.hpp
|
||||||
|
- src/mbgl/renderer/render_orchestrator.cpp
|
||||||
|
|
||||||
|
必要时小改:
|
||||||
|
- include/mbgl/renderer/layer_group.hpp
|
||||||
|
- src/mbgl/renderer/layer_group.cpp
|
||||||
|
|
||||||
|
需要:
|
||||||
|
- 查询 retained dynamic boundary。
|
||||||
|
- 遍历 boundary 之前 groups。
|
||||||
|
- 遍历 boundary 及之后 groups。
|
||||||
|
- forward/reverse 都支持。
|
||||||
|
- callback 能获得完整原始 LayerGroup 序列 ordinal。
|
||||||
|
|
||||||
|
可采用 before/from visitor 或更通用 indexed/range visitor。
|
||||||
|
|
||||||
|
关键:
|
||||||
|
parameters.currentLayer、depthRangeSize、opaque/translucent ordering 必须与完整遍历等价。
|
||||||
|
不得让 dynamic subset 从 0 重新编号导致 depth/transparency 变化。
|
||||||
|
|
||||||
|
## 6. 修改三:Renderer-owned RetainedUnderlayCache
|
||||||
|
优先新增:
|
||||||
|
- src/mbgl/renderer/retained_underlay_cache.hpp
|
||||||
|
- src/mbgl/renderer/retained_underlay_cache.cpp
|
||||||
|
|
||||||
|
并在 CMakeLists.txt 真实 source list 注册。
|
||||||
|
|
||||||
|
至少维护:
|
||||||
|
- offscreen renderable / texture
|
||||||
|
- viewport size
|
||||||
|
- boundary
|
||||||
|
- valid
|
||||||
|
- buildCount
|
||||||
|
- reuseCount
|
||||||
|
- fallbackCount
|
||||||
|
- last invalidation reason
|
||||||
|
|
||||||
|
不要修改 Heatmap/Hillshade RenderTarget 原有语义。
|
||||||
|
|
||||||
|
STEP-088 至少保证:
|
||||||
|
- resource 创建失败 -> full-render fallback
|
||||||
|
- viewport resize -> invalid
|
||||||
|
- boundary change -> invalid
|
||||||
|
- has3D -> fallback
|
||||||
|
- Debug Overdraw 等明显不兼容模式 -> fallback
|
||||||
|
- context 不可用 -> 不复用
|
||||||
|
- Camera fingerprint 连续相同、style loaded、2D、boundary stable 才允许 reuse
|
||||||
|
|
||||||
|
任何不能证明安全的状态都 full render。
|
||||||
|
|
||||||
|
## 7. 修改四:Renderer::Impl 分离 Static Underlay / Dynamic Range
|
||||||
|
重点:
|
||||||
|
- src/mbgl/renderer/renderer_impl.hpp
|
||||||
|
- src/mbgl/renderer/renderer_impl.cpp
|
||||||
|
|
||||||
|
STEP-088 必须保留:
|
||||||
|
- Renderer::render -> createRenderTree
|
||||||
|
- source update/prepare
|
||||||
|
- layer prepare
|
||||||
|
- updateLayers
|
||||||
|
- LayerGroup tweaker/upload
|
||||||
|
|
||||||
|
只修改真正 draw 阶段。
|
||||||
|
|
||||||
|
目标流程:
|
||||||
|
1. existing independent RenderTargets。
|
||||||
|
2. cache invalid 时,boundary 前 LayerGroups render 到 offscreen 并 mark valid。
|
||||||
|
3. main clear。
|
||||||
|
4. composite cached underlay texture。
|
||||||
|
5. boundary 及之后 opaque/translucent 正常 render。
|
||||||
|
6. legacy items 按安全规则。
|
||||||
|
7. debug。
|
||||||
|
8. present。
|
||||||
|
|
||||||
|
Underlay offscreen 优先透明 clear。
|
||||||
|
main framebuffer 保持原 backgroundColor/depth/stencil 语义。
|
||||||
|
必须确认 background layer optimized-as-color 路径。
|
||||||
|
|
||||||
|
## 8. Legacy RenderItems
|
||||||
|
renderer_impl.cpp translucent pass 仍有 legacy layerRenderItems。
|
||||||
|
|
||||||
|
必须确认是否有 boundary 以下 legacy item。
|
||||||
|
- 无法安全按 layerIndex 切分时不纳入 cache。
|
||||||
|
- 可能破坏 ordering 时 fallback full render。
|
||||||
|
- 不得静默改变顺序。
|
||||||
|
|
||||||
|
## 9. 修改五:cached texture compositor
|
||||||
|
不要正式复用 LocationIndicatorTexturedShader。
|
||||||
|
|
||||||
|
建议新增:
|
||||||
|
RetainedUnderlayCompositeShader
|
||||||
|
|
||||||
|
职责:
|
||||||
|
- viewport/fullscreen quad
|
||||||
|
- sample cached RGBA
|
||||||
|
- 正确 premultiplied alpha
|
||||||
|
- 不做 color ramp
|
||||||
|
- 不做 map transform
|
||||||
|
- 不做 Particle 业务
|
||||||
|
|
||||||
|
按当前真实 shader 注册规则检查/修改至少:
|
||||||
|
- include/mbgl/shaders/shader_source.hpp
|
||||||
|
- src/mbgl/shaders/shader_source.cpp
|
||||||
|
- include/mbgl/shaders/shader_defines.hpp
|
||||||
|
- include/mbgl/shaders/shader_manifest.hpp
|
||||||
|
- include/mbgl/shaders/gl/shader_info.hpp
|
||||||
|
- src/mbgl/shaders/gl/shader_info.cpp
|
||||||
|
- src/mbgl/gl/renderer_backend.cpp
|
||||||
|
- src/mbgl/mtl/renderer_backend.cpp
|
||||||
|
- cmake/opengl.cmake
|
||||||
|
- cmake/metal.cmake
|
||||||
|
- 新增 GL/Metal compositor shader 文件
|
||||||
|
|
||||||
|
如当前 shader codegen 要求生成文件,按真实 codegen 走。
|
||||||
|
Android OpenGL 与 iOS Metal 语义都要完整。
|
||||||
|
|
||||||
|
## 10. Renderer Debug Counters
|
||||||
|
修改:
|
||||||
|
- include/mbgl/particle/renderer_debug_counters.hpp
|
||||||
|
|
||||||
|
至少新增:
|
||||||
|
- FullRendererFrame
|
||||||
|
- RetainedUnderlayBuild
|
||||||
|
- RetainedUnderlayReuse
|
||||||
|
- RetainedUnderlayFallback
|
||||||
|
- RetainedUnderlayInvalidation
|
||||||
|
- RetainedUnderlayStaticGroupDraw
|
||||||
|
- RetainedDynamicGroupDraw
|
||||||
|
|
||||||
|
要求:
|
||||||
|
- NDEBUG 下 no-op
|
||||||
|
- 不增加 Host API
|
||||||
|
- 不每帧刷大量日志
|
||||||
|
|
||||||
|
允许有限首次日志:cache enabled/build/fallback reason。
|
||||||
|
|
||||||
|
## 11. Native 定向测试
|
||||||
|
不新增或运行 Jest。
|
||||||
|
|
||||||
|
至少覆盖:
|
||||||
|
Boundary:
|
||||||
|
- 无 Weather/Particle -> disabled
|
||||||
|
- only Particle
|
||||||
|
- Weather + Particle 不同顺序
|
||||||
|
|
||||||
|
LayerGroup range:
|
||||||
|
- before/from
|
||||||
|
- forward/reverse
|
||||||
|
- original ordinal
|
||||||
|
|
||||||
|
Cache:
|
||||||
|
- first build
|
||||||
|
- reuse
|
||||||
|
- resize invalid
|
||||||
|
- boundary change invalid
|
||||||
|
- unsupported 3D fallback
|
||||||
|
|
||||||
|
Failure:
|
||||||
|
- compositor/resource 创建失败 -> safe fallback/no crash
|
||||||
|
|
||||||
|
测试放当前 renderer test 真实结构,不新造孤立测试框架。
|
||||||
|
|
||||||
|
## 12. Mac 编译与验证
|
||||||
|
|
||||||
|
本任务采用仓库 AGENTS.md 的固定闭环:
|
||||||
|
|
||||||
|
`Codex 写代码 → RDC 执行编译/测试 → 失败则把错误证据交回 Codex 修复 → RDC 再验证`
|
||||||
|
|
||||||
|
Codex 本轮负责:
|
||||||
|
- 完成 Renderer / OpenGL / Metal 相关源码与测试代码实现;
|
||||||
|
- 给出 RDC 需要执行的精确命令;
|
||||||
|
- 不自行启动、等待或轮询长时间编译/测试;
|
||||||
|
- RDC 返回失败日志后,继续分析并修复代码。
|
||||||
|
|
||||||
|
由 RDC / 普通 Terminal 实际执行:
|
||||||
|
- 编译受影响 Renderer C++ target;
|
||||||
|
- 编译 OpenGL compositor shader 相关 target;
|
||||||
|
- 编译 Metal compositor shader 相关 target;
|
||||||
|
- 运行直接相关 C++ 定向测试;
|
||||||
|
- git diff --check。
|
||||||
|
|
||||||
|
RDC 返回结果时:
|
||||||
|
- 成功:返回 exit code、耗时、target 摘要;
|
||||||
|
- 失败:返回首个真实 error、相关 target、必要上下文和完整日志路径;
|
||||||
|
- 不把完整大型 build log 反复送入 Codex。
|
||||||
|
|
||||||
|
明确禁止 Codex 执行:
|
||||||
|
- CMake / Ninja / Bazel 长时间编译;
|
||||||
|
- OpenGL / Metal 长时间构建等待;
|
||||||
|
- Android Gradle build;
|
||||||
|
- AAR/APK build/install;
|
||||||
|
- ADB;
|
||||||
|
- Android App 启动/清数据;
|
||||||
|
- Android 真机/虚拟机;
|
||||||
|
- Jest。
|
||||||
|
|
||||||
|
Android 最终由用户手工验证。
|
||||||
|
|
||||||
|
## 13. PoC 验收证据
|
||||||
|
理想 fixed Camera + Particle ~30 FPS:
|
||||||
|
- RetainedUnderlayBuild ≈ 1
|
||||||
|
- RetainedUnderlayReuse 持续增长
|
||||||
|
- ParticleSimulationAdvance 持续增长
|
||||||
|
- RetainedUnderlayStaticGroupDraw 建 cache 后不随 Particle FPS 增长
|
||||||
|
- RetainedDynamicGroupDraw 持续增长
|
||||||
|
|
||||||
|
如 Mac 无法跑完整 Particle runtime:
|
||||||
|
- 完成 Native 定向/静态验证
|
||||||
|
- 给出用户最少 Android 手工验证项
|
||||||
|
- 最终状态必须:IMPLEMENTED / WAITING FOR ANDROID VALIDATION
|
||||||
|
- 不得写 CLOSED / PASS
|
||||||
|
|
||||||
|
## 14. 本任务明确不做
|
||||||
|
- production 全部 tile/style/placement invalidation
|
||||||
|
- Map::Impl::onUpdate fast path
|
||||||
|
- 跳过 createRenderTree
|
||||||
|
- 跳过 source->update
|
||||||
|
- 跳过 TilePyramid::update
|
||||||
|
- 跳过普通 prepare
|
||||||
|
- 新 Host API
|
||||||
|
- NavSea React 修改
|
||||||
|
- AIS 专项优化
|
||||||
|
- 通用任意 static/dynamic RenderGraph
|
||||||
|
- STEP-089
|
||||||
|
- STEP-090
|
||||||
|
|
||||||
|
## 15. 文档输出
|
||||||
|
必须生成:
|
||||||
|
- docs/50_Step记录/STEP-088_静态底图缓存PoC与Renderer观测_v1.md
|
||||||
|
- docs/报告/STEP-088_静态底图缓存PoC与Renderer观测_Codex完成报告_v1.md
|
||||||
|
|
||||||
|
如同主题已有,递增版本,不覆盖旧报告。
|
||||||
|
|
||||||
|
Step 记录至少写:
|
||||||
|
- 当前问题
|
||||||
|
- 实现结构
|
||||||
|
- boundary
|
||||||
|
- underlay cache flow
|
||||||
|
- PoC 限制
|
||||||
|
- 用户 Android 手工验证项
|
||||||
|
|
||||||
|
完成报告必须写:
|
||||||
|
- 起始 HEAD / 最终 HEAD
|
||||||
|
- 修改文件
|
||||||
|
- boundary 实现位置
|
||||||
|
- LayerGroup 分段实现位置
|
||||||
|
- cache/compositor 实现位置
|
||||||
|
- debug counters
|
||||||
|
- 测试/编译结果
|
||||||
|
- Android 未执行项
|
||||||
|
- fallback
|
||||||
|
- 是否满足进入 STEP-089 条件
|
||||||
|
- final commit hash
|
||||||
|
- commit message
|
||||||
|
- push result
|
||||||
|
- remote branch
|
||||||
|
- Final HEAD
|
||||||
|
|
||||||
|
## 16. Git 收尾
|
||||||
|
完成后:
|
||||||
|
1. 只 stage/commit STEP-088 相关文件。
|
||||||
|
2. 不提交当前主仓库既有无关 dirty/untracked 内容。
|
||||||
|
3. commit message 建议:feat: add retained underlay render cache poc
|
||||||
|
4. push 到 origin/particle/main。
|
||||||
|
5. push 前 fetch;远端前进时安全整合本任务。
|
||||||
|
6. push 失败不得声称完成。
|
||||||
|
7. 最终确认任务提交已在 origin/particle/main。
|
||||||
|
|
||||||
|
## 17. 最终响应
|
||||||
|
直接逐项报告:
|
||||||
|
- 已完成
|
||||||
|
- 未完成
|
||||||
|
- 无法验证
|
||||||
|
|
||||||
|
不要再输出实施计划。
|
||||||
144
docs/40_任务/STEP-089_静态渲染Revision与Invalidation基础层_Codex任务_v1.md
Normal file
144
docs/40_任务/STEP-089_静态渲染Revision与Invalidation基础层_Codex任务_v1.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# STEP-089 静态渲染 Revision 与 Invalidation 基础层 Codex 任务 v1
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
为 STEP-090 Static CPU Fast Path 建立可靠、可验证的静态状态失效判据。
|
||||||
|
|
||||||
|
STEP-089 不负责跳过 static source/tile/render-tree/prepare,也不负责降低 CPU;只建立“静态前缀当前是否仍可安全复用”的 revision / invalidation 基础,确保后续 fast path 不会复用过期静态状态。
|
||||||
|
|
||||||
|
当前基线:
|
||||||
|
|
||||||
|
- branch: `particle/main`
|
||||||
|
- baseline HEAD: `07a17b4c701cd22c8e8b516ad84f4044f291f9fa`
|
||||||
|
- STEP-088 retained underlay 已完成:
|
||||||
|
- GPU static draw cache
|
||||||
|
- map-scoped runtime Cache ON/OFF
|
||||||
|
- Release 最小 diagnostics
|
||||||
|
- Android/iOS/RN runtime API
|
||||||
|
- Android NavSea 真实 PBF 性能场景
|
||||||
|
- 实测证明主要剩余成本是 Particle continuous repaint 下 PBF/vector/static renderer pipeline 被重复 processing。
|
||||||
|
- STEP-090 才负责 CPU fast path。
|
||||||
|
|
||||||
|
## 必做项
|
||||||
|
|
||||||
|
- [ ] 引入单调递增的 static/retained revision 机制,能表达“静态前缀相关输入已变化”。
|
||||||
|
- [ ] retained cache build 时记录所依据的 revision。
|
||||||
|
- [ ] reuse 前必须确认当前 revision 与 build revision 一致。
|
||||||
|
- [ ] 下列变化必须使 revision 前进或明确 invalidate:
|
||||||
|
- [ ] camera / transform
|
||||||
|
- [ ] viewport / size / pixel ratio
|
||||||
|
- [ ] style reload / style mutation
|
||||||
|
- [ ] layer add/remove/reorder/property mutation
|
||||||
|
- [ ] source add/remove/change
|
||||||
|
- [ ] tile / drawable 内容更新
|
||||||
|
- [ ] image / sprite / glyph 变化
|
||||||
|
- [ ] placement / symbol fade 状态变化
|
||||||
|
- [ ] render context / surface / backend resource reset
|
||||||
|
- [ ] debug render options
|
||||||
|
- [ ] terrain / 3D / custom layer 等当前不能安全证明静态的场景
|
||||||
|
- [ ] symbol fade / placement transition 活跃时不得复用 stale static cache。
|
||||||
|
- [ ] unsupported / uncertain 情况继续 fail closed 到 full renderer。
|
||||||
|
- [ ] Cache OFF/ON runtime 行为保持不变。
|
||||||
|
- [ ] Release diagnostics 保持低成本;允许新增只读 revision 字段,但禁止 per-frame Release 日志。
|
||||||
|
- [ ] Debug 日志可增加 revision / invalidation reason,便于 RDC 验证。
|
||||||
|
- [ ] 原有 LayerGroup ordinal、dynamic boundary、transparent underlay、legacy ordering 语义不得回退。
|
||||||
|
- [ ] 不实现 STEP-090 的 static CPU processing skip。
|
||||||
|
- [ ] 不新增 React 管理的 renderer cache。
|
||||||
|
- [ ] 不把 provider/network/NavSea Store 逻辑引入 MapLibre Core。
|
||||||
|
- [ ] 不使用 Jest。
|
||||||
|
|
||||||
|
## Revision 设计约束
|
||||||
|
|
||||||
|
优先采用单调 `uint64_t` revision / generation:
|
||||||
|
|
||||||
|
- revision 只增不减;
|
||||||
|
- 不依赖 wall clock;
|
||||||
|
- 不要求每种事件都维护独立缓存副本;
|
||||||
|
- 可以保留 reason/category 作为诊断,但 correctness 依赖 revision,而不是字符串 reason;
|
||||||
|
- revision wraparound 可按现实上不可达处理,但类型必须明确。
|
||||||
|
|
||||||
|
建议最少区分:
|
||||||
|
|
||||||
|
- 当前 static revision;
|
||||||
|
- retained underlay build revision;
|
||||||
|
|
||||||
|
如现有 Renderer/RenderOrchestrator 的生命周期更适合其他命名,可调整,但不要把模型复杂化。
|
||||||
|
|
||||||
|
## Invalidation 范围原则
|
||||||
|
|
||||||
|
STEP-089 首选“安全偏保守”。
|
||||||
|
|
||||||
|
如果某类状态变化是否会影响 static prefix 无法可靠判断:
|
||||||
|
|
||||||
|
- 直接 bump revision / invalidate;
|
||||||
|
- 不做复杂局部 dependency graph;
|
||||||
|
- 不为了提高 hit rate 提前实现 STEP-090 级别的细粒度 source/layer dependency tracking。
|
||||||
|
|
||||||
|
目标是让 STEP-090 有一个可信的 fast-path gate,而不是在 089 追求最高 cache reuse。
|
||||||
|
|
||||||
|
## Placement / Symbol 特别规则
|
||||||
|
|
||||||
|
已有 Android 真机证据:
|
||||||
|
|
||||||
|
- Particle 放到全部 63 层上方时,symbol-heavy prefix 会出现持续 `static-placement` / `static-transition`。
|
||||||
|
- BEFORE_SYMBOL_CLUSTER(fill/line 在下,symbol cluster 在上)是目前最稳定边界。
|
||||||
|
|
||||||
|
因此:
|
||||||
|
|
||||||
|
- placement/fade active 必须阻止旧 static state 被错误复用;
|
||||||
|
- fade/placement 稳定后允许重新 build 并记录新 revision;
|
||||||
|
- 不得简单忽略 symbol placement 来提高 TOP 场景 reuse。
|
||||||
|
|
||||||
|
## Runtime API / Diagnostics
|
||||||
|
|
||||||
|
保持现有:
|
||||||
|
|
||||||
|
- `setRetainedUnderlayEnabled`
|
||||||
|
- `getRetainedUnderlayDiagnostics`
|
||||||
|
|
||||||
|
允许在 diagnostics 增加:
|
||||||
|
|
||||||
|
- `staticRevision`
|
||||||
|
- `buildRevision`
|
||||||
|
|
||||||
|
如增加,Android/iOS/RN 三端需一致;如果为了控制 STEP-089 范围决定暂不公开,也至少在 Debug Native 日志中可观察。
|
||||||
|
|
||||||
|
不要新增第二套 runtime module / map registry。
|
||||||
|
|
||||||
|
## 测试要求
|
||||||
|
|
||||||
|
新增/扩展 Native directed tests,至少覆盖:
|
||||||
|
|
||||||
|
1. revision 单调递增;
|
||||||
|
2. build revision 与 current revision 相等时允许 reuse;
|
||||||
|
3. camera/viewport/style/source 变化后 revision 改变并禁止旧 cache reuse;
|
||||||
|
4. placement/fade active 时不 reuse stale cache;
|
||||||
|
5. Cache OFF -> invalidate;重新 ON -> 下一次重新 build;
|
||||||
|
6. unsupported / resource failure 保持安全 fallback;
|
||||||
|
7. 原有 RetainedDynamicBoundary / LayerGroupRange / RetainedUnderlayCache 测试继续通过。
|
||||||
|
|
||||||
|
不要求 Jest。
|
||||||
|
|
||||||
|
## Codex 执行规则
|
||||||
|
|
||||||
|
- 先完整阅读本任务、AGENTS.md、STEP-088 v3 Step/报告,以及 retained underlay 当前实现。
|
||||||
|
- 不需要提交实施计划。
|
||||||
|
- 直接实现。
|
||||||
|
- 不运行 Gradle / Android build / ADB / Xcode。
|
||||||
|
- 不运行大型 noisy build/test;编译和最终验证由 RDC 负责。
|
||||||
|
- 可以做必要的静态代码检查,但不要替代 RDC 编译。
|
||||||
|
- 不 commit/push,等 RDC 验证后统一收尾。
|
||||||
|
- 不触碰用户既有无关 dirty/untracked。
|
||||||
|
- 不使用 worktree。
|
||||||
|
- 无法完成的项明确说明,不得静默跳过。
|
||||||
|
|
||||||
|
## 完成时必须报告
|
||||||
|
|
||||||
|
逐项按“已完成 / 未完成 / 无法验证”报告上述必做项,并列出:
|
||||||
|
|
||||||
|
- 修改文件;
|
||||||
|
- revision 的实际所有者与生命周期;
|
||||||
|
- 每类 invalidation 在哪里触发;
|
||||||
|
- 新增测试名;
|
||||||
|
- RDC 应执行的精确 OpenGL / Metal / Android Validation 命令;
|
||||||
|
- 明确说明没有实现 STEP-090 fast path。
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
# STEP-090 Particle-only Static CPU Fast Path Codex 任务 v1
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
基于 STEP-088 retained underlay GPU cache 与 STEP-089 static revision/invalidation gate,真正减少 Particle continuous repaint 下静态 PBF/vector 的 CPU processing。
|
||||||
|
|
||||||
|
当前已实测:
|
||||||
|
|
||||||
|
- PURE PBF idle:RenderThread ~0%
|
||||||
|
- PURE PARTICLE 2048 / FPS30:RenderThread ~10.2%
|
||||||
|
- production Style + 60 个 PBF layer hidden + Particle:RenderThread ~25.5%
|
||||||
|
- production PBF + Particle:RenderThread ~96~100%
|
||||||
|
- STEP-088 仅省 static GPU draw,RenderThread 仍 ~96%
|
||||||
|
- STEP-089 已保证:
|
||||||
|
- steady state staticRevision/buildRevision 稳定;
|
||||||
|
- camera/style/tile 等真实变化会 revision bump;
|
||||||
|
- transient drawable/topology churn 不误触发。
|
||||||
|
|
||||||
|
因此 STEP-090 的目标是:
|
||||||
|
|
||||||
|
> 当当前帧仅由 Particle animation 触发,且静态状态 revision/topology/camera/viewport/placement 等均稳定时,跳过不必要的静态 PBF/vector CPU pipeline,复用上一帧已准备好的 render state / RenderTree,并只执行 Particle 所需的动态 render。
|
||||||
|
|
||||||
|
## 基线
|
||||||
|
|
||||||
|
- branch: `particle/main`
|
||||||
|
- baseline HEAD: `98e1d2d977ba9e007ba313c14d63bfa54ab9d23b`
|
||||||
|
- STEP-089: CLOSED / PASS
|
||||||
|
|
||||||
|
## 核心约束
|
||||||
|
|
||||||
|
### 1. Fast path 必须在 createRenderTree 之前判定
|
||||||
|
|
||||||
|
当前:
|
||||||
|
`Renderer::render(UpdateParameters)`
|
||||||
|
→ `RenderOrchestrator::createRenderTree()`
|
||||||
|
→ `renderTree->prepare()`
|
||||||
|
→ `Renderer::Impl::render()`
|
||||||
|
|
||||||
|
如果 fast path 只放在 `Renderer::Impl::render()`,已经无法节省 createRenderTree/source update/prepare,不满足 STEP-090。
|
||||||
|
|
||||||
|
### 2. 仅允许 Particle-only frame
|
||||||
|
|
||||||
|
必须内部区分:
|
||||||
|
|
||||||
|
- Particle-only animation frame;
|
||||||
|
- 其他任何会影响 map/render tree 的更新。
|
||||||
|
|
||||||
|
Fast path 只在能够证明“除了 Particle animation,没有其他更新需要处理”时使用。
|
||||||
|
|
||||||
|
不得新增 Host/RN 手工调用:
|
||||||
|
- 不要 `particle.renderFastFrame()`
|
||||||
|
- 不要 `cacheMapNow()`
|
||||||
|
- 不要 `invalidateBasemap()`
|
||||||
|
|
||||||
|
这是 MapLibre Native 内部自动行为。
|
||||||
|
|
||||||
|
### 3. 安全 gate
|
||||||
|
|
||||||
|
至少要求:
|
||||||
|
|
||||||
|
- retained underlay runtime enabled;
|
||||||
|
- retained cache valid;
|
||||||
|
- retained buildRevision == current staticRevision;
|
||||||
|
- camera / transform unchanged;
|
||||||
|
- viewport / pixelRatio unchanged;
|
||||||
|
- styleLoaded;
|
||||||
|
- dynamic boundary unchanged;
|
||||||
|
- topologyRevision unchanged;
|
||||||
|
- no static transition;
|
||||||
|
- no placement/fade transition affecting correctness;
|
||||||
|
- no source/tile/image/glyph/style/layer mutation;
|
||||||
|
- no context reset/debug/3D/custom unsupported;
|
||||||
|
- no pending change request that would alter render tree;
|
||||||
|
- no non-particle layer transition/repaint;
|
||||||
|
- no fading tile;
|
||||||
|
- no still image request;
|
||||||
|
- no map mode / special render path that cannot safely reuse。
|
||||||
|
|
||||||
|
任何不确定情况必须 full path。
|
||||||
|
|
||||||
|
### 4. RenderTree reuse
|
||||||
|
|
||||||
|
优先实现一个最小、明确的 cached prepared RenderTree / render state 生命周期:
|
||||||
|
|
||||||
|
- full frame 成功创建并 prepare 后,可作为下一次 particle-only fast frame 的候选;
|
||||||
|
- fast frame 不重新 createRenderTree;
|
||||||
|
- fast frame 不重新调用 full `RenderTree::prepare()`;
|
||||||
|
- 复用对象生命周期必须明确,不能悬挂引用;
|
||||||
|
- full path 创建新 tree 后替换旧缓存;
|
||||||
|
- revision/context/style reset 时失效。
|
||||||
|
|
||||||
|
若 RenderTree 当前设计无法安全长期持有,允许提取一个专用 immutable prepared frame state,但不要大规模重构。
|
||||||
|
|
||||||
|
### 5. 动态部分
|
||||||
|
|
||||||
|
Particle fast frame必须仍然:
|
||||||
|
- 正常 advance simulation;
|
||||||
|
- 正常 render Particle;
|
||||||
|
- composite retained underlay;
|
||||||
|
- 正常 present;
|
||||||
|
- 保持 particle cadence/targetFps 语义。
|
||||||
|
|
||||||
|
对于 Particle 之外的动态 layer:
|
||||||
|
- 如果存在其他 `needsRepaint()` / transition / placement / fading tile,则禁止 fast path,走 full path。
|
||||||
|
- 不要在本步骤尝试支持 AIS/Ownship 等所有未来动态 layer;先确保 Particle-only fast frame 安全。
|
||||||
|
|
||||||
|
WeatherColor:
|
||||||
|
- 若本身不需要 repaint 且输入未变,可与静态 prepared state 一起复用;
|
||||||
|
- 若其状态变化,full path。
|
||||||
|
|
||||||
|
### 6. Source/PBF processing
|
||||||
|
|
||||||
|
Fast path 命中时,目标是明确不执行:
|
||||||
|
- full `createRenderTree()`
|
||||||
|
- static PBF/vector source update
|
||||||
|
- tile pyramid normal per-frame update
|
||||||
|
- static layer prepare
|
||||||
|
- static placement orchestration
|
||||||
|
- static layer group rebuild
|
||||||
|
|
||||||
|
允许仍执行 fast frame 所必须的最小 upload/tweaker/render,但要避免重新走静态 source/layer CPU pipeline。
|
||||||
|
|
||||||
|
### 7. Runtime ON/OFF
|
||||||
|
|
||||||
|
继续使用现有 `retainedUnderlayEnabled` 作为整体优化 gate:
|
||||||
|
|
||||||
|
- ON:允许 retained cache + CPU fast path;
|
||||||
|
- OFF:完全回到原 full-render baseline,用于 A/B;
|
||||||
|
- 不新增第二个用户开关,除非内部 debug-only 诊断确实必要。
|
||||||
|
|
||||||
|
这样同一个 Release 构建仍可通过 RN RendererRuntime 做完整 A/B。
|
||||||
|
|
||||||
|
### 8. 诊断
|
||||||
|
|
||||||
|
Debug 增加最小 counters/log:
|
||||||
|
|
||||||
|
- `ParticleFastFrameHit`
|
||||||
|
- `ParticleFastFrameMiss`
|
||||||
|
- `ParticleFullFrame`
|
||||||
|
- miss reason(debug only)
|
||||||
|
|
||||||
|
建议周期日志继续并入 `ParticleRetained:` 或新增稳定前缀 `ParticleFastPath:`,字段至少:
|
||||||
|
|
||||||
|
```text
|
||||||
|
hit=<n>
|
||||||
|
miss=<n>
|
||||||
|
full=<n>
|
||||||
|
reason=<...>
|
||||||
|
staticRevision=<n>
|
||||||
|
buildRevision=<n>
|
||||||
|
```
|
||||||
|
|
||||||
|
Release 不做 per-frame 日志。
|
||||||
|
|
||||||
|
如现有 diagnostics 适合,可在 Release pull snapshot 中增加累计 hit/miss,但不是强制;不要扩大 RN 高频接口。
|
||||||
|
|
||||||
|
### 9. 性能验收目标
|
||||||
|
|
||||||
|
不预设固定百分比门槛,但 Android NavSea PBF Test App 必须证明:
|
||||||
|
|
||||||
|
- AFTER_CLIP / BEFORE_SYMBOL_CLUSTER 固定 Camera;
|
||||||
|
- Cache/FastPath ON 时 fast hit 持续增长;
|
||||||
|
- full frame 不随 Particle frame 一比一增长;
|
||||||
|
- Particle 仍连续运动;
|
||||||
|
- static draw 继续不随 particle frame 增长;
|
||||||
|
- RenderThread CPU 相比 STEP-089 / Cache OFF 有明显下降。
|
||||||
|
|
||||||
|
关键基线用于比较:
|
||||||
|
|
||||||
|
```text
|
||||||
|
PURE PARTICLE ~10.2% RenderThread
|
||||||
|
PBF hidden + Particle ~25.5%
|
||||||
|
PBF visible + Particle ~96~100%
|
||||||
|
```
|
||||||
|
|
||||||
|
目标方向是让 PBF visible + Particle 大幅向前两者靠近,而不是只减少几个百分点。
|
||||||
|
|
||||||
|
### 10. 回退验证
|
||||||
|
|
||||||
|
必须验证:
|
||||||
|
|
||||||
|
- pan / zoom / rotate → fast path 立即 miss/full;
|
||||||
|
- tile load/change → full;
|
||||||
|
- style/layer/source change → full;
|
||||||
|
- cache OFF → full;
|
||||||
|
- static state重新稳定后 → fast hit恢复;
|
||||||
|
- TOP symbol-heavy / placement active → 保守 full/fallback;
|
||||||
|
- resize/context reset → full;
|
||||||
|
- no crash / stale map / ordering regression。
|
||||||
|
|
||||||
|
## 不做
|
||||||
|
|
||||||
|
- 不新增 Host API 触发 fast frame。
|
||||||
|
- 不把 provider/network/vendor 逻辑放入 Native。
|
||||||
|
- 不改 RN Weather Service 边界。
|
||||||
|
- 不做 STEP-091 级别的多动态层通用调度。
|
||||||
|
- 不做复杂 dependency graph。
|
||||||
|
- 不做 Jest。
|
||||||
|
- 不使用 worktree。
|
||||||
|
|
||||||
|
## Native 测试
|
||||||
|
|
||||||
|
新增 directed tests,至少覆盖:
|
||||||
|
|
||||||
|
1. particle-only + stable revision → fast candidate true;
|
||||||
|
2. revision mismatch → false;
|
||||||
|
3. camera/viewport mismatch → false;
|
||||||
|
4. non-particle transition/repaint → false;
|
||||||
|
5. placement/fade/fading tile → false;
|
||||||
|
6. cache disabled → false;
|
||||||
|
7. context/style/source/tile mutation后 → false;
|
||||||
|
8. full frame重新稳定后 → fast candidate true;
|
||||||
|
9. cached RenderTree 生命周期/替换不悬挂;
|
||||||
|
10. 原 12 个 retained tests继续通过。
|
||||||
|
|
||||||
|
## Codex 执行规则
|
||||||
|
|
||||||
|
- 先完整读取 AGENTS.md、STEP-088 v3、STEP-089 task/Step/报告、当前 renderer.cpp / render_orchestrator / renderer_impl / RenderTree。
|
||||||
|
- 不提交实施计划,直接调查并实现。
|
||||||
|
- 不运行 Gradle/ADB/Xcode/大构建。
|
||||||
|
- 不 commit/push。
|
||||||
|
- 不触碰无关 dirty/untracked。
|
||||||
|
- 不使用 worktree。
|
||||||
|
- 不加 Jest。
|
||||||
|
- 如果发现当前 RenderTree 不能安全缓存,先在当前架构内采用最小安全方案,不进行无边界重构。
|
||||||
|
- 如果实现范围明显膨胀到新的架构层,停止并报告阻塞点,不强行完成。
|
||||||
|
|
||||||
|
## 完成时必须报告
|
||||||
|
|
||||||
|
逐项说明:
|
||||||
|
- fast path gate 实际位于哪里;
|
||||||
|
- 如何判断 particle-only;
|
||||||
|
- 哪些 full-frame 工作被跳过;
|
||||||
|
- cached RenderTree / prepared state 生命周期;
|
||||||
|
- 所有 miss/fallback 条件;
|
||||||
|
- 修改文件;
|
||||||
|
- 新增 tests;
|
||||||
|
- RDC 精确验证命令;
|
||||||
|
- 明确没有新增 Host 手工 fast-frame API。
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
# STEP-091 Zoom Out 后粒子 Viewport 一次性重播种 Codex 任务 v1
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
修复正式 RasterParticleLayer 在“大 Zoom → 小 Zoom(Zoom Out)”后,旧粒子短时间聚集在原视野小区域,必须等待粒子生命周期/运动逐渐扩散后才恢复正常密度的问题。
|
||||||
|
|
||||||
|
用户实机现象:
|
||||||
|
- Zoom Out 完成后,粒子集中成一小团/小矩形;
|
||||||
|
- 风场和粒子仍继续运动;
|
||||||
|
- 等旧粒子跑远或寿命到期后,整个新视野才逐渐恢复正常铺开;
|
||||||
|
- Zoom In 暂未观察到同类问题。
|
||||||
|
|
||||||
|
本任务必须以当前实际源码为准修复,不改 Field/T0/T1/Timeline 数据契约,不引入第二套粒子系统。
|
||||||
|
|
||||||
|
## 基线
|
||||||
|
|
||||||
|
- repo: MapLibreParticleLab
|
||||||
|
- branch baseline: `origin/particle/main`
|
||||||
|
- baseline HEAD: `297b9126200cea3da3bdef2faacedcd9845f6d08`
|
||||||
|
- 实施工作树: `/Users/tei/dev/MapLibreParticleLab-step091`
|
||||||
|
- 实施分支: `step091-zoom-out-reseed`
|
||||||
|
- STEP-090 已 CLOSED / PASS,retained underlay + Particle-only CPU fast path 必须保持。
|
||||||
|
## 已确认根因
|
||||||
|
|
||||||
|
重点读取:
|
||||||
|
- `src/mbgl/renderer/layers/render_particle_validation_layer.cpp`
|
||||||
|
- OpenGL / Metal particle shader
|
||||||
|
- 当前 camera invalidation、respawnBounds、State/Trail seed 逻辑。
|
||||||
|
|
||||||
|
当前正式路径已经存在:
|
||||||
|
- `TrailCameraStableDelay = 150ms`
|
||||||
|
- camera 变化时 `trailCameraInvalidated=true`
|
||||||
|
- camera stable 后重新启用 viewport density
|
||||||
|
- `viewportRespawnDomain(...)`
|
||||||
|
- `respawnBounds`
|
||||||
|
- shader 的 `outsideRespawnBounds(position)`
|
||||||
|
- `stateSeeded / trailSeeded`
|
||||||
|
- initial seed 时由 shader 使用 `respawnSeed(...)` 在 `respawnBounds` 内播种。
|
||||||
|
|
||||||
|
问题是 Zoom Out 后新的 respawnBounds 变大,旧粒子仍位于扩大后的 bounds 内,因此不会命中 outsideRespawnBounds,只能等待 age/invalid/运动触发 respawn。
|
||||||
|
|
||||||
|
## 修复原则
|
||||||
|
|
||||||
|
Camera 稳定后,如果本次手势/连续 camera 变化的最终结果是明确 Zoom Out,则执行一次 viewport density reseed:
|
||||||
|
- 下一次 State pass 把全部粒子重新播种到“新的、已经计算完成的 respawnBounds”;
|
||||||
|
- 同时清理/重新 seed Trail,避免旧小视野 trail 与新粒子叠加;
|
||||||
|
- 之后立即恢复正常连续 simulation。
|
||||||
|
这不是完整 Simulation reset。禁止为了 Zoom Out:
|
||||||
|
- 调用 `resetSequence("ZOOM_OUT")`;
|
||||||
|
- 重建 State/Trail GPU resource;
|
||||||
|
- 重新上传 Field texture;
|
||||||
|
- reset Timeline / T0 / T1 / MIX;
|
||||||
|
- style reload;
|
||||||
|
- source reload;
|
||||||
|
- Host/RN 手工 reset API。
|
||||||
|
|
||||||
|
优先复用现有 `stateSeeded=false` / `trailSeeded=false` 和 shader initial-seed 机制;若实际代码证明还需一个极小内部状态,可最小新增。
|
||||||
|
|
||||||
|
## Camera 手势状态
|
||||||
|
|
||||||
|
需要记录“一次 camera invalidation 开始前”的稳定 Zoom,不能每帧覆盖。
|
||||||
|
|
||||||
|
建议最小状态语义:
|
||||||
|
- camera gesture / invalidation 开始时保存 `startZoom`;
|
||||||
|
- camera 变化期间仍沿用现有 invalidated 行为;
|
||||||
|
- 150ms stable 时比较最终 `endZoom`;
|
||||||
|
- 仅当 `endZoom < startZoom - epsilon` 才请求 viewport reseed;
|
||||||
|
- epsilon 仅防浮点/微小抖动,建议约 0.05,实际值可按现有 double/zoom 语义选择并写明理由;
|
||||||
|
- Zoom In、纯 Pan、Rotate、Pitch 不触发 full reseed;
|
||||||
|
- 新一轮 camera invalidation 必须重新建立 startZoom,不得复用上一轮。
|
||||||
|
## 执行顺序要求
|
||||||
|
|
||||||
|
必须保证 reseed 使用新的 viewport domain:
|
||||||
|
|
||||||
|
1. camera 变化 → invalidated;
|
||||||
|
2. camera 连续变化期间不触发 reseed;
|
||||||
|
3. stable debounce 到期;
|
||||||
|
4. 计算当前 `viewportRespawnDomain(transformState, viewport, field)`;
|
||||||
|
5. 得到当前 `viewportDensityActive` 和新的 `respawnBounds`;
|
||||||
|
6. 仅在本轮最终为 Zoom Out 且 viewport respawn domain 有效时执行一次:
|
||||||
|
- `stateSeeded = false`
|
||||||
|
- `trailSeeded = false`
|
||||||
|
- 清除 pending 标记;
|
||||||
|
7. 下一次 State/Trail pass 使用现有 initial seed 流程;
|
||||||
|
8. 后续帧继续正常 simulation。
|
||||||
|
|
||||||
|
若 stable 时 respawn domain fallback/无效:
|
||||||
|
- 不允许留下会在未来无关 Pan 时突然触发的 pending reseed;
|
||||||
|
- 必须安全消费/取消本轮请求并记录原因;
|
||||||
|
- 不得伪造 viewport domain。
|
||||||
|
|
||||||
|
## Trail
|
||||||
|
|
||||||
|
Zoom Out viewport reseed 必须同时让 Trail 重新 seed/清理。
|
||||||
|
不得保留旧小视野 trail 作为新 viewport 的历史。
|
||||||
|
不得改变正常连续播放时的 trail fade、T0/T1 overlap、simulationRebuild=false 语义。
|
||||||
|
## Shader / UBO 边界
|
||||||
|
|
||||||
|
当前 GL/Metal shader 已有:
|
||||||
|
- initial seed;
|
||||||
|
- respawnSeed;
|
||||||
|
- respawnBounds;
|
||||||
|
- alpha/validity 防止 respawn jump 连线。
|
||||||
|
|
||||||
|
本任务默认不修改 shader、UBO layout 或公共 Layer API。
|
||||||
|
只有实际源码证明现有 initial-seed 无法正确完成 viewport reseed 时,才允许最小修改,并在完成报告说明原因。
|
||||||
|
|
||||||
|
绝对禁止:
|
||||||
|
- partial reseed ratio 新协议;
|
||||||
|
- per-particle CPU readback;
|
||||||
|
- JS/RN 粒子重播种;
|
||||||
|
- 新增 Host camera callback 驱动粒子;
|
||||||
|
- 将 provider/network/vendor 逻辑放入 Renderer。
|
||||||
|
|
||||||
|
## STEP-089 / STEP-090 保护
|
||||||
|
|
||||||
|
Camera 变化期间原本就应使 fast path miss/full;稳定后应恢复 fast hit。
|
||||||
|
本次修复不得破坏:
|
||||||
|
- retained underlay cache;
|
||||||
|
- Particle-only static CPU fast path;
|
||||||
|
- staticRevision/buildRevision 语义;
|
||||||
|
- camera/style/tile mutation fail-closed;
|
||||||
|
- normal continuous particle cadence;
|
||||||
|
- WeatherColor 与 RasterParticle 分层;
|
||||||
|
- Runtime Field Window/T0/T1/MIX。
|
||||||
|
## 诊断日志
|
||||||
|
|
||||||
|
新增或扩展最小 Debug 日志,至少能确认一次 Zoom Out reseed:
|
||||||
|
|
||||||
|
建议稳定前缀:
|
||||||
|
`ParticleDensity: event=VIEWPORT_RESEED reason=ZOOM_OUT ...`
|
||||||
|
|
||||||
|
字段至少包含:
|
||||||
|
- startZoom
|
||||||
|
- endZoom
|
||||||
|
- respawnBounds
|
||||||
|
- stateReseed=true
|
||||||
|
- trailReseed=true
|
||||||
|
- fieldResubmit=false
|
||||||
|
- timelineReset=false
|
||||||
|
- gpuResourceRebuild=false
|
||||||
|
|
||||||
|
fallback/无效 domain 时输出一次 skip/cancel 原因即可。
|
||||||
|
禁止 per-frame spam。
|
||||||
|
|
||||||
|
## 定向测试
|
||||||
|
|
||||||
|
不得加入 Jest。
|
||||||
|
|
||||||
|
至少验证:
|
||||||
|
1. Zoom Out 超过 epsilon → stable 后请求一次 reseed;
|
||||||
|
2. Zoom In → 不请求;
|
||||||
|
3. 纯 Pan → 不请求;
|
||||||
|
4. Rotate/Pitch 且 Zoom 未下降 → 不请求;
|
||||||
|
5. Zoom Out 后 fallback domain → 不留下延迟 pending;
|
||||||
|
6. 一次 gesture 只 reseed 一次;
|
||||||
|
7. 下一轮 gesture startZoom 正确刷新;
|
||||||
|
8. 现有 respawn/State/Trail 生命周期测试继续通过(如已有可复用测试)。
|
||||||
|
测试实现优先使用小型 C++ helper / directed native test;不要为了测试扩大公共 API。
|
||||||
|
若当前测试结构无法直接覆盖 private renderer 状态,允许测试提取出的纯判定 helper,但不得引入复杂新模块。
|
||||||
|
|
||||||
|
## Mac 验证
|
||||||
|
|
||||||
|
Codex 需要:
|
||||||
|
- 读取根目录 `AGENTS.md` 与相关 STEP-055、STEP-089、STEP-090 记录/报告;
|
||||||
|
- 执行与修改直接相关的 Mac 原生精确编译/定向 C++ 测试;
|
||||||
|
- noisy build 遵循全局 `$build-output-throttle`;
|
||||||
|
- `git diff --check` PASS;
|
||||||
|
- 不运行 Android Gradle/APK/ADB;
|
||||||
|
- 不运行 Jest。
|
||||||
|
|
||||||
|
## Android Test App
|
||||||
|
|
||||||
|
本任务默认不新增 UI 控件。
|
||||||
|
现有 Particle Core Layer Validation / 正式 RasterParticleLayer 场景应足以通过 ADB 手势验证。
|
||||||
|
如果现有 Test App 无法可靠触发正式 viewport respawn path,才允许最小增加 debug-only validation 入口,禁止改产品 Host API。
|
||||||
|
|
||||||
|
## 实机验收预期(由 RDC 在 Codex 完成后执行)
|
||||||
|
|
||||||
|
- 编译 OpenGL Debug Test APK;
|
||||||
|
- 安装到当前可用 Android 测试设备;
|
||||||
|
- 启动 `ParticleCoreLayerValidationActivity`;
|
||||||
|
- 使用正式 RasterParticleLayer 场景与较高粒子数;
|
||||||
|
- 做明显 Zoom In 后再快速 Zoom Out;
|
||||||
|
- 日志出现一次 VIEWPORT_RESEED;
|
||||||
|
- Zoom Out 后不再等待 10 秒级生命周期才铺满新视野;
|
||||||
|
- 无 crash / ANR / GL/EGL error;
|
||||||
|
- camera stable 后 ParticleFastPath fast hit 恢复增长。
|
||||||
|
## 文档与 Git
|
||||||
|
|
||||||
|
Codex 完成后必须:
|
||||||
|
- 更新 `粒子开发记录.md`;
|
||||||
|
- 生成 `docs/50_Step记录/STEP-091_ZoomOut_Viewport_Particle_Reseed_v1.md`;
|
||||||
|
- 生成 `docs/报告/STEP-091_ZoomOut_Viewport_Particle_Reseed_Codex完成报告_v1.md`;
|
||||||
|
- 完成报告逐项写“已完成 / 未完成 / 无法验证”,不要写实施计划;
|
||||||
|
- 记录实际修改文件、实际测试命令与结果;
|
||||||
|
- 记录是否改 shader/UBO/API;
|
||||||
|
- 记录 STEP-090 fast path 是否保持。
|
||||||
|
|
||||||
|
提交本任务相关变更到当前工作分支,并 push:
|
||||||
|
`git push origin HEAD:particle/main`
|
||||||
|
|
||||||
|
不得提交任何与本任务无关的改动。
|
||||||
|
push 失败不得声称任务完成。
|
||||||
|
|
||||||
|
完成报告必须记录:
|
||||||
|
- implementation commit hash
|
||||||
|
- commit message
|
||||||
|
- push result
|
||||||
|
- remote branch
|
||||||
|
- Final HEAD / origin/particle/main
|
||||||
|
|
||||||
|
## 完成标准
|
||||||
|
|
||||||
|
只有同时满足以下条件才可判定 Codex 阶段 PASS:
|
||||||
|
- 根因按当前代码被修复;
|
||||||
|
- Zoom Out stable 后只做一次 viewport State/Trail reseed;
|
||||||
|
- 不做 full simulation reset / field resubmit / timeline reset / GPU resource rebuild;
|
||||||
|
- Zoom In/Pan/Rotate/Pitch 不误触发;
|
||||||
|
- 定向 C++ 测试与 Mac 精确编译通过;
|
||||||
|
- git diff --check 通过;
|
||||||
|
- 文档完成;
|
||||||
|
- commit + push 到 `origin/particle/main` 成功。
|
||||||
|
|
||||||
|
Android APK 构建、安装与设备手势验收由后续 RDC 串行执行,不属于 Codex 自己的完成条件。
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
# STEP-092 Continuous Range 可退化分段 Retained Cache — Codex 任务 v1
|
||||||
|
|
||||||
|
## 0. 目标
|
||||||
|
在当前 MapLibreParticleLab / Particle V11 Renderer 上实现一个 explicit Continuous Range。
|
||||||
|
正式架构定义:
|
||||||
|
App 用正常 Style 定义唯一 Z-order,并指定两个现有 Style Layer ID 作为闭区间 Continuous Range;
|
||||||
|
Renderer 将两个 ID 解析为连续 layerIndex,根据 Continuous Range 在 Style 中的位置自然形成 0~2 个 Retained Range Cache;
|
||||||
|
最终可以是 1、2 或 3 个 Render Range,绝不是固定“三层三明治”。
|
||||||
|
|
||||||
|
本任务只支持一个 Continuous Range,不做多个 Range 或任意 N Segment Planner。
|
||||||
|
|
||||||
|
## 1. 环境与起始确认
|
||||||
|
工作目录:/Users/tei/dev/MapLibreParticleLab
|
||||||
|
目标分支:particle/main
|
||||||
|
历史参考 HEAD:b29e564944197072a0effaec36ca95c48fe0649b,但不得据此强制回退。
|
||||||
|
修改前必须:
|
||||||
|
1. git fetch origin
|
||||||
|
2. 确认当前分支、HEAD、origin/particle/main
|
||||||
|
3. 记录起始 HEAD
|
||||||
|
4. 检查 git status --short
|
||||||
|
5. 不覆盖、不删除、不 reset/clean、不提交无关 dirty/untracked
|
||||||
|
6. 若远端更新,以当前真实 origin/particle/main 为基线
|
||||||
|
## 2. 唯一 Z-order 事实来源
|
||||||
|
Style Layer 顺序是唯一 Z-order 事实来源:
|
||||||
|
App/RN 负责业务语义和 Style Layer 顺序;
|
||||||
|
MapLibre Style 保存正式 Z-order;
|
||||||
|
C++ Renderer 只消费最终连续 layerIndex。
|
||||||
|
Renderer 禁止硬编码 NavSea 业务 Layer ID(AIS/Alarm/Wind/Wave 等)。
|
||||||
|
禁止建立 BOTTOM/CONTINUOUS/TOP 第二套 metadata 分类表。
|
||||||
|
禁止增加 AnchorLayer、ContinuousStartLayer、ContinuousEndLayer、RenderAnchorLayer、空白 Marker Layer。
|
||||||
|
|
||||||
|
## 3. Continuous Range API 语义
|
||||||
|
App 直接指定两个已有正常 Style Layer:
|
||||||
|
startLayerId
|
||||||
|
endLayerId
|
||||||
|
公开语义为闭区间 [startLayerId, endLayerId]。
|
||||||
|
Native 按当前 Style 解析成 startIndex/endIndex。
|
||||||
|
合法条件必须是 startIndex <= endIndex。
|
||||||
|
必须正式支持 startLayerId == endLayerId,单 Layer Continuous 完全合法。
|
||||||
|
App/RN 不传数字 index,RN 不计算 layerIndex。
|
||||||
|
|
||||||
|
内部建议转半开区间:
|
||||||
|
continuousBegin = startIndex
|
||||||
|
continuousEndExclusive = endIndex + 1
|
||||||
|
Bottom=[0,continuousBegin)
|
||||||
|
Continuous=[continuousBegin,continuousEndExclusive)
|
||||||
|
Top=[continuousEndExclusive,layerCount)
|
||||||
|
Range 存在判断统一为 begin < endExclusive。
|
||||||
|
## 4. 四种合法结构与 Cache 数量
|
||||||
|
必须支持:
|
||||||
|
1. Bottom + Continuous + Top → 2 retained caches
|
||||||
|
2. Bottom + Continuous → 1 Bottom cache
|
||||||
|
3. Continuous + Top → 1 Top cache
|
||||||
|
4. Continuous only → 0 retained caches
|
||||||
|
|
||||||
|
Continuous only 当 startIndex==0 且 endIndex==layerCount-1,是完全合法状态,不是 error/fallback。
|
||||||
|
不存在的 Range:
|
||||||
|
不创建 cache;
|
||||||
|
不分配 texture;
|
||||||
|
不分配 framebuffer/render target;
|
||||||
|
不 build;
|
||||||
|
不 composite;
|
||||||
|
不参与 cache-valid gate。
|
||||||
|
|
||||||
|
当前 RetainedUnderlayCache 应合理泛化为可复用 retained range cache 能力。
|
||||||
|
具体命名服从当前源码,但不得固定创建 bottomCache+topCache 占位资源。
|
||||||
|
Bottom/Top GPU 资源必须 lazy 创建。
|
||||||
|
|
||||||
|
## 5. Final Render 顺序
|
||||||
|
如果 Bottom 存在:build/reuse/composite Bottom
|
||||||
|
然后 render Continuous
|
||||||
|
如果 Top 存在:build/reuse/composite Top
|
||||||
|
顺序只能来自 Style,自然退化为 3/2/1 段。
|
||||||
|
## 6. global layerIndex / pass 语义
|
||||||
|
分 Range 后仍必须保持整个 Style 的:
|
||||||
|
原始 layerIndex;
|
||||||
|
原始 ordinal;
|
||||||
|
parameters.currentLayer;
|
||||||
|
depthRangeSize。
|
||||||
|
不得给 Bottom/Continuous/Top 各自从 0 重新编号。
|
||||||
|
Opaque 继续 reverse traversal;
|
||||||
|
Translucent 继续 forward traversal。
|
||||||
|
|
||||||
|
## 7. Visibility
|
||||||
|
Range topology 来自 Style/orderedLayers,不来自当前 visible/renderable Layers。
|
||||||
|
hidden start/end 仍必须参与 Range index 解析。
|
||||||
|
Layer ON/OFF 是内容变化,不是 Range topology 变化。
|
||||||
|
例如 start=10 hidden、11 visible、end=12 hidden,Range 仍是 10..12,不得缩成 11..11。
|
||||||
|
|
||||||
|
## 8. Style mutation
|
||||||
|
必须支持正常 Style mutation。
|
||||||
|
Continuous 内新增/删除普通成员正常工作。
|
||||||
|
Bottom/Top 内增删使对应实际存在 cache invalid/rebuild。
|
||||||
|
同一 Range 内 reorder 使 cache 内容 invalid/rebuild。
|
||||||
|
Layer 跨 Range 移动:重新解析 start/end index → topology revision → invalidate affected caches → invalidate fast prepared state → rebuild。
|
||||||
|
startLayerId/endLayerId 自身删除:Range 立即 invalid,清除 stale cache/index/prepared state并安全 fallback。
|
||||||
|
Layer 后续重新出现且顺序合法时可恢复 explicit Range。
|
||||||
|
## 9. Top Cache 正确性
|
||||||
|
Top retained texture 必须是透明 RGBA,并正确使用 premultiplied alpha。
|
||||||
|
目标支持 Particle 属于 Continuous、land-area 属于 Top Retained 时,Top texture 正确覆盖 Particle。
|
||||||
|
不得新增 LandMask RT、Wave/Tide mask system 或 Native land-mask 业务判断。
|
||||||
|
land-area 仍只是普通 Style Z-order。
|
||||||
|
|
||||||
|
## 10. Legacy compatibility
|
||||||
|
当前 WeatherColor/RasterParticle 可能通过 startsRetainedDynamicRange() 参与 legacy boundary。
|
||||||
|
规则:
|
||||||
|
explicit Continuous Range 已配置且有效 → STEP-092 Range 模式;
|
||||||
|
未配置 explicit Range → 保持当前 legacy retained-underlay 行为。
|
||||||
|
一旦 explicit Range 生效,startLayerId/endLayerId 是唯一 topology/boundary 来源。
|
||||||
|
禁止 explicit Range 与 startsRetainedDynamicRange() 混合生成第二套 boundary。
|
||||||
|
|
||||||
|
## 11. RN / Native API
|
||||||
|
在现有 RendererRuntime 增加稳定入口,目标语义:
|
||||||
|
setContinuousLayerRange(reactTag,startLayerId,endLayerId)
|
||||||
|
要求:ID 非空;允许相同;map-scoped;thread-safe;monotonic config revision;
|
||||||
|
Range 修改触发 renderer update/invalidation。
|
||||||
|
Android/iOS bridge 不理解业务;C++ 按当前 Style 解析 index。
|
||||||
|
保留 setRetainedUnderlayEnabled() 与 getRetainedUnderlayDiagnostics() 兼容。
|
||||||
|
## 12. Invalid / fallback
|
||||||
|
至少处理:
|
||||||
|
start 不存在;
|
||||||
|
end 不存在;
|
||||||
|
startIndex > endIndex;
|
||||||
|
Style 未 ready;
|
||||||
|
未配置 explicit range。
|
||||||
|
要求:不 crash;不猜 index;不复用 stale index/cache/prepared state;
|
||||||
|
不根据 Particle/WeatherColor 类型偷偷修正 explicit Range;
|
||||||
|
安全进入 legacy/full renderer;
|
||||||
|
diagnostics 说明 fallback 原因。
|
||||||
|
Continuous-only 不是 invalid/fallback。
|
||||||
|
|
||||||
|
## 13. Particle-only CPU Fast Path
|
||||||
|
现有 Particle-only CPU fast path 不得退化。
|
||||||
|
三段:Bottom reuse → Continuous render → Top reuse
|
||||||
|
只有 Bottom:Bottom reuse → Continuous render
|
||||||
|
只有 Top:Continuous render → Top reuse
|
||||||
|
0 cache:Continuous render
|
||||||
|
不存在的 cache 不能成为 Fast Path miss 原因。
|
||||||
|
Bottom 不存在时不得要求 bottomCache.valid=true;Top 同理。
|
||||||
|
Fast gate 只检查实际存在 retained ranges。
|
||||||
|
Continuous Range 本身必须通用,不依赖 RasterParticleLayer/Weather Layer ID。
|
||||||
|
若 Continuous 中存在当前 Fast Path 无法证明安全的未知 Layer:Fast Path fail closed,走正常 full CPU frame,Range Z-order/cache 仍正确。
|
||||||
|
不要为此重写整个 RenderGraph。
|
||||||
|
## 14. Invalidation
|
||||||
|
继续 correctness-first、偏保守模型,至少覆盖:
|
||||||
|
camera/transform/viewport/resize/pixel ratio/style reload;
|
||||||
|
layer add/remove/reorder/property;
|
||||||
|
source mutation/tile update/drawable update;
|
||||||
|
image/sprite/glyph;
|
||||||
|
placement/symbol fade/transition;
|
||||||
|
context reset/debug options/unsupported 3D/legacy ordering/resource/compositor failure。
|
||||||
|
无法判断只影响 Bottom 还是 Top 时,可同时 invalidate 所有实际存在 retained caches。
|
||||||
|
本任务不做复杂 dependency graph。
|
||||||
|
topology 变化还要 invalid Range index resolution、retained prepared state、Particle fast prepared state。
|
||||||
|
|
||||||
|
## 15. Symbol / Placement
|
||||||
|
Top 可能包含 symbol/label/Navigation/AIS/Alarm。
|
||||||
|
correctness 优先。
|
||||||
|
placement/symbol fade/transition 无法证明 reuse 安全时,invalidate/full fallback,稳定后 rebuild。
|
||||||
|
不重写 Placement 系统。
|
||||||
|
|
||||||
|
## 16. Diagnostics
|
||||||
|
至少增加/暴露:
|
||||||
|
rangeConfigured, rangeValid,
|
||||||
|
startLayerId,endLayerId,startIndex,endIndex,
|
||||||
|
bottomPresent,bottomBuild,bottomReuse,bottomInvalidation,
|
||||||
|
topPresent,topBuild,topReuse,topInvalidation,
|
||||||
|
fallback,lastReason。
|
||||||
|
bottomPresent=false 且 topPresent=false 可以只是 Continuous-only,不能自动判 error。
|
||||||
|
Release 保持 pull-based,不增加 per-frame 高频日志。
|
||||||
|
## 17. GPU 生命周期与双后端
|
||||||
|
实际 retained texture 数量必须严格等于实际存在 retained ranges:
|
||||||
|
Continuous only=0;
|
||||||
|
Bottom+Continuous=1;
|
||||||
|
Continuous+Top=1;
|
||||||
|
三段=2。
|
||||||
|
稳定 Continuous frame 不得每帧重建 retained texture/framebuffer/compositor resources。
|
||||||
|
|
||||||
|
Mac 上完成当前工程允许的精确 Native 验证。
|
||||||
|
OpenGL 重点:Bottom/Top target 创建复用、Top RGBA/premultiplied alpha、0/1/2 cache 无空资源、Continuous-only、Fast Path。
|
||||||
|
Metal 重点:同等 Range/cache topology、透明 Top composite、0/1/2 cache 生命周期、explicit Range/fallback 与 OpenGL 一致。
|
||||||
|
不得用 Linux Core/OpenGL 构建替代 Mac 基线。
|
||||||
|
若 Android 实机/Gradle/ADB 项未执行,报告必须明确列为“未执行 Android 项”,不得伪造通过。
|
||||||
|
|
||||||
|
## 18. Native directed tests(不加入 Jest)
|
||||||
|
至少覆盖:
|
||||||
|
start==end;
|
||||||
|
start<end;
|
||||||
|
start>end invalid;
|
||||||
|
start missing;
|
||||||
|
end missing;
|
||||||
|
hidden start/end;
|
||||||
|
Style reorder;
|
||||||
|
start/end 删除;
|
||||||
|
四种 Range 结构;
|
||||||
|
Bottom empty/Top empty/both empty;
|
||||||
|
不存在 Bottom 时不因 bottom cache missing 而 Fast Path miss;
|
||||||
|
不存在 Top 时不因 top cache missing 而 Fast Path miss。
|
||||||
|
还应验证:
|
||||||
|
explicit Range 有效时不混用 legacy boundary;
|
||||||
|
explicit Range invalid 不复用 stale cache;
|
||||||
|
single-layer Continuous 正常;
|
||||||
|
hidden endpoint 不改变 topology;
|
||||||
|
reorder 后重新解析 index;
|
||||||
|
endpoint 删除 fail safe;
|
||||||
|
0/1/2 retained cache resource 数量与 topology 一致;
|
||||||
|
Opaque/Translucent traversal 保持 global layerIndex 语义。
|
||||||
|
|
||||||
|
## 19. 明确不做
|
||||||
|
不做:任意 N Segment Planner、多个 Continuous Range、LOW/MEDIUM/HIGH 分类、
|
||||||
|
自动分析 Layer 更新频率、自动调整 Z-order、Anchor/Marker Layer、新 Style Layer 类型、
|
||||||
|
NavSea AIS 专项优化、Weather provider、Route ETA、Native land-mask 业务、
|
||||||
|
Wave/Tide 业务判断、第二套 Z-order、NavSea Layer ID 硬编码、
|
||||||
|
复杂 per-source dependency graph、PBF 重构/重生成、通用 RenderGraph、
|
||||||
|
Placement 重写、Particle 专属 Continuous 机制、Jest。
|
||||||
|
如发现必须突破边界才能继续,停止扩大范围并记录阻塞。
|
||||||
|
|
||||||
|
## 20. 必做文档
|
||||||
|
生成:
|
||||||
|
docs/50_Step记录/STEP-092_ContinuousRange可退化分段RetainedCache_v1.md
|
||||||
|
docs/报告/STEP-092_ContinuousRange可退化分段RetainedCache_Codex完成报告_v1.md
|
||||||
|
追加:粒子开发记录.md
|
||||||
|
同主题已存在则递增版本,不覆盖旧文件;所有文档中文。
|
||||||
|
完成报告必须包含:
|
||||||
|
起始 HEAD、最终 HEAD、修改文件清单;
|
||||||
|
C++ Renderer、RN API、Android API/bridge、iOS API/bridge;
|
||||||
|
Range config 所有者、ID→layerIndex 解析位置机制;
|
||||||
|
single-layer range、1/2/3 Render Range、0/1/2 retained cache;
|
||||||
|
invalidation、legacy compatibility、Fast Path、diagnostics、GPU lifecycle;
|
||||||
|
OpenGL、Metal、Native tests、未执行 Android 项、known limitations;
|
||||||
|
commit SHA/message、push result、remote branch、origin/particle/main Final HEAD、最终 git status --short。
|
||||||
|
|
||||||
|
## 21. Git
|
||||||
|
仅 scoped staging。
|
||||||
|
禁止 git add -A、git add .、git reset --hard、git clean。
|
||||||
|
不得触碰无关 dirty/untracked。
|
||||||
|
结束前:git diff --check;核对变更;只 stage 本任务文件;scoped commit;push origin particle/main。
|
||||||
|
push 后必须确认:
|
||||||
|
git rev-parse HEAD
|
||||||
|
git rev-parse origin/particle/main
|
||||||
|
git status --short
|
||||||
|
要求 HEAD == origin/particle/main。
|
||||||
|
push 失败不得声明完成。
|
||||||
|
|
||||||
|
## 22. PASS Gate
|
||||||
|
只有架构、四种 Range、0/1/2 cache、global layerIndex、Opaque/Translucent、Fast Path、
|
||||||
|
diagnostics、RN/Android/iOS API、Native directed tests、OpenGL/Metal 规定验证、
|
||||||
|
文档、scoped commit、push、远端 HEAD 一致全部满足,才可判 STEP-092 PASS。
|
||||||
|
关键项无法完成必须明确 PARTIAL/BLOCKED 和原因,禁止伪造 PASS。
|
||||||
652
docs/40_任务/STEP-093_ContinuousRange运行态综合验收_Codex任务_v1.md
Normal file
652
docs/40_任务/STEP-093_ContinuousRange运行态综合验收_Codex任务_v1.md
Normal file
@@ -0,0 +1,652 @@
|
|||||||
|
# STEP-093:Continuous Range 运行态综合验收
|
||||||
|
|
||||||
|
## 1. 任务目标
|
||||||
|
|
||||||
|
在 STEP-092 已完成并关闭的 Continuous Range / Retained Cache 架构基础上,进入真实 Renderer 运行态综合验收。
|
||||||
|
|
||||||
|
本任务不重新设计 STEP-092 架构,不引入新的 Z-order 体系,不重做 retained cache 结构。目标是通过可重复、可观察、可诊断的运行态场景,确认以下核心事实在真实渲染过程中成立:
|
||||||
|
|
||||||
|
1. Style Layer 顺序仍然是唯一 Z-order 真相。
|
||||||
|
2. `[startLayerId, endLayerId]` 能稳定解析为一个 Continuous Range。
|
||||||
|
3. Bottom / Continuous / Top 四种合法退化结构均能正确工作。
|
||||||
|
4. 实际 retained cache 数量严格为 0 / 1 / 2,不存在的 range 不创建无意义 GPU 资源。
|
||||||
|
5. Particle 连续播放时,稳定的普通 Style/PBF 内容能够命中 retained cache,而不是每帧重复完整绘制。
|
||||||
|
6. Top range 能真实覆盖 Continuous 中的 Particle,验证最终画面顺序为:`Bottom → Continuous → Top`。
|
||||||
|
7. Camera、viewport、style、layer/source 等变化发生时能够正确 invalidation、rebuild,并重新进入稳定 reuse。
|
||||||
|
8. Fast Path 失败时必须安全退回 full CPU frame,但不得破坏 Continuous Range、retained cache 和 Z-order。
|
||||||
|
9. Android/OpenGL 与 iOS/Metal 最终表现一致;若本阶段只能先完成 Android,则 iOS 可明确记录为后续待验收,不得虚报通过。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 当前基线
|
||||||
|
|
||||||
|
开始任务前必须先检查真实仓库,不得只依赖任务书中的 SHA。
|
||||||
|
|
||||||
|
预期起始基线:
|
||||||
|
|
||||||
|
- Repository: `MapLibreParticleLab`
|
||||||
|
- Branch: `particle/main`
|
||||||
|
- STEP-092 最终状态:`CLOSED / PASS(Mac Native 验证闭环)`
|
||||||
|
- STEP-092 最终已知 HEAD:`8a5d88008c142e2a80e0ff602d228e0e67de2d83`
|
||||||
|
- `HEAD == origin/particle/main`
|
||||||
|
- 工作区应为 clean
|
||||||
|
|
||||||
|
STEP-092 已完成的关键内容包括:
|
||||||
|
|
||||||
|
- Continuous Range 显式配置;
|
||||||
|
- Bottom / Continuous / Top 三段模型;
|
||||||
|
- 四种合法退化拓扑;
|
||||||
|
- 0 / 1 / 2 retained cache;
|
||||||
|
- Bottom/Top retained cache;
|
||||||
|
- Top 透明 RGBA + premultiplied alpha 合成;
|
||||||
|
- Style mutation 后重新解析;
|
||||||
|
- invalid explicit range 的安全 fallback;
|
||||||
|
- Particle Fast Path 与 retained cache 的协同;
|
||||||
|
- Android / iOS Bridge 接口;
|
||||||
|
- OpenGL Native build;
|
||||||
|
- iOS Metal-only native build;
|
||||||
|
- 定向 Native tests 29/29 PASS。
|
||||||
|
|
||||||
|
本 STEP 不重新证明上述静态设计,而是证明它们在真实运行态画面与资源生命周期中成立。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 开始前必读
|
||||||
|
|
||||||
|
Codex 开始前必须读取:
|
||||||
|
|
||||||
|
1. 仓库根目录 `AGENTS.md`
|
||||||
|
2. 当前粒子系统总规则 / 架构约束文档
|
||||||
|
3. `docs/50_Step记录/STEP-092_ContinuousRange可退化分段RetainedCache_v2.md`
|
||||||
|
4. `docs/报告/STEP-092_ContinuousRange可退化分段RetainedCache_Codex完成报告_v2.md`
|
||||||
|
5. STEP-092 相关实现文件与 diagnostics 实现
|
||||||
|
6. 当前 Android/iOS Particle Test App / validation 页面 / Host runtime 实现
|
||||||
|
7. 当前可用的测试按钮、场景、日志和 diagnostics 展示入口
|
||||||
|
|
||||||
|
如实际仓库结构与本任务书描述不一致,以当前真实代码为准,并在报告中写明差异。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 本任务职责划分
|
||||||
|
|
||||||
|
### 4.1 Codex 负责
|
||||||
|
|
||||||
|
Codex 负责:
|
||||||
|
|
||||||
|
- 调查当前可用的运行态测试入口;
|
||||||
|
- 为 STEP-093 补齐必要的 Test/Validation UI、场景切换、diagnostics 展示和最小测试辅助代码;
|
||||||
|
- 只在确有必要时补充可观察性;
|
||||||
|
- 修复在准备阶段发现的明确、局部、与 STEP-093 验收直接相关的问题;
|
||||||
|
- 为 RDC / 用户准备清晰、可逐项执行的运行态验收入口;
|
||||||
|
- 更新中文 STEP 记录、完成报告和开发记录;
|
||||||
|
- 提交并 push 自己完成的代码与文档。
|
||||||
|
|
||||||
|
### 4.2 RDC / 用户负责
|
||||||
|
|
||||||
|
实际 Android / iOS 编译、安装、启动、真机或模拟器运行、视觉操作与最终画面确认由 RDC / 用户执行。
|
||||||
|
|
||||||
|
Codex 不得仅依据静态代码、日志或自己推测就把运行态测试标记为 PASS。
|
||||||
|
|
||||||
|
如果没有实际执行某个运行态场景,必须写为 `NOT RUN`、`WAITING_RUNTIME_VALIDATION` 或其他明确的未完成状态,不得写成 PASS。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 严格保护的 STEP-092 架构
|
||||||
|
|
||||||
|
本 STEP 禁止借“测试方便”修改以下既定架构:
|
||||||
|
|
||||||
|
1. Style 顺序是唯一 Z-order 真相。
|
||||||
|
2. App/RN 只提供 `startLayerId`、`endLayerId`。
|
||||||
|
3. Native 自行解析 index。
|
||||||
|
4. Continuous Range 为闭区间 `[startLayerId, endLayerId]`。
|
||||||
|
5. 内部:`continuousBegin = startIndex`,`continuousEndExclusive = endIndex + 1`。
|
||||||
|
6. 三段:Bottom `[0, begin)`、Continuous `[begin, endExclusive)`、Top `[endExclusive, layerCount)`。
|
||||||
|
7. `start == end` 合法。
|
||||||
|
8. Continuous-only 合法,不是 fallback。
|
||||||
|
9. 不允许加入 anchor / marker layer。
|
||||||
|
10. 不允许增加第二套 BOTTOM / CONTINUOUS / TOP metadata 真相源。
|
||||||
|
11. 不允许 Renderer 硬编码 NavSea/AIS/Alarm/Wind/Wave 等业务 Layer ID。
|
||||||
|
12. Visibility 不改变 topology。
|
||||||
|
13. explicit range 有效时不得再混用旧 `startsRetainedDynamicRange()` 作为 topology boundary。
|
||||||
|
14. 不允许把普通 style layer 改造成新的特殊 layer type 来解决测试问题。
|
||||||
|
15. 不做任意 N-range planner。
|
||||||
|
16. 不做多个 Continuous Range。
|
||||||
|
17. 不做频率分类系统。
|
||||||
|
18. 不做 PBF 通用重构。
|
||||||
|
19. 不做 RenderGraph 重构。
|
||||||
|
20. 不加入 Jest。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6. STEP-093 必测场景
|
||||||
|
|
||||||
|
## 6.1 场景 A:Bottom + Continuous + Top
|
||||||
|
|
||||||
|
构造至少:Bottom 为普通 Style/PBF Layer,Continuous 为 Particle / Weather 动态 Layer,Top 为普通 Style/PBF Layer。
|
||||||
|
|
||||||
|
必须验证:
|
||||||
|
|
||||||
|
- explicit range valid;
|
||||||
|
- Bottom present = true;
|
||||||
|
- Top present = true;
|
||||||
|
- retained cache 实际数量 = 2;
|
||||||
|
- Bottom cache 可 build/reuse;
|
||||||
|
- Top cache 可 build/reuse;
|
||||||
|
- 最终画面顺序 `Bottom → Continuous → Top`;
|
||||||
|
- Top 内容能够真实覆盖 Particle;
|
||||||
|
- 无明显黑边、错误透明、旧 cache 或层序颠倒。
|
||||||
|
|
||||||
|
## 6.2 场景 B:Bottom + Continuous
|
||||||
|
|
||||||
|
Continuous 结束于当前 Style 最顶层。
|
||||||
|
|
||||||
|
必须验证:
|
||||||
|
|
||||||
|
- Bottom present = true;
|
||||||
|
- Top present = false;
|
||||||
|
- retained cache 实际数量 = 1;
|
||||||
|
- 不得创建 Top texture/FBO/compositor;
|
||||||
|
- 不存在的 Top 不得阻塞 Fast Path;
|
||||||
|
- 画面顺序正确。
|
||||||
|
|
||||||
|
## 6.3 场景 C:Continuous + Top
|
||||||
|
|
||||||
|
Continuous 从当前 Style 最底层开始。
|
||||||
|
|
||||||
|
必须验证:
|
||||||
|
|
||||||
|
- Bottom present = false;
|
||||||
|
- Top present = true;
|
||||||
|
- retained cache 实际数量 = 1;
|
||||||
|
- 不得创建 Bottom texture/FBO/compositor;
|
||||||
|
- 不存在的 Bottom 不得阻塞 Fast Path;
|
||||||
|
- Top 可正确覆盖 Particle。
|
||||||
|
|
||||||
|
## 6.4 场景 D:Continuous only
|
||||||
|
|
||||||
|
Continuous 覆盖全部 Style Layer。
|
||||||
|
|
||||||
|
必须验证:
|
||||||
|
|
||||||
|
- Bottom present = false;
|
||||||
|
- Top present = false;
|
||||||
|
- explicit range valid = true;
|
||||||
|
- retained cache 实际数量 = 0;
|
||||||
|
- 这是合法 Continuous-only,不得被识别为 fallback;
|
||||||
|
- 不创建 retained texture/FBO/compositor;
|
||||||
|
- Particle/continuous renderer 正常运行。
|
||||||
|
|
||||||
|
## 6.5 场景 E:Single-layer Continuous
|
||||||
|
|
||||||
|
设置 `startLayerId == endLayerId`。
|
||||||
|
|
||||||
|
必须验证:
|
||||||
|
|
||||||
|
- range valid;
|
||||||
|
- startIndex == endIndex;
|
||||||
|
- continuousEndExclusive == startIndex + 1;
|
||||||
|
- 正常参与 Bottom/Top 切分;
|
||||||
|
- 不得 fallback;
|
||||||
|
- 不得被误认为空 range。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7. Z-order 核心视觉验收
|
||||||
|
|
||||||
|
这是 STEP-093 的最高优先级运行态验收。
|
||||||
|
|
||||||
|
必须构造一个肉眼可以清楚确认覆盖关系的场景:
|
||||||
|
|
||||||
|
1. Bottom 中放一个明显底图/填充层;
|
||||||
|
2. Continuous 中运行 Particle;
|
||||||
|
3. Top 中放一个能够明显覆盖 Particle 的普通 Style Layer。
|
||||||
|
|
||||||
|
优先使用已有普通 Style Layer;如果测试环境缺少合适图层,可添加纯测试用普通 Style Layer,但不得引入新的特殊 layer type 或业务硬编码。
|
||||||
|
|
||||||
|
建议 Top 验证至少覆盖:opaque fill、半透明 fill、line;如当前测试环境可行,再加入 symbol/text。
|
||||||
|
|
||||||
|
必须确认:
|
||||||
|
|
||||||
|
- Top 真正绘制在 Particle 上方;
|
||||||
|
- Particle 不会穿透 opaque Top;
|
||||||
|
- 半透明 Top 与 Particle 合成正确;
|
||||||
|
- 无黑边;
|
||||||
|
- 无颜色异常变暗;
|
||||||
|
- 无错误双重 alpha;
|
||||||
|
- 无 cache 旧内容残留。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8. Stable Frame / Cache Reuse 验收
|
||||||
|
|
||||||
|
在 Camera、viewport、Style、普通 PBF/source 内容都不变化,仅 Particle 持续动画时,必须观察:
|
||||||
|
|
||||||
|
- Bottom/Top retained cache build 后持续 reuse;
|
||||||
|
- 不应每个 Particle frame 重建 retained cache;
|
||||||
|
- 不应每 frame 重建 retained texture;
|
||||||
|
- 不应每 frame 重建 FBO/offscreen texture;
|
||||||
|
- 不应每 frame 重建 composite group;
|
||||||
|
- Fast Path 在满足条件时能够持续命中;
|
||||||
|
- diagnostics 能够区分 build / reuse / invalidation。
|
||||||
|
|
||||||
|
如果现有 diagnostics 无法证明资源生命周期,可增加最小 debug-only counter / reason 字段。禁止为了测试引入生产环境高频日志风暴。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 9. Camera / Viewport invalidation
|
||||||
|
|
||||||
|
至少验证:Pan、Zoom、Rotate、Pitch、viewport resize/orientation 或等价尺寸变化,以及测试环境可实际触发时的 pixel ratio 变化。
|
||||||
|
|
||||||
|
每项检查:
|
||||||
|
|
||||||
|
- 旧 retained cache 被正确 invalidation;
|
||||||
|
- 下一帧或正确时点 rebuild;
|
||||||
|
- rebuild 后重新进入 reuse;
|
||||||
|
- 不出现 stale cache;
|
||||||
|
- 不出现错位;
|
||||||
|
- 不出现黑屏;
|
||||||
|
- 不出现 Particle 与地图不同步;
|
||||||
|
- topology 不应无故改变。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 10. Style Mutation / Topology 重新解析
|
||||||
|
|
||||||
|
必须提供可重复测试入口,至少覆盖:
|
||||||
|
|
||||||
|
### 10.1 Continuous 内部变更
|
||||||
|
|
||||||
|
添加 Layer、删除非 endpoint Layer、reorder Continuous 内部 Layer。
|
||||||
|
|
||||||
|
预期:重新解析必要状态;range 仍有效时保持正确 topology;cache 根据需要 invalidation;不产生第二套 Z-order。
|
||||||
|
|
||||||
|
### 10.2 Bottom 内变更
|
||||||
|
|
||||||
|
添加、删除、reorder,检查 Bottom cache invalidation / rebuild / reuse。
|
||||||
|
|
||||||
|
### 10.3 Top 内变更
|
||||||
|
|
||||||
|
添加、删除、reorder,检查 Top cache invalidation / rebuild / reuse。
|
||||||
|
|
||||||
|
### 10.4 Layer 跨边界移动
|
||||||
|
|
||||||
|
将某个普通 Layer 做有代表性的 Bottom → Continuous、Continuous → Top 或 Top → Bottom 跨 range 移动。
|
||||||
|
|
||||||
|
必须验证:
|
||||||
|
|
||||||
|
- ID → index 重新解析;
|
||||||
|
- topology/cache/prepared state 正确 invalidation;
|
||||||
|
- 新 Z-order 正确;
|
||||||
|
- 不使用旧 index;
|
||||||
|
- 不出现 stale cache。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 11. Endpoint 删除与恢复
|
||||||
|
|
||||||
|
删除 start endpoint 时:explicit range 立即 invalid;不 crash;不猜测新 start;不继续使用 stale index/cache;进入既定安全 fallback;diagnostics 有明确 lastReason。
|
||||||
|
|
||||||
|
恢复 start endpoint 时:Native 能重新解析;explicit range 恢复 valid;正确重建需要的 cache;不需要重建整个 Map instance。
|
||||||
|
|
||||||
|
对 end endpoint 做同等验证。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 12. Visibility 不改变 topology
|
||||||
|
|
||||||
|
对 start endpoint、end endpoint 或 Continuous 内普通 Layer 做 visible → hidden → visible。
|
||||||
|
|
||||||
|
必须验证:
|
||||||
|
|
||||||
|
- topology 仍以 Style 顺序解析;
|
||||||
|
- startIndex/endIndex 不因 visibility 改变;
|
||||||
|
- hidden endpoint 仍是 endpoint;
|
||||||
|
- 不自动收缩 Continuous Range;
|
||||||
|
- ON/OFF 只作为内容变化处理;
|
||||||
|
- 如需 cache invalidation,应是内容 invalidation,不得变成新的 topology 规则。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 13. Invalid Explicit Range
|
||||||
|
|
||||||
|
至少验证:
|
||||||
|
|
||||||
|
1. startLayerId 不存在;
|
||||||
|
2. endLayerId 不存在;
|
||||||
|
3. startIndex > endIndex;
|
||||||
|
4. Style 尚未 ready 时配置;
|
||||||
|
5. 空 ID / 未配置(按实际 API 合法性处理)。
|
||||||
|
|
||||||
|
预期:不 crash;不猜测;不复用 stale cache;不按 Layer 类型自动修正;进入明确安全 fallback;diagnostics 显示 invalid 原因;条件恢复后能够重新进入 explicit range。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 14. Particle Fast Path
|
||||||
|
|
||||||
|
## 14.1 可命中场景
|
||||||
|
|
||||||
|
Continuous 内只保留当前 Fast Path 已支持的动态内容。
|
||||||
|
|
||||||
|
检查:Fast Path 可持续命中;Bottom/Top 的存在与否只影响实际存在的 retained cache;0 cache 的 Continuous-only 不得因为“没有 retained cache”而错误阻止 Fast Path。
|
||||||
|
|
||||||
|
## 14.2 Fail Closed 场景
|
||||||
|
|
||||||
|
在 Continuous 中加入当前 Fast Path 不支持的普通/未知 Continuous Layer。
|
||||||
|
|
||||||
|
必须验证:
|
||||||
|
|
||||||
|
- Fast Path 退回 full CPU frame;
|
||||||
|
- 不 crash;
|
||||||
|
- 不错误跳过该 Layer;
|
||||||
|
- Continuous Range 本身保持 valid;
|
||||||
|
- Bottom / Top retained cache Z-order 仍正确;
|
||||||
|
- 不允许为了 Fast Path 强行把未知 Layer 移出 Continuous。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 15. Top Alpha / Premultiplied Alpha 验收
|
||||||
|
|
||||||
|
必须重点看 Top retained cache 合成:transparent background、opaque fill、semi-transparent fill、line,以及可行时 symbol/text。
|
||||||
|
|
||||||
|
检查:透明区域正确露出 Particle;半透明区域正常叠加;opaque 区域完整覆盖;无黑色边缘;无 halo;无颜色被错误乘两次;无残影;OpenGL 与 Metal 设计保持一致。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 16. 长时间运行与资源稳定性
|
||||||
|
|
||||||
|
在测试环境允许情况下进行持续运行测试,至少包括:Particle 连续播放、多次 Pan/Zoom/Rotate、多次 range 配置变化、endpoint 删除/恢复、visibility 切换、style mutation、再次稳定运行。
|
||||||
|
|
||||||
|
观察:
|
||||||
|
|
||||||
|
- 无持续 GPU resource 增长迹象;
|
||||||
|
- retained texture/FBO 不因 stable frame 每帧新建;
|
||||||
|
- 无明显内存持续增长;
|
||||||
|
- 无黑屏;
|
||||||
|
- 无闪屏;
|
||||||
|
- 无 stale cache;
|
||||||
|
- 无偶发层序颠倒;
|
||||||
|
- 无 Particle 停止后恢复不了的问题。
|
||||||
|
|
||||||
|
如果当前工程缺少可靠 GPU memory 数字来源,不要求伪造数值;允许使用资源创建/释放 counter、diagnostics 和稳定运行现象作为当前阶段证据,并明确限制。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 17. Diagnostics 要求
|
||||||
|
|
||||||
|
STEP-093 的测试入口必须能清楚观察至少以下信息:
|
||||||
|
|
||||||
|
### Range
|
||||||
|
|
||||||
|
- configured
|
||||||
|
- valid
|
||||||
|
- startLayerId
|
||||||
|
- endLayerId
|
||||||
|
- startIndex
|
||||||
|
- endIndex
|
||||||
|
- continuousBegin
|
||||||
|
- continuousEndExclusive
|
||||||
|
- fallback / lastReason
|
||||||
|
|
||||||
|
### Bottom
|
||||||
|
|
||||||
|
- present
|
||||||
|
- cache state
|
||||||
|
- build count
|
||||||
|
- reuse count
|
||||||
|
- invalidation count
|
||||||
|
- last invalidation reason
|
||||||
|
|
||||||
|
### Top
|
||||||
|
|
||||||
|
- present
|
||||||
|
- cache state
|
||||||
|
- build count
|
||||||
|
- reuse count
|
||||||
|
- invalidation count
|
||||||
|
- last invalidation reason
|
||||||
|
|
||||||
|
### Fast Path
|
||||||
|
|
||||||
|
- eligible / active
|
||||||
|
- fallback reason
|
||||||
|
- full CPU frame count 或等价诊断(若现有已有)
|
||||||
|
- unknown continuous layer 导致 fail-closed 的可观察原因
|
||||||
|
|
||||||
|
如当前已有字段足够,不得重复造一套 diagnostics。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 18. Test UI / Playground 要求
|
||||||
|
|
||||||
|
优先复用现有 Particle Test App / validation UI。
|
||||||
|
|
||||||
|
如果需要新增 STEP-093 面板,要求:
|
||||||
|
|
||||||
|
- 不影响生产 MapLibre API 语义;
|
||||||
|
- 不引入 NavSea 业务逻辑;
|
||||||
|
- 场景按钮命名明确;
|
||||||
|
- 能一键切换四种 topology;
|
||||||
|
- 能设置 single-layer range;
|
||||||
|
- 能触发 invalid range;
|
||||||
|
- 能隐藏/显示 endpoints;
|
||||||
|
- 能删除/恢复 endpoint;
|
||||||
|
- 能触发代表性的 reorder / cross-boundary move;
|
||||||
|
- 能显示当前 diagnostics;
|
||||||
|
- 能恢复默认测试场景。
|
||||||
|
|
||||||
|
测试 UI 本身不应成为复杂新框架。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 19. Android 运行态验收
|
||||||
|
|
||||||
|
Android 作为本 STEP 首要完整运行态验收平台。
|
||||||
|
|
||||||
|
Codex 必须准备:
|
||||||
|
|
||||||
|
- 明确的 build/install/run 手工命令;
|
||||||
|
- STEP-093 测试入口位置;
|
||||||
|
- 每项场景的操作步骤;
|
||||||
|
- 需要采集的日志/diagnostics;
|
||||||
|
- PASS/FAIL 判定。
|
||||||
|
|
||||||
|
实际 Android Gradle / APK / ADB / 真机或模拟器操作由 RDC / 用户执行。
|
||||||
|
|
||||||
|
不得由 Codex 仅凭代码静态分析代替 Android 实机/模拟器结论。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 20. iOS / Metal 运行态验收
|
||||||
|
|
||||||
|
Android 完整通过后,再进行 iOS / Metal 对等测试。
|
||||||
|
|
||||||
|
至少需要验证:四 topology、Single-layer Continuous、Top 覆盖 Particle、Stable cache reuse、Camera invalidation、endpoint missing/recovery、visibility、Fast Path fail-closed、Top alpha。
|
||||||
|
|
||||||
|
如果本 STEP 暂未执行 iOS runtime:
|
||||||
|
|
||||||
|
- 报告必须明确写 `WAITING_IOS_RUNTIME_VALIDATION`;
|
||||||
|
- Android PASS 不得自动推导为 iOS PASS;
|
||||||
|
- Metal native build PASS 也不能替代 Metal runtime rendering PASS。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 21. 编译与测试原则
|
||||||
|
|
||||||
|
1. MapLibre Native 的 Android/iOS 开发与编译统一使用 Mac。
|
||||||
|
2. 不安排 Linux Native 编译。
|
||||||
|
3. noisy native build 遵循 Mac/Codex 全局构建输出节流规则。
|
||||||
|
4. 不加入 Jest。
|
||||||
|
5. 可运行已有 Native 精确测试;如代码变更影响 STEP-092 核心逻辑,应重跑相关现有 Native tests。
|
||||||
|
6. 不为了“让测试绿”删除或弱化 STEP-092 已有覆盖。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 22. 发现问题时的处理规则
|
||||||
|
|
||||||
|
### 22.1 小型、事实明确、局部问题
|
||||||
|
|
||||||
|
例如测试 UI 接线错误、diagnostics 漏展示、明确计数器错误、局部 invalidation reason 错误、编译错误、测试工具小缺陷,允许直接修复,并重新验收受影响项目。
|
||||||
|
|
||||||
|
### 22.2 架构级问题
|
||||||
|
|
||||||
|
如果发现需要修改 Continuous Range 数学定义、Style 顺序唯一 Z-order、0/1/2 retained cache 模型、renderer 大范围遍历结构、Fast Path 核心模型、第二套 layer metadata、新特殊 layer type、新 RenderGraph 或大规模 GPU cache redesign,必须停止当前修复扩张,先生成明确的“STEP-093 架构阻塞报告”,说明实际问题、复现方式、为什么 STEP-092 架构无法满足、建议方案和影响范围。
|
||||||
|
|
||||||
|
不得在运行态测试任务中擅自重构架构。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 23. 验收矩阵
|
||||||
|
|
||||||
|
最终报告必须至少给出以下矩阵:
|
||||||
|
|
||||||
|
| 类别 | 场景 | Android | iOS/Metal | 关键证据 | 结论 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| Topology | Bottom+Continuous+Top | | | | |
|
||||||
|
| Topology | Bottom+Continuous | | | | |
|
||||||
|
| Topology | Continuous+Top | | | | |
|
||||||
|
| Topology | Continuous-only | | | | |
|
||||||
|
| Range | Single-layer Continuous | | | | |
|
||||||
|
| Z-order | Top covers Particle | | | | |
|
||||||
|
| Cache | Stable reuse | | | | |
|
||||||
|
| Invalidation | Pan/Zoom/Rotate/Pitch | | | | |
|
||||||
|
| Mutation | Continuous internal mutation | | | | |
|
||||||
|
| Mutation | Cross-range move | | | | |
|
||||||
|
| Endpoint | start missing/recovery | | | | |
|
||||||
|
| Endpoint | end missing/recovery | | | | |
|
||||||
|
| Visibility | hidden endpoint topology | | | | |
|
||||||
|
| Invalid | missing/reversed range | | | | |
|
||||||
|
| Fast Path | eligible | | | | |
|
||||||
|
| Fast Path | unknown layer fail-closed | | | | |
|
||||||
|
| Alpha | Top premultiplied composite | | | | |
|
||||||
|
| Stability | repeated operations / long run | | | | |
|
||||||
|
|
||||||
|
状态只允许使用清楚枚举,例如:PASS、FAIL、NOT RUN、BLOCKED、WAITING_ANDROID_RUNTIME_VALIDATION、WAITING_IOS_RUNTIME_VALIDATION。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 24. STEP-093 完成条件
|
||||||
|
|
||||||
|
只有满足下面条件,才允许把 STEP-093 标记为 `CLOSED / PASS`:
|
||||||
|
|
||||||
|
1. Android 运行态核心矩阵实际执行并通过;
|
||||||
|
2. 四种 topology 实际验证;
|
||||||
|
3. Single-layer Continuous 实际验证;
|
||||||
|
4. Top 覆盖 Particle 实际验证;
|
||||||
|
5. 0/1/2 cache 与 diagnostics 一致;
|
||||||
|
6. Stable reuse 实际验证;
|
||||||
|
7. Camera invalidation → rebuild → reuse 实际验证;
|
||||||
|
8. endpoint missing/recovery 实际验证;
|
||||||
|
9. visibility 不改变 topology 实际验证;
|
||||||
|
10. invalid range fallback 实际验证;
|
||||||
|
11. Fast Path eligible + fail-closed 实际验证;
|
||||||
|
12. Top alpha 无明显错误;
|
||||||
|
13. 发现的阻塞性缺陷已解决并回归;
|
||||||
|
14. 文档与 Git 收尾完整;
|
||||||
|
15. `HEAD == origin/particle/main`;
|
||||||
|
16. 工作区 clean。
|
||||||
|
|
||||||
|
如果 Android 已全部通过但 iOS 尚未运行,可记录为:`ANDROID CLOSED / PASS + IOS WAITING_RUNTIME_VALIDATION`。不得把未运行的平台写成 PASS。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 25. 必做输出
|
||||||
|
|
||||||
|
本任务所有需要 ChatGPT/RDC 后续审查的文件必须由 Codex 在任务中实际生成,不得在结束后临时补要。
|
||||||
|
|
||||||
|
## 25.1 STEP 记录
|
||||||
|
|
||||||
|
生成:
|
||||||
|
|
||||||
|
`docs/50_Step记录/STEP-093_ContinuousRange运行态综合验收_v1.md`
|
||||||
|
|
||||||
|
如果同名主题已有版本,递增为 v2/v3……,不得覆盖旧版。
|
||||||
|
|
||||||
|
内容至少包括:起始 Git 基线、调查到的实际测试入口、新增/修改内容、Android/iOS 实际执行状态、验收矩阵、问题与修复、未执行项、当前状态、下一步。
|
||||||
|
|
||||||
|
## 25.2 Codex 完成报告
|
||||||
|
|
||||||
|
生成:
|
||||||
|
|
||||||
|
`docs/报告/STEP-093_ContinuousRange运行态综合验收_Codex完成报告_v1.md`
|
||||||
|
|
||||||
|
同主题已有版本时递增版本号。
|
||||||
|
|
||||||
|
报告至少包含:任务目标、修改文件、测试入口、diagnostics、build/test 实际结果、Android runtime 实际结果、iOS runtime 实际结果、PASS/FAIL/WAITING 状态、Git commit、push、Final HEAD、origin/particle/main、git status、未执行事项、风险与已知限制。
|
||||||
|
|
||||||
|
## 25.3 开发记录
|
||||||
|
|
||||||
|
追加:
|
||||||
|
|
||||||
|
`粒子开发记录.md`
|
||||||
|
|
||||||
|
只追加,不覆盖历史内容。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 26. Git 规则
|
||||||
|
|
||||||
|
严格遵守项目 Git 规则:
|
||||||
|
|
||||||
|
- 只提交本任务相关文件;
|
||||||
|
- 禁止 `git add -A`;
|
||||||
|
- 禁止 `git add .`;
|
||||||
|
- 禁止 `git reset --hard`;
|
||||||
|
- 禁止 `git clean`;
|
||||||
|
- 不触碰无关 dirty/untracked;
|
||||||
|
- 功能代码与文档可按合理 scoped commit 拆分;
|
||||||
|
- push 到 `origin/particle/main`;
|
||||||
|
- push 后确认 `HEAD == origin/particle/main`;
|
||||||
|
- 最终确认 `git status --short`;
|
||||||
|
- push 失败不得标记任务完成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 27. Codex 执行终态规则
|
||||||
|
|
||||||
|
Codex 本轮结束时必须给出真实状态,而不是默认写 PASS。
|
||||||
|
|
||||||
|
建议状态:
|
||||||
|
|
||||||
|
- `IMPLEMENTED / WAITING_RUNTIME_VALIDATION`
|
||||||
|
- `PARTIAL / WAITING_ANDROID_RUNTIME_VALIDATION`
|
||||||
|
- `ANDROID PASS / WAITING_IOS_RUNTIME_VALIDATION`
|
||||||
|
- `CLOSED / PASS`
|
||||||
|
- `BLOCKED`
|
||||||
|
- `FAILED`
|
||||||
|
|
||||||
|
如果只是完成测试工具与代码准备,而 RDC/用户尚未真正运行 Android,则只能标记等待运行态验收。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 28. 明确不做
|
||||||
|
|
||||||
|
STEP-093 不做:
|
||||||
|
|
||||||
|
- 任意 N-range planner;
|
||||||
|
- 多 Continuous Range;
|
||||||
|
- frequency class 自动分类;
|
||||||
|
- 自动 Z-order;
|
||||||
|
- anchor/marker layer;
|
||||||
|
- 第二套 BOTTOM/CONTINUOUS/TOP metadata;
|
||||||
|
- NavSea AIS 专用优化;
|
||||||
|
- Alarm 专用优化;
|
||||||
|
- Weather provider;
|
||||||
|
- Route ETA;
|
||||||
|
- LandMask 业务系统;
|
||||||
|
- Wave/Tide 业务系统;
|
||||||
|
- PBF 通用重构;
|
||||||
|
- RenderGraph 大改;
|
||||||
|
- 新粒子算法;
|
||||||
|
- 粒子视觉美化;
|
||||||
|
- unrelated performance tuning;
|
||||||
|
- Jest。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 29. 最终核心判定
|
||||||
|
|
||||||
|
STEP-093 最终不是检查“按钮能不能点”,而是验证 STEP-092 的核心架构在真实渲染中确实成立:
|
||||||
|
|
||||||
|
> 普通地图内容可以被合理拆成 Bottom / Top retained cache,Particle 所在 Continuous Range 保持高频实时绘制;Style 顺序仍然决定最终 Z-order;不存在的 retained range 不产生资源;缓存只在需要时失效,并在稳定状态持续复用。
|
||||||
|
|
||||||
|
只要这个结论还没有通过真实运行态证据建立,STEP-093 就不能写成最终 `CLOSED / PASS`。
|
||||||
599
docs/40_任务/STEP-094_通用RenderSegment分段渲染与Style分类诊断_Codex任务_v1.md
Normal file
599
docs/40_任务/STEP-094_通用RenderSegment分段渲染与Style分类诊断_Codex任务_v1.md
Normal file
@@ -0,0 +1,599 @@
|
|||||||
|
# STEP-094 通用 Render Segment 分段渲染与 Style 分类诊断 — Codex 任务 v1
|
||||||
|
|
||||||
|
## 0. 任务目标
|
||||||
|
|
||||||
|
从已冻结的 Particle V12 `Bottom Retained / Continuous / Top Retained` 单 Continuous Range 模型,演进到最终通用模型:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Style Z-order
|
||||||
|
↓
|
||||||
|
连续 layerIndex
|
||||||
|
↓
|
||||||
|
若干连续 Render Segment
|
||||||
|
↓
|
||||||
|
每个 Segment 的执行策略仅为:
|
||||||
|
CONTINUOUS 或 RETAINED
|
||||||
|
```
|
||||||
|
|
||||||
|
Style 顺序继续是唯一 Z-order 事实来源。Renderer 不理解 LOW / MEDIUM / HIGH,也不理解 AIS、Route、地图、Weather 等业务语义。
|
||||||
|
|
||||||
|
本 STEP 同时必须建立新的 pull-based Segment Diagnostics,能够取回“当前 Style → Segment → Layer”的真实分类快照,供后续人工判断 Segment 划分是否合理。
|
||||||
|
|
||||||
|
## 1. 当前冻结基线
|
||||||
|
|
||||||
|
工作目录:`/Users/tei/dev/MapLibreParticleLab`
|
||||||
|
目标分支:`particle/main`
|
||||||
|
|
||||||
|
当前已知 V12 冻结基线:
|
||||||
|
- HEAD:`7eaff6e12360e26591dceb5e011129eeab7ba4c7`
|
||||||
|
- Commit:`release: finalize unified particle.12`
|
||||||
|
- Tag:`maplibre-particle/rn/13.4.1-particle.12`
|
||||||
|
- `HEAD == origin/particle/main`
|
||||||
|
- 工作区 clean
|
||||||
|
|
||||||
|
开始前必须重新执行 fetch/status/HEAD 检查,以实际仓库为准。禁止 reset/clean,禁止回退、移动、覆盖 V12 Tag。STEP-094 是 V12 之后的新开发,不修改 V12 已发布事实。
|
||||||
|
|
||||||
|
必须先读取:
|
||||||
|
- 根目录 `AGENTS.md`
|
||||||
|
- `ARCHITECTURE.md`
|
||||||
|
- STEP-092 / STEP-093 任务、Step 记录与完成报告
|
||||||
|
- V12 当前 Continuous Range、Retained Cache、Fast Path、Android/iOS/RN bridge 实现
|
||||||
|
- 当前 `RendererRuntime.ts`、`types.ts`、`NativeWeatherRuntimeBridge.ts`
|
||||||
|
|
||||||
|
## 2. 最终架构原则
|
||||||
|
|
||||||
|
1. Style Layer 顺序是唯一 Z-order 真相。
|
||||||
|
2. 整个 Style 必须按连续 layerIndex 被完整划分为 1..N 个 Render Segment。
|
||||||
|
3. 每个 Segment 只声明 `RETAINED` 或 `CONTINUOUS`。
|
||||||
|
4. 允许多个 RETAINED、多个 CONTINUOUS,且可按 Style 顺序任意交错。
|
||||||
|
5. 相邻同策略 Segment 不得自动合并。
|
||||||
|
6. 每个 RETAINED Segment 独立拥有 cache / revision / invalidation / rebuild / GPU resource。
|
||||||
|
7. CONTINUOUS Segment 不创建 retained texture/FBO/compositor。
|
||||||
|
8. Renderer 不自动分析 Layer 更新频率,不自动调整 Z-order。
|
||||||
|
9. App 通过 Style 顺序 + Segment boundary 表达分类。
|
||||||
|
10. 不建立 LOW/MEDIUM/HIGH 第二套业务分类。
|
||||||
|
11. 不硬编码 NavSea Layer ID。
|
||||||
|
12. 不增加空白 Anchor / Marker Layer。
|
||||||
|
|
||||||
|
V12 三明治只是本模型的一个特例:`RETAINED → CONTINUOUS → RETAINED`。
|
||||||
|
|
||||||
|
## 3. 命名约束
|
||||||
|
|
||||||
|
MapLibre 现有代码已经大量使用 shader/bucket 的 `Segment` / `SegmentVector`。本任务禁止新增裸名称 `Segment` 造成语义冲突。
|
||||||
|
|
||||||
|
新模型命名必须明确属于 Renderer execution topology,例如:
|
||||||
|
- `RenderSegmentStrategy`
|
||||||
|
- `RenderSegmentDefinition`
|
||||||
|
- `RenderSegmentPlan`
|
||||||
|
- `ResolvedRenderSegment`
|
||||||
|
- `RenderSegmentDiagnostics`
|
||||||
|
- `RetainedSegmentCache`
|
||||||
|
|
||||||
|
具体文件名可按当前代码结构调整,但必须避免与 geometry/shader Segment 混淆。
|
||||||
|
|
||||||
|
## 4. Segment Plan 公共语义
|
||||||
|
|
||||||
|
推荐公共模型:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RenderSegmentDefinition
|
||||||
|
strategy: RETAINED | CONTINUOUS
|
||||||
|
startLayerId?: string
|
||||||
|
```
|
||||||
|
|
||||||
|
规则:
|
||||||
|
- definitions 至少 1 个。
|
||||||
|
- 第 0 个 Segment 从 Style index 0 开始,`startLayerId` 必须省略/为空。
|
||||||
|
- 第 1..N-1 个 Segment 必须提供非空 `startLayerId`。
|
||||||
|
- startLayerId 必须引用当前 Style 中已经存在的普通 Layer。
|
||||||
|
- Native 根据当前 Style 解析真实 layerIndex。
|
||||||
|
- App/RN 不传数字 index。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[
|
||||||
|
{ strategy: RETAINED },
|
||||||
|
{ strategy: CONTINUOUS, startLayerId: "rn-weather-color" },
|
||||||
|
{ strategy: RETAINED, startLayerId: "land-area" },
|
||||||
|
{ strategy: RETAINED, startLayerId: "ais-layer" }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
解析后:
|
||||||
|
- Segment 0 = `[0, index(rn-weather-color))`
|
||||||
|
- Segment 1 = `[index(rn-weather-color), index(land-area))`
|
||||||
|
- Segment 2 = `[index(land-area), index(ais-layer))`
|
||||||
|
- Segment 3 = `[index(ais-layer), layerCount)`
|
||||||
|
|
||||||
|
Segment 2/3 虽然都是 RETAINED,也必须保持两个独立 cache,不得合并。
|
||||||
|
|
||||||
|
这种定义保证整个 Style 完整覆盖;插入普通 Layer 后依据 Style 位置自然进入对应 Segment;最顶端新增 Layer自动属于最后一个 Segment。
|
||||||
|
|
||||||
|
## 5. Plan 合法性与 resolve
|
||||||
|
|
||||||
|
有效计划必须满足:
|
||||||
|
- Style ready;
|
||||||
|
- 所有 boundary Layer 存在;
|
||||||
|
- boundary index 严格递增;
|
||||||
|
- 无重复 boundary;
|
||||||
|
- 每个 resolved Segment 非空;
|
||||||
|
- 最终覆盖 `[0, layerCount)`;
|
||||||
|
- 不重叠、不留 gap。
|
||||||
|
|
||||||
|
hidden / visibility=none 的 boundary Layer 仍参与 topology。Visibility 变化不得改变 Segment membership。
|
||||||
|
|
||||||
|
以下情况 plan invalid:
|
||||||
|
- boundary missing;
|
||||||
|
- boundary 顺序反转或重复;
|
||||||
|
- Style 未 ready;
|
||||||
|
- 空 definitions;
|
||||||
|
- 非法 strategy;
|
||||||
|
- JSON/schema 非法。
|
||||||
|
|
||||||
|
invalid 时不得 crash、不得猜 boundary、不得复用 stale cache。必须释放/隔离失效 topology 的 retained resource,安全走 full renderer,并在 diagnostics 给出明确 reason。Style 后续恢复合法时允许自动重新 resolve。
|
||||||
|
|
||||||
|
必须有 monotonic `configRevision`。Style add/remove/reorder 后重新 resolve;实际 begin/end 变化时推进 `topologyRevision`。Visibility 变化不得推进 topologyRevision。
|
||||||
|
|
||||||
|
## 6. Renderer 执行模型
|
||||||
|
|
||||||
|
Resolved Segment 按 Style 顺序执行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
for segment in resolvedSegments:
|
||||||
|
RETAINED -> build/reuse cache -> composite
|
||||||
|
CONTINUOUS -> direct render
|
||||||
|
```
|
||||||
|
|
||||||
|
逻辑上按 Segment 顺序组合,但实际实现必须继续满足 MapLibre 原有 RenderPass 语义。
|
||||||
|
|
||||||
|
必须保持:
|
||||||
|
- 全 Style 原始 global layerIndex;
|
||||||
|
- 原始 ordinal;
|
||||||
|
- `parameters.currentLayer`;
|
||||||
|
- `depthRangeSize`;
|
||||||
|
- Opaque reverse traversal;
|
||||||
|
- Translucent forward traversal。
|
||||||
|
|
||||||
|
禁止每个 Segment 从 0 重新编号,禁止为了 Segment 模型重新排序 Style Layer。
|
||||||
|
|
||||||
|
## 7. RETAINED Segment 独立 Cache
|
||||||
|
|
||||||
|
每个 RETAINED Segment 必须拥有独立:
|
||||||
|
- cache state;
|
||||||
|
- content revision;
|
||||||
|
- build/reuse/invalidation count;
|
||||||
|
- last invalidation reason;
|
||||||
|
- texture/compositor create count;
|
||||||
|
- resource release count。
|
||||||
|
|
||||||
|
GPU resource 必须 lazy 创建。CONTINUOUS Segment 绝不能因为占位而创建 retained texture。
|
||||||
|
|
||||||
|
Plan/topology 改变后,不能证明安全复用的旧 cache 必须释放并重建;stale segment cache 绝不能错误 composite。本 STEP 不要求复杂 cache migration,correctness-first。
|
||||||
|
|
||||||
|
## 8. Invalidation 粒度
|
||||||
|
|
||||||
|
目标是 Segment 级 invalidation。
|
||||||
|
|
||||||
|
能够明确定位影响 Segment 时,只 invalidate 受影响 RETAINED Segment;其他 RETAINED Segment 保持 valid/reuse。
|
||||||
|
|
||||||
|
LayerMutation 若明确属于某 Segment,应只使该 Segment dirty。Source/Tile mutation 若能从现有 RenderLayer/Source 关系安全确定影响的多个 Segment,应 invalidate 对应集合。
|
||||||
|
|
||||||
|
若无法可靠确定影响范围,允许保守 invalidate 全部 RETAINED Segment。本 STEP 不建立复杂通用 dependency graph。
|
||||||
|
|
||||||
|
camera/transform/viewport/resize/pixel ratio、style reload、image/sprite/glyph、placement/symbol fade/transition、context reset、debug options、unsupported 3D、resource/compositor failure 等无法证明局部安全时可 invalidate 全部 retained。
|
||||||
|
|
||||||
|
完成报告必须区分“已实现的 segment-local invalidation”和“仍采用全 retained invalidation 的原因”。
|
||||||
|
|
||||||
|
## 9. Cache ON/OFF
|
||||||
|
|
||||||
|
现有 Cache ON/OFF 总开关继续有效。
|
||||||
|
|
||||||
|
Cache OFF:
|
||||||
|
- Segment Plan 仍然 resolve;
|
||||||
|
- Diagnostics 仍能看到 Style/Segment/Layer 分类;
|
||||||
|
- RETAINED Segment 不错误复用 retained GPU cache;
|
||||||
|
- Renderer 安全走非 retained 路径。
|
||||||
|
|
||||||
|
Cache ON:
|
||||||
|
- 每个 RETAINED Segment 独立 build/reuse;
|
||||||
|
- CONTINUOUS Segment 直接绘制。
|
||||||
|
|
||||||
|
关闭所有 CONTINUOUS Layer 的 visibility 不得删除 Segment topology。
|
||||||
|
|
||||||
|
## 10. V12 兼容入口
|
||||||
|
|
||||||
|
不得破坏现有:
|
||||||
|
- `setRetainedUnderlayEnabled()`
|
||||||
|
- `setContinuousLayerRange(startLayerId,endLayerId)`
|
||||||
|
- `getRetainedUnderlayDiagnostics()`
|
||||||
|
|
||||||
|
新内部实现应尽量只有一套 Segment topology。
|
||||||
|
|
||||||
|
`setContinuousLayerRange(start,end)` 应作为 compatibility adapter,转换成等价 RenderSegmentPlan:
|
||||||
|
- start 前存在 → RETAINED
|
||||||
|
- start..end → CONTINUOUS
|
||||||
|
- end 后存在 → RETAINED
|
||||||
|
|
||||||
|
自然退化成 V12 原有 1/2/3 Range。
|
||||||
|
|
||||||
|
旧 API 与新 Segment Plan 不得同时形成两套 Renderer topology。最后一次显式 topology 配置必须只有一个 active plan,并从 diagnostics 可见 `configSource`。
|
||||||
|
|
||||||
|
旧 `getRetainedUnderlayDiagnostics()` 保留兼容摘要;新的多 Segment 事实以新 Diagnostics API 为准。
|
||||||
|
|
||||||
|
## 11. 新 RN / Native API
|
||||||
|
|
||||||
|
新增稳定公共语义:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RendererRuntime.setRenderSegmentPlan(reactTag, plan)
|
||||||
|
RendererRuntime.getRenderSegmentDiagnostics(reactTag)
|
||||||
|
```
|
||||||
|
|
||||||
|
建议 TS:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RenderSegmentStrategy = "RETAINED" | "CONTINUOUS"
|
||||||
|
|
||||||
|
RenderSegmentDefinition = {
|
||||||
|
strategy: RenderSegmentStrategy
|
||||||
|
startLayerId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderSegmentPlan = {
|
||||||
|
schemaVersion: 1
|
||||||
|
segments: readonly RenderSegmentDefinition[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
RN public API 必须是 typed object。TurboModule ABI 可使用 JSON string 作为 plan/snapshot 内部传输,避免可扩展嵌套数组变成脆弱 parallel arrays。若采用 JSON,必须有 schemaVersion 与严格校验;public TS 不暴露 raw JSON。
|
||||||
|
|
||||||
|
必须沿当前真实链路扩展:
|
||||||
|
`RendererRuntime → NativeWeatherRuntimeBridge/TurboModule → Android/iOS package bridge → MapLibre platform API → Core Renderer state`。
|
||||||
|
|
||||||
|
不得新建第二套独立 RN bridge。
|
||||||
|
|
||||||
|
## 12. Segment Diagnostics — 强制要求
|
||||||
|
|
||||||
|
这是 STEP-094 的强制验收项,不是附加日志。
|
||||||
|
|
||||||
|
新增 pull-based `getRenderSegmentDiagnostics()`,必须能够取回当前真实 Style 的:
|
||||||
|
1. Style 全 Layer 顺序;
|
||||||
|
2. 当前 resolved Segment 顺序;
|
||||||
|
3. 每个 Segment 的 strategy;
|
||||||
|
4. 每个 Segment 的 begin/end;
|
||||||
|
5. 每个 Segment 内实际包含的 Layer;
|
||||||
|
6. 每个 RETAINED Segment 的 cache 状态与统计。
|
||||||
|
|
||||||
|
Diagnostics 只能按需读取。禁止 per-frame 打印完整 Style/Segment/Layer,禁止每帧构造大 JSON/大字符串。
|
||||||
|
|
||||||
|
## 13. Diagnostics Snapshot 最低字段
|
||||||
|
|
||||||
|
顶层至少:
|
||||||
|
|
||||||
|
```text
|
||||||
|
schemaVersion
|
||||||
|
enabled
|
||||||
|
configured
|
||||||
|
valid
|
||||||
|
configSource
|
||||||
|
configRevision
|
||||||
|
topologyRevision
|
||||||
|
styleLayerCount
|
||||||
|
segmentCount
|
||||||
|
retainedSegmentCount
|
||||||
|
continuousSegmentCount
|
||||||
|
coverageComplete
|
||||||
|
fallback
|
||||||
|
lastReason
|
||||||
|
fastPathEligible
|
||||||
|
fastPathActive
|
||||||
|
fastPathFallbackReason
|
||||||
|
fastPathHit
|
||||||
|
fastPathMiss
|
||||||
|
fullCpuFrame
|
||||||
|
```
|
||||||
|
|
||||||
|
`configSource` 至少区分:
|
||||||
|
- `explicit-render-segment-plan`
|
||||||
|
- `continuous-range-compat`
|
||||||
|
- `unconfigured/legacy`(若仍存在)
|
||||||
|
|
||||||
|
必须提供完整 ordered `styleLayers[]`,用于 plan invalid 时仍可人工分析 Style。
|
||||||
|
|
||||||
|
## 14. Style Layer Diagnostics
|
||||||
|
|
||||||
|
每个 `styleLayers[]` / Segment 内 `layers[]` 至少返回:
|
||||||
|
|
||||||
|
```text
|
||||||
|
layerIndex
|
||||||
|
layerId
|
||||||
|
layerType
|
||||||
|
visible
|
||||||
|
```
|
||||||
|
|
||||||
|
如当前 Style/Core 可低成本稳定取得,还应返回:
|
||||||
|
- `sourceId`
|
||||||
|
- `sourceLayer`
|
||||||
|
|
||||||
|
不适用时可为空/null,禁止伪造。Layer Diagnostics 只描述通用 Style 信息,禁止加入 AIS/Route/Wind 等业务判断。
|
||||||
|
|
||||||
|
## 15. 每个 Segment Diagnostics
|
||||||
|
|
||||||
|
每个 Segment 至少:
|
||||||
|
|
||||||
|
```text
|
||||||
|
segmentIndex
|
||||||
|
strategy
|
||||||
|
configuredStartLayerId
|
||||||
|
beginIndex
|
||||||
|
endExclusive
|
||||||
|
resolvedStartLayerId
|
||||||
|
resolvedEndLayerId
|
||||||
|
layerCount
|
||||||
|
layers[]
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 `strategy=RETAINED`,还必须有:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cacheState
|
||||||
|
contentRevision
|
||||||
|
build
|
||||||
|
reuse
|
||||||
|
invalidation
|
||||||
|
lastInvalidationReason
|
||||||
|
textureCreate
|
||||||
|
compositeCreate
|
||||||
|
resourceRelease
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 `strategy=CONTINUOUS`,不得伪装成 disabled retained cache。retained-only 字段应为 null/not-applicable,或由 TS discriminated union 排除。
|
||||||
|
|
||||||
|
## 16. Diagnostics 一致性
|
||||||
|
|
||||||
|
当 plan valid:
|
||||||
|
- `styleLayers.length == styleLayerCount`
|
||||||
|
- Segment 按 segmentIndex/beginIndex 严格递增
|
||||||
|
- 所有 `segment.layers[]` 拼接后必须与 `styleLayers[]` 完全一致
|
||||||
|
- 每个 Style Layer 必须且只能属于一个 Segment
|
||||||
|
- `segment.layerCount == segment.layers.length`
|
||||||
|
- `coverageComplete=true`
|
||||||
|
|
||||||
|
当 plan invalid:
|
||||||
|
- 仍尽可能返回当前 ordered styleLayers
|
||||||
|
- segments 可为空或只返回已安全解析部分,但必须 `valid=false`
|
||||||
|
- 禁止把猜测结果标成 valid
|
||||||
|
|
||||||
|
## 17. DEBUG 使用目标
|
||||||
|
|
||||||
|
必须能取回类似:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Segment 0 RETAINED [0..18)
|
||||||
|
background
|
||||||
|
water
|
||||||
|
coastline
|
||||||
|
|
||||||
|
Segment 1 CONTINUOUS [18..20)
|
||||||
|
rn-weather-color
|
||||||
|
rn-weather-particles
|
||||||
|
|
||||||
|
Segment 2 RETAINED [20..27)
|
||||||
|
land-area
|
||||||
|
navigation-marks
|
||||||
|
route
|
||||||
|
|
||||||
|
Segment 3 RETAINED [27..30)
|
||||||
|
ais
|
||||||
|
radar
|
||||||
|
alarm
|
||||||
|
```
|
||||||
|
|
||||||
|
此信息只用于人工分析 Style 分类是否合理。Renderer 不根据 Debug 结果自动重新分类。
|
||||||
|
|
||||||
|
设计文档必须给出至少一个完整 JSON/TS snapshot 示例。
|
||||||
|
|
||||||
|
## 18. Particle Fast Path
|
||||||
|
|
||||||
|
V12 已有 Particle Fast Path 不得无声退化。
|
||||||
|
|
||||||
|
至少保证:
|
||||||
|
- 对“恰好一个 CONTINUOUS Segment + 其余为 RETAINED”的 V12 等价拓扑,且 Continuous 内容满足现有支持条件时,现有 Fast Path 能继续工作。
|
||||||
|
- 不存在某个 retained cache 时不得成为 miss 原因。
|
||||||
|
|
||||||
|
对于多个 CONTINUOUS Segment,若当前 Fast Path 无法安全支持,允许 fail closed:
|
||||||
|
- `fastPathActive=false`
|
||||||
|
- 明确 fallback reason
|
||||||
|
- 正常 full CPU frame
|
||||||
|
- Segment topology / retained cache / Z-order 仍正确
|
||||||
|
|
||||||
|
禁止为了 STEP-094 一次性重写整个 RenderGraph 或实现所有 N-Continuous Fast Path 优化。
|
||||||
|
|
||||||
|
## 19. Style Mutation
|
||||||
|
|
||||||
|
必须验证:
|
||||||
|
- Segment 内新增/删除/reorder Layer
|
||||||
|
- boundary 前后插入 Layer
|
||||||
|
- boundary Layer hidden
|
||||||
|
- boundary Layer 删除/重新出现
|
||||||
|
- boundary reorder 导致 plan invalid
|
||||||
|
- plan 更新后恢复
|
||||||
|
|
||||||
|
普通 Layer 插入 Segment 内后应自然成为该 Segment 成员,不要求 App 给每个 Layer 单独注册频率 metadata。
|
||||||
|
|
||||||
|
相邻 RETAINED Segment 即使策略相同,也必须依靠显式 boundary 保持独立。
|
||||||
|
|
||||||
|
## 20. GPU 生命周期
|
||||||
|
|
||||||
|
实际 retained GPU resource 数量应与当前 resolved plan 中 RETAINED Segment 数量一致,允许 lazy 未创建状态,但不得为 CONTINUOUS Segment 分配 retained target。
|
||||||
|
|
||||||
|
Plan topology 变化后 stale resource 必须正确 release。稳定帧不得重复创建 retained texture/FBO/compositor。OpenGL 与 Metal 行为必须一致。
|
||||||
|
|
||||||
|
## 21. Native Directed Tests(不加入 Jest)
|
||||||
|
|
||||||
|
本任务明确不加入 Jest。
|
||||||
|
|
||||||
|
至少覆盖:
|
||||||
|
1. 单 RETAINED Segment 覆盖全 Style
|
||||||
|
2. 单 CONTINUOUS Segment 覆盖全 Style
|
||||||
|
3. V12 等价 R-C-R
|
||||||
|
4. C-R-R
|
||||||
|
5. R-R-C
|
||||||
|
6. R-C-R-C-R 多段交错
|
||||||
|
7. 相邻 R-R 不自动 merge
|
||||||
|
8. hidden boundary 不改变 topology
|
||||||
|
9. boundary missing / duplicate / 顺序非法
|
||||||
|
10. Style 内插入 Layer 后 membership 正确
|
||||||
|
11. boundary 删除后 fail safe、恢复后重新 resolve
|
||||||
|
12. retained resource 数量匹配 RETAINED Segment 数
|
||||||
|
13. CONTINUOUS 无 retained resource
|
||||||
|
14. Cache OFF 时 topology diagnostics 仍完整
|
||||||
|
15. V12 `setContinuousLayerRange` 兼容映射
|
||||||
|
16. global layerIndex / Opaque / Translucent 语义不变
|
||||||
|
17. 多 CONTINUOUS Fast Path 安全 fallback
|
||||||
|
|
||||||
|
Diagnostics tests 额外验证:
|
||||||
|
- styleLayers 顺序精确
|
||||||
|
- segments 顺序精确
|
||||||
|
- 每个 Segment 的 layers 成员精确
|
||||||
|
- 拼接 segment.layers == styleLayers
|
||||||
|
- layerIndex 不重编号
|
||||||
|
- layerId/layerType/visible 正确
|
||||||
|
- sourceId/sourceLayer(若实现)正确
|
||||||
|
- invalid plan 仍能取得 ordered styleLayers
|
||||||
|
- snapshot pull 不产生 per-frame 日志
|
||||||
|
|
||||||
|
## 22. 平台与 RN 验证
|
||||||
|
|
||||||
|
Core/C++:
|
||||||
|
- 执行规定的 Mac 原生精确编译与 Native tests
|
||||||
|
- OpenGL / Metal
|
||||||
|
- 编译输出遵循全局 build-output-throttle
|
||||||
|
|
||||||
|
Android:
|
||||||
|
- 完成源码、JNI/Kotlin/Java/RN bridge 修改
|
||||||
|
- Android Gradle 编译由用户手工执行,不由 Codex 运行
|
||||||
|
- 报告列明“未执行 Android 编译/实体机”直到用户验证
|
||||||
|
|
||||||
|
iOS:
|
||||||
|
- 只面向 iPhone/iPad arm64 真机架构
|
||||||
|
- 不要求 Simulator slice
|
||||||
|
- 可执行当前工程已有的 Mac/iOS arm64 精确编译验证
|
||||||
|
- 不伪造实体机运行 PASS
|
||||||
|
|
||||||
|
## 23. 最小 Debug/Validation 入口
|
||||||
|
|
||||||
|
在当前已有 Particle validation/Test Host 体系中补一个最小 pull 入口:
|
||||||
|
- Fetch Segment Diagnostics
|
||||||
|
- 展示或输出一次完整 snapshot
|
||||||
|
- 能看到 Segment 列表和每个 Segment 的 Layer 列表
|
||||||
|
|
||||||
|
不得增加持续轮询,不得把大 snapshot 每帧写日志。若现有 UI 已有 diagnostics panel,优先复用。
|
||||||
|
|
||||||
|
## 24. 性能要求
|
||||||
|
|
||||||
|
Segment topology/style diagnostic 数据只在 config/style topology 或相关 debug metadata 变化时更新,或在显式 diagnostics pull 时做必要轻量组装。
|
||||||
|
|
||||||
|
禁止因为 Particle 30/60fps:
|
||||||
|
- 每帧扫描/复制完整 Style Layer 字符串
|
||||||
|
- 每帧 JSON stringify
|
||||||
|
- 每帧创建 Segment diagnostics 大对象
|
||||||
|
- 每帧 Logger 输出 topology
|
||||||
|
|
||||||
|
高频 render path 只消费预解析的轻量 segment ranges/cache state。
|
||||||
|
|
||||||
|
## 25. 明确不做
|
||||||
|
|
||||||
|
本 STEP 不做:
|
||||||
|
- LOW/MEDIUM/HIGH 枚举或自动频率分类
|
||||||
|
- Renderer 识别 NavSea 业务 Layer
|
||||||
|
- 自动调整 Style Z-order
|
||||||
|
- Anchor/Marker Layer
|
||||||
|
- 复杂通用业务 dependency graph
|
||||||
|
- Weather provider / Route ETA / AIS 专项 renderer
|
||||||
|
- PBF 重生成
|
||||||
|
- 通用 RenderGraph 重写
|
||||||
|
- 自动合并同策略 Segment
|
||||||
|
- 多 Continuous 的高级 Fast Path 优化
|
||||||
|
- per-frame Segment debug log
|
||||||
|
- Jest
|
||||||
|
|
||||||
|
如发现必须突破这些边界才能保证 correctness,停止扩大范围并在报告明确 BLOCKED/PARTIAL。
|
||||||
|
|
||||||
|
## 26. 必做文档
|
||||||
|
|
||||||
|
新增:
|
||||||
|
- `docs/30_设计/STEP-094_通用RenderSegment模型与Style分类Diagnostics设计_v1.md`
|
||||||
|
- `docs/50_Step记录/STEP-094_通用RenderSegment分段渲染与Style分类诊断_v1.md`
|
||||||
|
- `docs/报告/STEP-094_通用RenderSegment分段渲染与Style分类诊断_Codex完成报告_v1.md`
|
||||||
|
|
||||||
|
追加:
|
||||||
|
- `粒子开发记录.md`
|
||||||
|
|
||||||
|
同主题已有文件则递增版本,不覆盖历史。所有文档中文。
|
||||||
|
|
||||||
|
设计文档必须包含最终 Segment 数据模型、config/resolve 生命周期、V12 compatibility mapping、cache ownership、invalidation、Fast Path 边界、Android/iOS/RN API、Diagnostics schema、完整 Style→Segment→Layer snapshot 示例。
|
||||||
|
|
||||||
|
## 27. 完成报告必须记录
|
||||||
|
|
||||||
|
- 起始 HEAD / 最终 HEAD
|
||||||
|
- 修改文件清单
|
||||||
|
- RenderSegment config API
|
||||||
|
- boundary Layer ID → layerIndex 解析
|
||||||
|
- N Segment 遍历
|
||||||
|
- retained cache ownership/resource lifecycle
|
||||||
|
- segment-local 与 global invalidation
|
||||||
|
- V12 compatibility / Fast Path
|
||||||
|
- Diagnostics schema
|
||||||
|
- styleLayers / segment.layers 验证
|
||||||
|
- RN/Android/iOS bridge
|
||||||
|
- OpenGL / Metal
|
||||||
|
- Native test 数量与结果
|
||||||
|
- 未执行 Android/实体机项目
|
||||||
|
- known limitations
|
||||||
|
- commit SHA/message
|
||||||
|
- push result / remote branch
|
||||||
|
- origin/particle/main Final HEAD
|
||||||
|
- 最终 `git status --short`
|
||||||
|
|
||||||
|
## 28. Git 规则
|
||||||
|
|
||||||
|
仅 scoped staging。禁止 `git add -A`、`git add .`、`git reset --hard`、`git clean`,不得删除/覆盖无关 dirty/untracked。
|
||||||
|
|
||||||
|
结束前必须:
|
||||||
|
1. `git diff --check`
|
||||||
|
2. 核对 diff
|
||||||
|
3. 只 stage 本任务文件
|
||||||
|
4. scoped commit
|
||||||
|
5. push `origin particle/main`
|
||||||
|
6. fetch/确认远端
|
||||||
|
|
||||||
|
最终要求:
|
||||||
|
```text
|
||||||
|
HEAD == origin/particle/main
|
||||||
|
git status --short 无本任务残留
|
||||||
|
```
|
||||||
|
|
||||||
|
push 失败不得声称完成。
|
||||||
|
|
||||||
|
## 29. PASS Gate
|
||||||
|
|
||||||
|
只有以下全部成立才可判 STEP-094 PASS:
|
||||||
|
- Style Z-order 仍唯一
|
||||||
|
- 1..N 连续 Segment 完整覆盖
|
||||||
|
- RETAINED/CONTINUOUS 可任意交错
|
||||||
|
- 相邻同策略 Segment 不自动合并
|
||||||
|
- 每个 RETAINED Segment 独立 cache/revision/invalidation/resource
|
||||||
|
- V12 Continuous Range 兼容
|
||||||
|
- V12 等价 Fast Path 不无声退化
|
||||||
|
- 新 Diagnostics 能真实返回 Style→Segment→Layer
|
||||||
|
- diagnostics pull-based、无 per-frame 大开销
|
||||||
|
- Android/iOS/RN API 完整
|
||||||
|
- Native directed tests 通过
|
||||||
|
- 规定编译验证完成
|
||||||
|
- 文档完成
|
||||||
|
- scoped commit/push 完成
|
||||||
|
- HEAD 与远端一致
|
||||||
|
|
||||||
|
关键项未完成必须明确 PARTIAL/BLOCKED,禁止伪造 PASS。
|
||||||
109
docs/40_任务/STEP-095_V13独立严格代码与性能审查_Codex任务_v1.md
Normal file
109
docs/40_任务/STEP-095_V13独立严格代码与性能审查_Codex任务_v1.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# STEP-095 V13 独立严格代码与性能审查 — Codex 任务 v1
|
||||||
|
|
||||||
|
## 0. 工作性质与模型
|
||||||
|
- 本任务是独立、严格、全链路、以源码为最终证据的只读代码/性能调查,不是修复任务,也不是批准已有报告。
|
||||||
|
- 执行环境:Mac-mini-tei.lan,仓库 /Users/tei/dev/MapLibreParticleLab,分支 particle/main。
|
||||||
|
- 运行模型:gpt-6-astra,reasoning effort=high;由外部启动命令实际指定,报告记录实际模型。
|
||||||
|
- 最终交付物必须为中文,具体源码符号、文件路径、命令、证据可保留英文。
|
||||||
|
- 不以“编译通过”“定向单元测试通过”“已有 Codex 报告 PASS”推断 GPU 运行态、安全性、性能或跨平台一致性已经通过。
|
||||||
|
|
||||||
|
## 1. 源码冻结基线
|
||||||
|
- 被审查的 V13 代码提交:ee0b89e711bf01335b3aa71d36def785cd97fec3。
|
||||||
|
- V13 核心功能提交:016fd1fff6a33cf795c10d4f5c5413d308383677。
|
||||||
|
- V13 发布标识:RN 13.4.1-particle.13;iOS 6.28.0-particle.13;以实际 manifest/tag/API 核实,不假设已在 NavSea 接入。
|
||||||
|
- 前置独立审查归档提交:45c4dca84928a57a2cf0226737c34cabd7fbfbfb。
|
||||||
|
- 审查前读取 AGENTS.md、PARTICLE_BASELINE.md、docs/00_项目规范/、docs/00_项目总则/README.md 与其中标记的当前有效总则。
|
||||||
|
- 同步检查 git branch、HEAD、origin/particle/main、working tree、release manifest 和相关 tag;记录所有偏差。
|
||||||
|
- 本任务自身新增文档提交不改变上述被审查的源码 commit;核查源码时固定以该提交为事实基线。
|
||||||
|
|
||||||
|
## 2. 审查独立性:先源码,后已有报告
|
||||||
|
- 首先依据 Git V12→V13 改动、当前调用链及实际代码独立扫描,不从旧报告结论直接生成 findings。
|
||||||
|
- 然后阅读 docs/20_调查/Particle_V13_严格代码审查报告_v1.md,逐项复核 F01–F10:确认、修正、否定或标记需动态验证。
|
||||||
|
- 不能复制上一份报告来冒充独立 review。需要发现旧报告遗漏、误判、证据不足及其他模块的潜在风险。
|
||||||
|
- 交叉阅读 STEP-088~094 的任务、设计、Step 记录、Codex 报告及相关发布/验证事实。
|
||||||
|
- 当任务书与代码冲突,分别说明“设计意图”和“实际实现”;不能把历史数据冒充 V13 测量。
|
||||||
|
- 可以读取 Integration repo 的 contract/status/compatibility,但不得修改该仓库;发现跨平台契约问题只提升级建议。
|
||||||
|
|
||||||
|
## 3. 强制审查范围 A:Renderer 与 V13 Segment
|
||||||
|
- 通用 RenderSegment plan、JSON parser/resolver、配置切换、missing/reorder/hidden boundary、valid/invalid/recovery。
|
||||||
|
- Style 全局 Z-order、layerIndex、LayerGroup ordinal、opaque reverse、translucent forward、depth/stencil/blending、debug/legacy/3D 与 offscreen composite 的一致性。
|
||||||
|
- 任意 R/C 交错:全 R、全 C、R-C-R、C-R-R、R-R-C、R-C-R-C-R、相邻 R-R;缓存 OFF 和资源失败的 full fallback。
|
||||||
|
- Retained cache 独立生命周期、build/reuse/invalidate/release、topology generation、content fingerprint、diagnostics counters 的归属。
|
||||||
|
- V12 连续区间兼容映射、单 Continuous Fast Path、多个 Continuous 安全 fallback,注意 silent fallback 与正常渲染的真实成本。
|
||||||
|
- 特别核查 Segment pass 分割后的 alpha/depth/stencil 语义、source/layer mutation 与下一帧 stale GPU object 风险。
|
||||||
|
## 4. 强制审查范围 B:GPU、粒子和时间场
|
||||||
|
- OpenGL 与 Metal OffscreenTexture、Framebuffer、Texture、render pass、commit/present/wait、resource destroy/context loss;区分通用 Core 与平台实现。
|
||||||
|
- 是否存在 CPU/GPU 同步等待、重复 GPU allocation、每帧 upload/UBO/drawable 重建、纹理引用失效和资源峰值。
|
||||||
|
- Particle State A/B、Trail、Field T0/T1/MIX、spatial sampling、forecast/playback、zoom-out reseed、viewport bounds 和 resize/orientation。
|
||||||
|
- Wind/Wave/Current/Rain 等 canonical FieldProfile;V3/V4 encoded/decoded field、QBIN decoder、projection/materialization、binding/validity/missing data。
|
||||||
|
- 检查异常或损坏输入、极大网格/时间、浮点 NaN/Inf、整数溢出、文件/缓冲区生命周期、重复/乱序/过期 field、安全失败和内存占用。
|
||||||
|
- 需要从 Source→Runtime→RenderLayer→Renderer→GPU 按真实调用链追踪,不接受只搜索名称的浅层判断。
|
||||||
|
|
||||||
|
## 5. 强制审查范围 C:高频、线程和 Host API
|
||||||
|
- 每帧 CPU 分配/深复制/锁竞争/字符串或 JSON 构造、LayerGroup 遍历复杂度、静态 CPU Fast Path 命中与错过原因。
|
||||||
|
- style/source/tile/image/glyph/placement 失效范围是否正确,segment-local 与全局 revision 的边界是否一致。
|
||||||
|
- map-scoped lifetime、prepared RenderTree、Runtime/renderer/control state 的并发、线程归属、异步回调、销毁后引用和重复 attach/detach。
|
||||||
|
- Android Java/Kotlin/JNI 与 iOS ObjC++/Metal 的桥接接口、错误归一化、RN TurboModule typed API、ABI/codegen 与版本锁。
|
||||||
|
- Test Host 的依赖及 lockfile、构建结果的可复现性、release manifest/npm/AAR/XCFramework 的版本一致性。
|
||||||
|
- 不将业务 provider、HTTP、Route/ETA 带入 Shared Core;不建议建立新的隐藏私有桥接或第二套协议。
|
||||||
|
|
||||||
|
## 6. 强制审查问题清单
|
||||||
|
- 是否存在 V13 上线后能够导致画面层级错误、透明度/遮挡错误、丢帧、黑屏、闪烁、过期 cache composite、GPU/CPU 泄漏、崩溃或错误数据的实际源码路径?
|
||||||
|
- 非法配置是否真正 fail closed?同一 index 换不同 Segment 时旧 cache/diagnostics 归属是否错误?
|
||||||
|
- Retained contentDirty 是否被误当作 GPU allocation invalid?Metal 逐 cache wait 是否可证实?需要哪些 profiler 证据才能决定修改?
|
||||||
|
- 高频 Fast Path 是否复制 full diagnostics、扫描 Style 或反复线性搜索?有无更重的热点在既有报告中遗漏?
|
||||||
|
- 多段 retained 是否存在全 viewport depth/stencil 过度分配;是否有峰值预算、安全回退及 resource accounting?
|
||||||
|
- source/tile 局部更新是否引起不必要全 retained rebuild?若建议局部化,必须给出不破坏 correctness 的依赖证据。
|
||||||
|
- 单 Continuous 与多 Continuous 的 Frame 处理在相同真实 Style 下有什么可测的差异?不能外推 STEP-090 历史 A/B 数字。
|
||||||
|
- release、Test Host、RN/iOS/Android 版本与实际产物是否能被独立复现?尚未测试的必须标 NOT RUN。
|
||||||
|
|
||||||
|
## 7. 审查方法
|
||||||
|
1. 建立 V12→V13 变更与模块地图,阅读入口、类型定义、状态机、调用方和析构路径。
|
||||||
|
2. 针对每个怀疑的问题制作最短触发条件和源码调用链;尝试找反例,禁止只因为名称相似就报告漏洞。
|
||||||
|
3. 按已证实缺陷、源码确定的成本、真实运行影响待验证、显式设计限制、文档/环境偏差分类。
|
||||||
|
4. 为每条 finding 写严重度 P0/P1/P2/P3、置信度、源文件精确行范围、触发场景、影响、最小安全修复方向和验收方法。
|
||||||
|
5. 独立比较上一份 F01–F10,标为 CONFIRMED / QUALIFIED / DISPROVED / NEEDS_RUNTIME,说明依据和新增发现。
|
||||||
|
6. 对潜在收益只给复杂度/估算表达和建议基准测试,不提供无 profiler 支持的 FPS/CPU/GPU 节省百分比。
|
||||||
|
7. 所有未亲自执行的测试必须标 HISTORY / NOT RUN;已有报告的 PASS 只能列为历史证据,不替代当前独立执行。
|
||||||
|
|
||||||
|
## 8. 运行与修改边界
|
||||||
|
- 禁止修改任何生产源码、shader、公共 API、构建配置、package/lockfile、release manifest、tag、发布产物或 Integration 文件。
|
||||||
|
- 禁止运行耗时 CMake/Xcode/Gradle 编译、安装、设备操作和大型测试;由 RDC/用户另行验证。
|
||||||
|
- 允许只读 git show/log/diff、文件检索、源码静态分析和查看既有日志、测试源码、报告。
|
||||||
|
- 如判断必须借助 profiler、真机或编译才可定论,写精确验证方案而不是擅自实施。
|
||||||
|
- 仅允许新增本 STEP 中文调查/Step/完成报告,按规定追加根目录粒子开发记录;不要顺手修复发现的问题。
|
||||||
|
## 9. 完成报告格式(必须实际落盘)
|
||||||
|
- 执行摘要:哪些是确定缺陷、哪些是性能成本、哪些只能动态验证;不能只给结论口号。
|
||||||
|
- 基线与范围:审查 commit、分支、release manifest、审查的主要文件/代码路径、未覆盖范围。
|
||||||
|
- Finding 总表:编号、严重度、证据类别、问题、触发、影响、代码定位、修复方向、验证方法。
|
||||||
|
- 逐条完整分析:至少包含最短可复核调用链及确认或推翻依据;新问题与旧报告重复项区分。
|
||||||
|
- V13 与 V12 架构/性能成本比较:不确定的写待测;不要虚构性能数值。
|
||||||
|
- F01~F10 独立复核矩阵:原结论、当前意见、依据、是否需补充/修正。
|
||||||
|
- 缺失测试与运行态矩阵:GPU correctness、CPU/GPU frame time、allocation/peak memory、30/60 FPS、长时间稳定性、各平台状态。
|
||||||
|
- 优先级清单:分 correctness gate、无需设备的安全优化、GPU 资源/同步、较大架构改造。
|
||||||
|
- 具体建议的下一轮 Codex 修复批次(只建议,不创建修复任务,不改源码)。
|
||||||
|
- Git:起始/最终 HEAD、docs-only diff 清单、commit SHA/message、push 结果、remote HEAD、最终 status。
|
||||||
|
- 结论状态:调查 PASS/PARTIAL/BLOCKED 与 V13 产品运行态是否通过必须分开。
|
||||||
|
|
||||||
|
## 10. 必做输出和固定路径
|
||||||
|
- 调查主文档:docs/20_调查/STEP-095_V13独立严格代码与性能调查_v1.md。
|
||||||
|
- 阶段记录:docs/50_Step记录/STEP-095_V13独立严格代码与性能审查_v1.md。
|
||||||
|
- Codex 完成报告:docs/报告/STEP-095_V13独立严格代码与性能审查_Codex完成报告_v1.md。
|
||||||
|
- 追加:根目录 粒子开发记录.md,短而具体地记录本次调查变化、现状及下一步,不覆盖历史。
|
||||||
|
- 若同主题相同文件已经存在,改用递增版本号并在报告写明,不覆盖旧报告。
|
||||||
|
- 以上文件全部中文;任何报告必须明确区分事实、推断和待验证。
|
||||||
|
|
||||||
|
## 11. Git 收尾
|
||||||
|
- 先确保用户现有 dirty/untracked 不被覆盖;只有本任务新增/追加文档允许 staged。
|
||||||
|
- 不执行 git reset --hard、git clean、force push、tag 移动或向 upstream push。
|
||||||
|
- 对所有新增文档进行路径、目录、内容、交叉引用检查;git diff --check。
|
||||||
|
- 仅 git add 上述具体文档和本次追加的开发记录;scoped commit 后 push origin particle/main。
|
||||||
|
- push 失败或 HEAD 与远端不一致时,不得写 PASS;明确失败与恢复建议。
|
||||||
|
- 记录源代码相对于原 V13 源码提交没有改变,禁止把 docs-only commit 误写成新 V13 软件发布。
|
||||||
|
- 最终由 RDC 独立检查报告证据、Git 与任务边界,Codex 不自行宣布正式产品验收。
|
||||||
|
|
||||||
|
## 12. 停止条件
|
||||||
|
- 无法确认当前 commit、源代码状态与所审查发布版本对应时,停止并报告 BLOCKED。
|
||||||
|
- 遇到跨项目协议变更/需要修改业务源码时,只形成接口阻塞或修复建议,不扩大任务。
|
||||||
|
- 证据不足时明确 NEEDS_RUNTIME,不应为了凑数量虚构问题。
|
||||||
|
- 重大 P0 或明确破坏性问题优先直接写进报告,但本任务仍只读,不自动修复。
|
||||||
59
docs/40_任务/STEP-096_V13隔离盲审_Codex任务_v1.md
Normal file
59
docs/40_任务/STEP-096_V13隔离盲审_Codex任务_v1.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# STEP-096 V13 隔离盲审 — Codex 任务 v1
|
||||||
|
|
||||||
|
## 0. 工作性质与隔离目标
|
||||||
|
|
||||||
|
这是针对已冻结 V13 源码的全新、只读、源码优先的代码与性能审查。发现必须来自当前隔离目录内的源代码和规范,不允许从任何既有审查结论、聊天记录或外部仓库导入问题清单。不得为凑数量制造 finding。
|
||||||
|
|
||||||
|
- 执行模型:全新 Codex TUI 会话,`gpt-6-astra`,reasoning effort=`high`;RDC 负责实际启动与记录,Codex 报告仅记录能核实的启动事实。
|
||||||
|
- 唯一工作目录:`/Users/tei/dev/MapLibreParticleV13Blind096`。
|
||||||
|
- 目标源码版本:`ee0b89e711bf01335b3aa71d36def785cd97fec3`;由 RDC 从该 Git 对象导出**无 .git 元数据**的源码快照。当前隔离目录不是主仓库、不是 Git worktree;不得在此运行 git,也不得用其他路径反查仓库历史。
|
||||||
|
- 唯一任务说明:本文件。用户和外部 RDC 将在审查开始前检查隔离树不含已有调查、阶段记录、完成报告、任务历史或开发日志。
|
||||||
|
- 禁止访问隔离目录之外的所有源码、文档、Git metadata、会话日志、终端回放、网页、连接器、用户历史或其他 AI 输出;不要尝试恢复缺失文档。对任何未在快照内的证据写 NOT AVAILABLE。
|
||||||
|
- 不得读取、搜索或比较其他轮次的审查材料;**本任务只有盲审,不包含解除盲态后的交叉比较。** 完成后的文件由 RDC 先冻结散列,再独立移交正式仓库。
|
||||||
|
|
||||||
|
## 1. 只读入口与事实层级
|
||||||
|
|
||||||
|
先读取隔离目录的 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、`docs/00_项目总则/README.md`、其中列出的当前有效总则和本任务。隔离快照故意不提供历史 Step、调查/完成报告;读取要求在盲审范围内以当前有效规范和实际源码为准,不得向目录外补读。规范包含历史发布坐标时,以本快照的 release manifest、源码和目标 SHA 为实现事实,区别说明规范意图与实际代码。
|
||||||
|
|
||||||
|
允许读取:隔离树内的 C++/GL/Metal/Android/iOS/RN 代码、头文件、shader、构建配置、测试源码、release manifest,以及当前有效规范/架构设计。历史测试输出和运行日志不可用;阅读测试源码不等于执行测试。不得浏览 `docs/20_调查`、`docs/50_Step记录`、`docs/报告`、既有历史任务或开发日志(这些输入在隔离树内也应不存在)。不引用主仓库的当前 HEAD 作为 V13 源码 SHA。
|
||||||
|
|
||||||
|
## 2. 审查目标与探索方法
|
||||||
|
|
||||||
|
从目录和公开 API 识别实际架构,自行建立模块和调用链地图;不要按照预设缺陷名称凑检索结果。对每个候选问题至少追踪入口、状态所有权、调用方、正常路径、错误路径、销毁路径,并寻找反例。分别审查:
|
||||||
|
|
||||||
|
1. Style 顺序和分段解析/更新/绘制语义;任意连续渲染策略交错、透明/深度/裁剪及缓存一致性。
|
||||||
|
2. Renderer→gfx→OpenGL/Metal 的目标、命令、资源和生命周期;稳定/变化帧、失败恢复、尺寸变化及上下文切换。
|
||||||
|
3. GPU 粒子、颜色场、时空插值与场数据链;输入合法性、数值边界、时间状态和显示状态。
|
||||||
|
4. Source/Runtime/Field/QBIN、数据快照、线程与内存所有权;重复、乱序、异常或极大输入与 fail-closed。
|
||||||
|
5. Android/iOS/RN 的公开 API、错误传播、生命周期和版本/产物引用;区分源码可证事实与安装环境需要的验证。
|
||||||
|
6. 高频执行成本:CPU、锁、分配、遍历、复制、GPU 同步/资源、UI 线程以及资源上界。查找实际热点路径,但没有 profiler 不得断言运行时热点排序或节省百分比。
|
||||||
|
7. 兼容与工程边界:既有行为、上游薄接入、Style Z-order 唯一事实来源;业务下载/HTTP/provider 属于 Host,不进入 Native。
|
||||||
|
|
||||||
|
如同一问题已有安全 fallback、缓存稳定复用或数据校验,必须写出反例和限制,而不是仅报告表面上可疑的符号。源代码路径和行号必须在隔离快照内真实存在。
|
||||||
|
|
||||||
|
## 3. 每条 finding 的证据契约
|
||||||
|
|
||||||
|
- 按发现顺序自行编号 `B01`、`B02`……;不要采用其他报告的任何编号。
|
||||||
|
- 列:P0/P1/P2/P3、类型(确定缺陷/源码确定成本/设计限制/环境或文档偏差/需运行验证)、置信度、触发条件、最短源码调用链与精确路径/行号、反例、影响边界、安全修复方向、最小验证方案。
|
||||||
|
- 区分“代码中的风险路径已经证实”和“用户画面、GPU、CPU、OOM、崩溃、性能收益已经实测”;后者本轮一概不可假定。
|
||||||
|
- 如果推断尚缺关键调用链,降为 HYPOTHESIS/NEEDS_RUNTIME,不以 P0/P1 明确事故冒充。
|
||||||
|
- 不要求 finding 数量、也不要求与任何报告相同;实际不存在证据就明确无此类 finding。
|
||||||
|
- 不复制包含商业/个人机密的数据;路径与定位信息足够即可。
|
||||||
|
|
||||||
|
## 4. 验证边界与停止条件
|
||||||
|
|
||||||
|
本任务只允许静态读取、轻量文件搜索、源码/语法/引用自检。禁止生产代码、测试代码、shader、manifest、package/lock、工具链改动;禁止 CMake/Xcode/Gradle、Codegen、Jest、设备、安装、性能测试、发布、Git 写入。历史构建/测试结果没有暴露,不得编造。所有未运行门槛标 `NOT RUN`,运行效果标 `NEEDS_RUNTIME`。如必须依赖平台设备、profiler 或竞态注入,写可执行验证矩阵,停止在证据边界。
|
||||||
|
|
||||||
|
只能写以下三份中文文件,写完确认三份存在、正文和相对链接有效、所引用路径/行号有效、无错误引用和行尾空格后退出:
|
||||||
|
|
||||||
|
1. `docs/20_调查/STEP-096_V13隔离盲审调查_v1.md`:架构地图、独立 finding 总表与证据、反例、风险、性能模型、未覆盖范围、运行验证矩阵、分批建议。开头明确声明仅使用隔离输入。
|
||||||
|
2. `docs/50_Step记录/STEP-096_V13隔离盲审_v1.md`:状态、输入版本、检查范围、只读工作过程、结果、三份文件清单、未运行验证、阻塞与下一步。
|
||||||
|
3. `docs/报告/STEP-096_V13隔离盲审_Codex完成报告_v1.md`:执行结果 PASS/PARTIAL/BLOCKED(仅表示盲审文档交付)、finding 索引、证据质量、输入限制、构建测试 NOT RUN、源码未变、交付/移交方案。
|
||||||
|
|
||||||
|
本隔离树无 Git metadata,**不要在报告内伪造 Git commit/push/remote/clean 状态**。正式仓库移交、`粒子开发记录.md` 追加、四个文件的 scoped commit/push、最终 SHA 和远端核对均由 RDC 在盲审文件冻结之后执行。Codex 不得自行打开正式仓库进行移交,避免在写稿前接触比较材料。
|
||||||
|
|
||||||
|
## 5. 盲审冻结与移交验收(由 RDC 执行,不是 Codex 自行执行)
|
||||||
|
|
||||||
|
Codex 停止后,RDC 验证隔离树的文件清单,计算三份原始报告 SHA-256 并记录时间;冻结后不得由同一会话基于其他资料重写 finding。随后独立把三份报告完整复制到正式 `particle/main` 同路径,追加根目录 `粒子开发记录.md`,执行引用、边界、git diff --check、scoped commit、push `origin particle/main`、远端 HEAD/clean 核查。禁止修改已冻结正文,若必须校正移交元数据,应写入独立移交记录,不得改写发现表。
|
||||||
|
|
||||||
|
文档交付 PASS 不等于 V13 Android/iOS GPU 运行态或性能 PASS。若 Codex 无法访问输入、丢失上下文或额度阻断,停止、保留已成品并标 PARTIAL/BLOCKED;不得使用其他会话已有审查结果补齐。
|
||||||
153
docs/40_任务/STEP-097_V13三轮审查问题合并修复_Codex任务_v1.md
Normal file
153
docs/40_任务/STEP-097_V13三轮审查问题合并修复_Codex任务_v1.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# STEP-097 V13 三轮审查问题合并修复 — Codex 任务 v1
|
||||||
|
|
||||||
|
状态:**计划 / 待执行**。本文件只定义后续代码修复任务;创建本文件不代表任何缺陷已经修复、编译通过或 V13 运行验收通过。
|
||||||
|
|
||||||
|
## 0. 目标、基线及本轮决策
|
||||||
|
|
||||||
|
目标:对 V13 三轮源码调查的问题去重、复核、按安全依赖顺序修改;保留全部原始编号和证据归属,不以“多份报告都说有问题”取代源码验证。先消除渲染与天气数据的正确性风险,再处理输入安全、Host 生命周期和可证明的热路径成本。保留通用连续 Render Segment、Style Z-order 唯一顺序、每个 RETAINED 独立 cache;不得退回旧 Bottom/Continuous/Top 固定三明治模型。
|
||||||
|
|
||||||
|
本次用户决定:**暂不执行 V13 正式运行态确认**。本任务结束的最高状态只能是 \`IMPLEMENTED / WAITING_RDC_BUILD_AND_RUNTIME_VALIDATION\`;不能写 Android/iOS GPU、FPS、长期稳定性、正式发布或 NavSea 接入 PASS。本任务不要求 Codex 编译、安装、操控设备或执行性能测试。运行态集中验证清单由 Codex准备,之后单独授权执行,避免小变动引发多次 Android 编译/安装。
|
||||||
|
|
||||||
|
实际源码基线以执行时 \`particle/main\`、\`origin/particle/main\` 和 clean worktree 为准;编写任务书时 HEAD 为 \`b5c1c4879a9d36608242e788afab5012bd9388f2\`,V13 软件冻结源码为 \`ee0b89e711bf01335b3aa71d36def785cd97fec3\`,功能提交 \`016fd1fff6a33cf795c10d4f5c5413d308383677\`。当前 \`.13\` 软件已发布,所有修复在后续开发提交中完成,**不得原地覆盖、移动或重新发布 \`.13\` 的 tag/manifest/产物**。若起始 HEAD 已前进,先核实源代码差异并报告,不用旧行号机械覆盖。
|
||||||
|
|
||||||
|
审查输入,必须逐份读取:
|
||||||
|
1. \`docs/20_调查/Particle_V13_严格代码审查报告_v1.md\`(F01–F10,含历史实测)。
|
||||||
|
2. \`docs/20_调查/STEP-095_V13独立严格代码与性能调查_v1.md\`(N01–N07、C01–C07、O01、E01/E02)。
|
||||||
|
3. \`docs/20_调查/STEP-096_V13隔离盲审调查_v1.md\`(B01–B13;**已冻结的独立盲审原文,不得因修复结果修改其 findings**)。
|
||||||
|
4. STEP-094、095、096 的任务/Step/完成报告与 release manifest,按真实性区分历史 PASS 与本轮 NOT RUN。
|
||||||
|
|
||||||
|
开始前读根 \`AGENTS.md\`、\`PARTICLE_BASELINE.md\`、\`docs/00_项目规范/\`、\`docs/00_项目总则/README.md\` 指定的四份有效总则。通过 RDC 确认 \`navsea-particle-integration\` 已同步到最新 \`main\`,并只读检查 \`README.md\`、\`WORKFLOW.md\`、\`coordination/TEAM_MODEL.md\`、\`contracts/\`、\`rfc/\`、\`adr/\`、\`status/\`、\`compatibility/\`。创建任务时 Integration main 为 \`39ad8415fbdb76c1822460bb09f3a3f35fe141e0\`,C-001 / RFC-001 为既有合同,不把其中历史 \`.5\` 误认为当前软件发布 \`.13\`。本任务**不修改 Integration、NavSea 仓库**,新增跨项目公共语义须先有协调/RFC/ADR/Contract 决定。
|
||||||
|
|
||||||
|
## 1. 去重修复总表与执行分类
|
||||||
|
|
||||||
|
状态枚举:\`CODE_FIX\`=本任务可实施;\`CONDITIONAL\`=只有满足设计/证据门槛才动;\`DESIGN_ONLY\`=不作为既有缺陷盲改;\`COORDINATION\`=需跨项目负责人协调。Codex 应将表中每项逐条写入实施矩阵,落实文件、测试入口、commit、实际状态;确认某项不成立也必须提供反证,不能直接删除。
|
||||||
|
|
||||||
|
| 合并问题 | 三轮原编号 | 初始分类 | 目标与安全边界 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 01 跨 render target 的 stencil CPU/GPU 状态失配 | N01 / B01 | CODE_FIX | target/pass 世代一致;首帧、R-R/R-C-R、cache ON/OFF |
|
||||||
|
| 02 Metal depth/stencil clear 与附件不完整 | N02 / B02 | CODE_FIX | 全部 RenderPass descriptor/combined attachment;不得只改颜色 |
|
||||||
|
| 03 LayerGroup render 异常后缓存错误标成功 | N03 / B12 | CODE_FIX | 失败不得 \`markBuildSucceeded\` 或 reuse;允许安全恢复 |
|
||||||
|
| 04 encoded PLAY 缺失唯一 production clock 驱动 | N04 / B06 | CODE_FIX | map/source owner 单一单调推进;无双重 tick |
|
||||||
|
| 05 GPU 粒子场身份未覆盖 V/validity | B13 | CODE_FIX | 两时间端点完整内容身份;正确复用和局部上传 |
|
||||||
|
| 06 8-bit 零向量偏差 | B07 | CONDITIONAL | 修复零表达和编码/解码一致性,不引入未验证的格式破坏 |
|
||||||
|
| 07 Mercator 东向位移缺纬度系数 | B08 | CODE_FIX | U/V 相同物理单位下正确度量,保留既定时间倍率 |
|
||||||
|
| 08 JSON 嵌入 NUL/重复键被接受 | N05 | CODE_FIX | 长度感知解析;非法计划清旧状态、fail-closed |
|
||||||
|
| 09 坏数值场先入库、物化失败替换好投影 | N06 | CODE_FIX | 事务式校验/发布;允许合法 no-data/validity=0 |
|
||||||
|
| 10 Android 重复 attach listener 积累 | N07 | CODE_FIX | 幂等 attach/detach、移除监听、对象释放 |
|
||||||
|
| 11 热路径完整 Diagnostics 深复制 | F01 / C01 | CODE_FIX | 内部轻快照与对外完整 pull 分离,线程一致性 |
|
||||||
|
| 12 Metal retained/普通 State/Trail 同步等待 | F02 / C02 / B03 | CONDITIONAL | 先记录同步点/依赖;不可直接删除 wait |
|
||||||
|
| 13 内容 dirty 导致相同尺寸 GPU allocation 重建 | F03 / C03 / B04 | CODE_FIX | contentDirty 与 resourceInvalid 分离;清屏正确性先通过 |
|
||||||
|
| 14 retained GPU 资源无总预算 | F04 / C04 | CONDITIONAL | 安全估算、分配失败完整 fallback;独立 R 不合并 |
|
||||||
|
| 15 encoded store 缺总 key/byte 预算 | C04 / B11 | CONDITIONAL | 保留绑定窗口和旧读者;若拒绝结果需新公共语义则停止 |
|
||||||
|
| 16 ordinal 前缀 O(S×G)/fast 扫全层 | F07 / C05 | CODE_FIX | topology generation 缓存索引,不能改变全局顺序 |
|
||||||
|
| 17 相同 time/mix-only 重物化和 UI 同步处理 | C06 / B05 / B10 | CODE_FIX(异步部分 CONDITIONAL) | 先幂等和不可变端点复用;owner-thread 安全发布 |
|
||||||
|
| 18 drawable/tweaker 重建与窗口端点复制 | C07 | CONDITIONAL | 只改可证明稳定的对象生命周期,避免新 stale 引用 |
|
||||||
|
| 19 旧 cache 的 release 记入新 topology | F09 / O01 | CODE_FIX | identity/generation 归属、历史/当前计数分离 |
|
||||||
|
| 20 Test Host RN \`.10\` / native \`.13\` 错配 | F05 / E01 | CODE_FIX | 默认 package/lock 与插件精确锁定;不把旧 TS 报错误归因 |
|
||||||
|
| 21 多 Continuous 退出 CPU Fast Path | F06 | DESIGN_ONLY | 保留安全 full CPU fallback,后续真实 A/B 再立项 multi-C |
|
||||||
|
| 22 source/tile 等保守全局失效 | F08 | DESIGN_ONLY | 没有完整依赖证明不得缩窄,后续 profiling/依赖设计 |
|
||||||
|
| 23 Integration compatibility/status 历史口径 | F10 / E02 | COORDINATION | 向粒子负责人提交接入证据请求,不擅改或声称 NavSea 支持 |
|
||||||
|
| 24 正式共享地理 Ocean Mask 尚未形成 | STEP-095 §7 / B09 | COORDINATION | 跨 Host/Shared Core/双平台显示契约需单独 RFC/ADR,不用合成 fixture 冒充正式实现 |
|
||||||
|
|
||||||
|
注意:B13 与 N06 **不是同一缺陷**;GPU 内容身份遗漏与 encoded ingress 事务发布必须分别修复和验证。B07/B08 是粒子数值正确性,不等于 V13 分段回归。所有 P1/P2 是调查建议优先级,不是运行事故证明。
|
||||||
|
|
||||||
|
## 2. 固定顺序与不可跨越的检查点
|
||||||
|
|
||||||
|
严格按 \`基线复核 → A正确性 → B输入/生命周期 → C安全性能 → D交付\` 串行进行。前一阶段没有明确代码完成和静态检查结果,禁止推进下一阶段;失败、进程超时、状态未知不能当作成功。任一阶段发现需要更改冻结公共 API、QBIN/FieldProfile 语义或破坏跨平台 ABI,**停在该问题**,记录接口阻塞和建议,不自行加入 Adapter/Normalizer、隐藏通道或不兼容 fallback。不得为了凑齐总表自动进行大架构重构。
|
||||||
|
|
||||||
|
### 阶段 0:基线与定点反证
|
||||||
|
|
||||||
|
1. 确认 branch/HEAD/origin、dirty/untracked、submodule、manifest、V13 tag;对照三轮行号回读实际源码,建立 \`问题→当前源码→最短复现→反例→修复文件→目标回归\` 表。
|
||||||
|
2. 确认 GL/Metal/RN/Test Host 的实际入口和平台条件,核对是否已有后续修改解决某项;有明确反证者记 \`NOT_REPRODUCIBLE_ON_CURRENT_HEAD\` 并附定位,不重复修复。
|
||||||
|
3. 固定公开语义:\`fieldId/generation/validTime\`、U 东向/V 北向、T0/T1/MIX 与 OLD_T1→NEW_T0 reuse、Style Z-order、每个 R 独立 cache、颜色与粒子分离、天气获取由 Host 所有。必要的内部对象身份可以新增,禁止创造第二套公共场协议。
|
||||||
|
4. 在 \`docs/30_设计/STEP-097_V13三轮问题去重与修复矩阵_v1.md\` 先写入逐项状态和安全不变量;后续随源码实现持续更新。这不是重新撰写第四轮审查。
|
||||||
|
|
||||||
|
### 阶段 A1:Renderer 正确性(01/02/03/19)
|
||||||
|
|
||||||
|
- **01 stencil**:明确记录属于当前 render target/pass,切换附件时重置/分代 CPU mask/ID 记录;GPU clear 与 CPU 映射同世代。保留同一 target 中安全的 tile mask 复用,不能中途全局清掉仍需使用的 stencil。针对相同 tiles 的 R-R、R-C-R、C-R-R、首次 build/rebuild/reuse/cache OFF 加最小可执行回归入口。
|
||||||
|
- **02 Metal**:从 gfx RenderPass descriptor 到 MTL descriptor 完整传递 color/depth/stencil 的 load/clear/store;正确处理 combined depth-stencil 和 device/simulator 附件存在性,创建失败显式不发布可用缓存。不能只依赖默认 loadAction 或 color isRenderable。补 descriptor/附件状态与 fallback 可观察测试入口;不宣称 iOS runtime PASS。
|
||||||
|
- **03 错误缓存**:使 layer-group 绘制失败结果贯穿 opaque/translucent、observer、finishBuild;部分绘制不得发布为 valid,不得在下一帧 reuse。正常 fallback/retry 能恢复;保留已有资源准备失败回退,不吞异常或改成进程崩溃。
|
||||||
|
- **19 计数归属**:cache 创建/析构使用稳定 identity + topology generation,旧资源 release 不记入同 index 的新段;聚合统计与当前 topology 统计分离。连续模式切换、R→C、相同 index 换边界均有验证入口。诊断错误不夸大为纹理本体泄漏。
|
||||||
|
- 本阶段有任何 stencil/Metal/异常正确性未闭合时,**不得开始资源复用/异步 GPU 性能改造**。
|
||||||
|
|
||||||
|
### 阶段 A2:Field/粒子正确性(04/05/06/07)
|
||||||
|
|
||||||
|
- **04 PLAY**:只允许一个 map/source owner 推进 forecast clock,基于单调时间处理 PLAY/PAUSE、速度、恢复前后台、clear/destroy;平台 timer 仅调度/唤醒,不允许 WeatherColor 与 Particle 各推进一次。只 setTime 一次后 PLAY 自动移动 time/mix;保持 Host 显式 setTime 路径语义。
|
||||||
|
- **05 B13**:在两个端点的 GPU 更新门槛覆盖 U、V、validity、grid、encoding/解释语义及来源版本;generation、fieldID、dataVersion 不能代替完整内容身份。只变 mix 不应上传场,真正跨窗时旧 T1→新 T0 安全复用,只上传需要的新端点,不能每 tick 重建;无效/旧 revision 不可污染有效窗口。
|
||||||
|
- **06 B07**:先确认当前 GL/Metal 纹理格式、GPU 精度与 CPU/shader 编解码约束;以零、微风、正负抵消及 NaN/validity fixture 定量约束误差。选择可准确表达零的**内部**表示或明确等效方案,GL/Metal 统一;如果必须改变公共 encoding、需要不支持的图形格式或改变已发布输入 ABI,则不强行实施,记录 \`BLOCKED_FORMAT_DECISION\`。
|
||||||
|
- **07 B08**:按 eastward U / northward V m/s 推导 Web Mercator 度量,对 U/V 一致应用纬度修正、符号、极区夹紧和既有 visual time scale。建立 0°/45°/60°、东/西/南/北及极区有限值的解析对照;不得更改 Host 输入向量单位。
|
||||||
|
|
||||||
|
### 阶段 B:输入与平台生命周期(08/09/10/20)
|
||||||
|
|
||||||
|
- **08 N05**:JSON strategy/startLayerId 必须使用长度感知字符串;拒绝内嵌 NUL、重复键、未知 schema/strategy、缺失/逆序边界。无效配置 fail-closed、旧计划清除与恢复语义保持,Android/iOS/RN 错误归一化不分叉。
|
||||||
|
- **09 N06**:在已解码 frame 真正替换 Store 前,按既有 profile/validity/no-data 语义验证有效 cell 的有限 U/V 和 scale/offset 运算不溢出;事务式提交,失败返回明确错误并保留好旧帧、投影及 GPU 显示。不把合法 validity=0 或规范允许的 noData 当成错误;不同时间/高 revision 的合法行为不改变。
|
||||||
|
- **10 N07**:同 View 重复 attach/detach 幂等;map listener 有明确 remove/dispose owner 和 map/destroy 世代。测试入口覆盖 100 次复用、切 view、销毁中的异步回调,不产生悬挂访问或多重 dispose。
|
||||||
|
- **20 E01**:核实真实发布坐标、Host \`package.json\` 和 lockfile、plugin 注入的 Native 版本;将**默认** Test Host 消费组合对齐经核对的 \`.13\` 精确配对,不将临时 node_modules/本地软链当作成功。若需要 install/codegen/Gradle,则准备 RDC/用户命令并标 \`NOT RUN\`;不发布、不覆盖现有 \`.13\` 产物、不改 NavSea。
|
||||||
|
|
||||||
|
### 阶段 C:有边界的性能与资源(11–18)
|
||||||
|
|
||||||
|
严格先做只读热点/资源依赖记录,再实施不会改变正确性和公共契约的优化,优先把同一阶段兼容变更集中,避免每个小点都要求一次 Android APK。
|
||||||
|
|
||||||
|
1. **11 轻量 Diagnostics**:内部 fast/render 热路径只取同一 generation 的轻量一致状态;完整 Style/Segments 深复制仅在外部 pull 时构造。避免用若干无同步原子值组成不一致快照;低频 pull schema 兼容。
|
||||||
|
2. **16 ordinal/扫描**:预计算 global ordinal 和粒子/待更新 group 列表,topology/style reorder 后失效重建;保持 opaque reverse、translucent forward、隐藏层及全局 Z-order,不能用局部 ordinal 替代全局编号。
|
||||||
|
3. **17 物化与 UI**:相同 requestedTime 和相同不可变端点早退;mix-only 仅更新 mix,复用完整内容 identity(U/V/validity/grid/encoding),与 B13 修复共用身份。双 binding 共享安全的不可变端点,不能跨 generation 复用旧数据。Android/iOS 解码和 CRC 若转 worker,必须保存 map/source 世代、owner-thread 事务发布、有限队列、取消和 destroy race;若涉及跨平台公共异步返回语义或不能证明生命周期,先完成同步路径早退,异步部分标 \`DEFERRED_DESIGN\`。
|
||||||
|
4. **13 retained allocation**:仅在 A1 的 target clear/错误语义已经正确后,分离内容 dirty 与资源 key invalid;同尺寸、同 gfx context/format 可清除重绘;resize/context loss/关闭明确释放。相邻 R-R 仍是独立 cache。
|
||||||
|
5. **14/15 预算**:显式估算 retained color+depth/stencil+pending 与 fieldStore key/byte 上界,处理整数溢出和在途旧快照。无法分配必须走原设计的安全 fallback 或拒绝;不得静默丢弃仍绑定窗口。若新增拒绝状态无法用现有 public error contract 表达,保留安全上界/诊断并提交接口阻塞,禁止私改 RN/Native 双方错误语义。
|
||||||
|
6. **18 Drawable**:仅在能证明版本/target/UBO 生命周期正确时保留可复用 drawable/tweaker、减少 field endpoint 深复制;区分 tick、reseed、resize、context loss 与窗口轮换。未能证明不产生 stale resources 时不做大重构。
|
||||||
|
7. **12 Metal wait**:仅盘点 retained build、State/Trail 等实际同步点、命令和资源依赖,准备 Metal capture/性能基准。未经设备/renderer 验证**不得直接删 \`waitUntilCompleted\`、改成无法证明安全的异步 present**。可在现有顺序中去除明确重复且不改变依赖的等待,但须提供静态资源所有权证据;否则标 \`DEFERRED_PROFILE_AND_RUNTIME\`。
|
||||||
|
|
||||||
|
## 3. 本任务明确不进行的架构/协调事项
|
||||||
|
|
||||||
|
- **F06 multi-C**:保留既有安全 full CPU fallback 和 GPU retained reuse,不为达到指标改变 Style 顺序或暗中合并 C/R;新的可组合 multi-C fast path 单独设计、后续 A/B 验证。
|
||||||
|
- **F08 精细失效**:保留 source/tile/glyph/image/placement 的保守全局 invalidation,直到存在完整依赖与变更序列回归;不因性能冲动将其裁窄。
|
||||||
|
- **B09 Ocean Mask**:现有 synthetic fixture 不是正式共享地理 Mask。本任务只形成 Host→Shared Core→两层消费及跨平台一致性需求、最小数据边界和阻塞报告,交粒子负责人按 Integration C-001/RFC/ADR 决定;**不借任务顺便建立新的公开 mask API、导入 provider 或让 MapLibre 理解 NavSea 业务**。
|
||||||
|
- **F10/E02**:只输出“需 Integration 更新 Compatibility/Status”的事实与建议,并提供当前软件版本与真正验证状态;不修改 Integration 文档或宣称 NavSea 已支持。
|
||||||
|
- 不扩大为 renderer 全架构重写、QBIN 格式版本升级、RN 第二套桥、全量新主题、NavSea业务变动、新软件版本发布或复原旧三明治设计。
|
||||||
|
|
||||||
|
## 4. 验证准备、执行责任与验收矩阵
|
||||||
|
|
||||||
|
Codex 负责按实际代码添加**现有框架下必要的定向 C++/平台测试源码或最小 fixture**、静态核对、命令和期望结果;仅有调用说明但没有针对关键回归的测试入口,不能把相应问题标代码完成。可按现有项目测试结构写回归,但本任务**不包含 Jest,也不运行 Jest**。不修改旧断言来掩盖真实失败,不删除测试、不降低测试门槛。
|
||||||
|
|
||||||
|
| 验证集合 | 必备情形/最低证据 |
|
||||||
|
| --- | --- |
|
||||||
|
| Renderer A1 | 同 source/same tiles R-R/R-C-R、主/离屏 stencil;Metal clear/load/store、combined attachment;异常后禁止 reuse;R→C/拓扑更换计数 |
|
||||||
|
| Field A2 | 一次 setTime 后 PLAY/PAUSE/rate;T0/T1 boundary reuse;同 identity 仅 V 或 validity 变化;零/微速量化、0°/45°/60° Mercator |
|
||||||
|
| 输入 B | NUL/重复键/invalid→valid;好旧场→坏高 revision 且旧场保留;重复 attach/销毁;Host 默认版本与 lockfile |
|
||||||
|
| CPU C | pull-only diagnostics、reorder/ordinal、相同时间/仅 mix、窗口换端点、UI owner 世代安全 |
|
||||||
|
| 资源 C | mutation vs resize vs context loss、allocation fail、R×viewport×fieldKey 阶梯、清理恢复、GL/Metal resource ownership |
|
||||||
|
| 集中运行态(后续) | Android OpenGL + iOS Metal 各自像素/数值/时间/资源复现;30/60 FPS、p50/p95/p99、峰值/长稳、方向/缩放、状态恢复;按平台独立记录 |
|
||||||
|
|
||||||
|
**现在不执行** CMake、Xcode、Gradle、Codegen、Test Host install、APK/AAR、ADB、安装、真实设备、GPU capture、profiler、NavSea 集成或正式 V13 运行确认。Codex 结束时给出可复制的 RDC 验证命令(以本仓现有 runner/build 入口为准);Android Win11 手工 Gradle/ADB 和 iOS 设备验证依后续授权,不在本任务自动触发。不得把 STEP-094 历史 43/43、旧报告 69/69 或任何已发布 manifest PASS 写成本任务结果。
|
||||||
|
|
||||||
|
RDC 在每个代码批次之后独立审查文件/静态 diff;未来获授权再集中编译/运行,而不是在本轮分阶段频繁请求重装 APK。未来完整 Android 编译应遵守 \`tools/particle_android_validation/build_validation_apk.sh\` 当前权威流水线与用户手工运行安排。Metal 修改必须留给 Mac 同 commit 验证,Android 结果不得替 iOS PASS。
|
||||||
|
|
||||||
|
## 5. 允许修改与明确禁止
|
||||||
|
|
||||||
|
允许(仅与已复核问题直接相关,实际存在的路径以源码核对为准):
|
||||||
|
- \`src/mbgl/renderer/\`、\`src/mbgl/gl/\`、\`src/mbgl/mtl/\`、\`include/mbgl/\`、所需 gfx backend;
|
||||||
|
- \`src/mbgl/particle/\`、\`src/mbgl/style/sources/raster_array_source.cpp\`、\`src/mbgl/renderer/layers/\`、对应 GL/Metal shader及其标准生成源;
|
||||||
|
- Android Java/Kotlin/JNI、iOS ObjC++、RN 公共实现及 Test Host 本任务限域依赖文件;
|
||||||
|
- 对应定向测试源码/fixture、下述交付文档和开发记录。
|
||||||
|
涉及 shader generator 必须改其权威源而非只手改自动生成产物,并附原生成/校验命令由 RDC 后续执行。
|
||||||
|
|
||||||
|
禁止:修改 Integration、NavSea、项目总则、旧三份调查/任务/报告、已发布 manifest/tag/tgz/AAR/XCFramework、无关 package/lock、官方上游基线;禁止 \`git reset --hard\`、\`git clean\`、force push、push upstream;禁止把公开 API/FieldProfile/QBIN breaking 变更悄悄塞进修复。不能自行把 V13 版本重命名为下一发布版,下一版本号属于独立发布任务。
|
||||||
|
|
||||||
|
## 6. 必做输出与报告路径
|
||||||
|
|
||||||
|
执行时必须建立、更新且全部中文:
|
||||||
|
1. \`docs/30_设计/STEP-097_V13三轮问题去重与修复矩阵_v1.md\`:保留表中全部 24 行、旧编号、实际源码定位、修复/不修理由、源码状态、测试入口、commit、平台影响、运行待测项。分阶段附可量化的变更前/后操作次数模型,不虚构 FPS/内存收益。
|
||||||
|
2. \`docs/50_Step记录/STEP-097_V13三轮审查问题合并修复_v1.md\`:阶段 0/A1/A2/B/C/D 检查点、状态、实际改动、阻塞、下一步。每一阶段结束后追加短而具体的事实,未完成不得勾选。
|
||||||
|
3. \`docs/60_测试/STEP-097_V13修复后集中验证清单_v1.md\`:按 Android GL、iOS Metal、通用 Core/测试入口、后续 GPU 性能分层记录**尚未执行**的精确命令、fixtures、预期输出和复核责任。
|
||||||
|
4. \`docs/报告/STEP-097_V13三轮审查问题合并修复_Codex完成报告_v1.md\`:按规定详列开始/结束 HEAD、每批提交、文件清单、各问题状态、跨项目阻塞、静态检查实际命令和结果、所有构建/运行 \`NOT RUN\`、软件发布 \`NOT RUN\`、下一步集中 RDC 验证。
|
||||||
|
5. 在根目录 \`粒子开发记录.md\` **仅追加**实际进展;不得覆盖历史。
|
||||||
|
6. 若实际触发跨 Contract 阻塞,额外写 \`docs/报告/STEP-097_公共接口阻塞报告_v1.md\`,明确现有 C-001/RFC-001、拟议语义、为什么无法安全局部修、Android/iOS/NavSea 各自责任;本地只记录并提升级建议,不假装协调决定已被接受。
|
||||||
|
|
||||||
|
完成状态只能细分为 \`CODE_IMPLEMENTED_STATIC_CHECKED\`、\`DEFERRED_DESIGN\`、\`BLOCKED_CONTRACT\`、\`NOT_REPRODUCIBLE_WITH_EVIDENCE\`、\`WAITING_RDC_BUILD_AND_RUNTIME\`;没有执行的验证永远写 \`NOT RUN\`,产品级 \`PASS\` 禁用。
|
||||||
|
|
||||||
|
## 7. Git 收尾与停止条件
|
||||||
|
|
||||||
|
在当前 \`particle/main\` 上执行,先保护所有已有 dirty/untracked;只 stage 本任务变更。按 A1、A2、B、C 各自边界形成 scoped commit,文档随阶段更新;阶段失败不能越过到下一阶段,不可混入不相关工作。每个成功阶段允许 push \`origin particle/main\` 供 RDC 直接核对,失败必须确认进程/状态并修复,不能以 timeout 当作已失败后盲目重复 push。最终 \`git diff --check\`、提交内容、远端 HEAD、clean status 明确记录。push 失败不得宣称交付完成;提交并不代表运行验收。
|
||||||
|
|
||||||
|
触发以下任一条件则停止相应实现并生成阻塞事实:需要新增/改变公共契约而无 Integration 决定;必须升级工具链/MapLibre;不能证明跨 GL/Metal 正确或资源所有权;修复需要删除已有功能/测试或强制绕过安全 fallback;当前 HEAD 与所需源码不符且不能安全合并;既有用户改动会被覆盖;任一关键问题没有明确退出条件。必要时整任务停在已完成的 scoped checkpoint,报告 PARTIAL,不靠继续扩大代码量冒充完成。
|
||||||
|
|
||||||
|
交付后由 RDC 独立审查并按后续授权集中执行构建、回归和设备验收。**本任务不发布下一版软件,不提前关闭 V13 GPU 运行态。**
|
||||||
141
docs/40_任务/STEP-098_QBIN_V4单帧首显与八帧滚动缓存_Codex任务_v1.md
Normal file
141
docs/40_任务/STEP-098_QBIN_V4单帧首显与八帧滚动缓存_Codex任务_v1.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# STEP-098:QBIN V4 单帧首显与按 fieldKey 独立的八帧滚动缓存 — Codex 任务 v1
|
||||||
|
|
||||||
|
状态:**任务书 / 待契约闸门通过后执行**。本任务书不代表代码已经修改、编译或双平台运行验收通过。
|
||||||
|
|
||||||
|
## 0. 目标与执行环境
|
||||||
|
|
||||||
|
本任务落实已讨论的目标模型:**单帧可独立显示;第二帧到达后支持正常时间插值;后续时间片在 Native 内部进入每个 fieldKey 独立的、按 validTime 升序排序的固定 8 帧滚动缓存。** 满 8 帧提交新的合法时间片时应自动淘汰,不应因正常满额返回 `REJECTED_CAPACITY`。
|
||||||
|
|
||||||
|
当前用户维护和修改的源码以 **Mac** 为准:`/Users/tei/dev/MapLibreParticleLab`,分支 `particle/main`。编写本任务书时 Mac HEAD / origin HEAD 均为 `0d13552bed84f427dd43cae9856391bd26622179`。执行前重新查询 Git 事实,不以该 SHA 或本文行号覆盖后续提交;不要拿 Linux 旧工作树代替 Mac 代码。任务 MD 的目标归档位置为 `docs/40_任务/STEP-098_QBIN_V4单帧首显与八帧滚动缓存_Codex任务_v1.md`;不要创建 `docs/任务/`。
|
||||||
|
|
||||||
|
本阶段以 **Shared Core 公共语义和现有 RN V4 公共接口**为实施目标,不在 NavSea/Weather Server 中实施下载策略;Android 与 iOS 使用同一共享语义,平台专项编译/实体机验收分别另行完成。不得因 Mac 为编辑机而建立 iOS 专属 Core 行为。
|
||||||
|
|
||||||
|
### 固定边界
|
||||||
|
|
||||||
|
- RN:负责获取预报目录、QBIN 文件、本地下载缓存、用户选择的预报时间、异步命令 latest-wins 和 UI。RN 已提供 `FieldRuntime.submitEncodedField(...)`、`FieldRuntime.setEncodedForecastTime(...)`,**先 await 前者成功,再 await 后者**;无需新增组合提交 API。
|
||||||
|
- Shared Core/Native:负责 QBIN 校验、存储、每 fieldKey 内排序/淘汰/复用、单帧/双帧解析、颜色与粒子投影、GPU 资源正确性。
|
||||||
|
- 不增加 Native→RN 索取数据回调;Native 不接入 provider、HTTP、manifest、下载、缓存文件管理或 NavSea 业务语义。
|
||||||
|
- 不修改 QBIN 文件格式、Rice 解码语义、现有公开方法签名、Style Z-order 或 V3 路径,不在 JS 伪造第二帧,也不在 Host 手动清空 8 帧来实现正常拖动。
|
||||||
|
|
||||||
|
## 1. 首要闸门:Integration 公共契约
|
||||||
|
|
||||||
|
1. 先经 RDC 检查并同步 `/Users/tei/dev/navsea-particle-integration` 的最新 `origin/main`(`git fetch origin main` + 确认可 fast-forward;尊重现有本地改动)。读取该仓库 `README.md`、`WORKFLOW.md`、`coordination/TEAM_MODEL.md`、`contracts/`、`rfc/`、`adr/`、`status/`、`compatibility/`、`coordination/active/COORD-004/` 和 `instructions/PARTICLE_PROJECT_INSTRUCTIONS.md`。
|
||||||
|
2. 编写本任务书时 Integration HEAD 为 `318f8ccf911ac3136c3086a1d14e61f83e2d4bce`;`RFC-003_编码场两帧首显与增量补帧语义.md` 仍处于 DRAFT,其第一条要求至少两帧、满额返回容量拒绝,与本任务新目标**明确冲突**。本任务书不是已经接受的跨平台 RFC。
|
||||||
|
3. **开始改公共源码前,必须由粒子负责人/Integration 按既定流程正式更新 RFC-003、COORD/Contract 或 ADR,接受下述单帧和滚动淘汰语义,明确 Android/iOS 共用。** Codex 不得自行把 DRAFT 当 ACCEPTED,也不得静默更改 Integration 文档替代负责人决定。
|
||||||
|
4. 如果执行时仍没有已接受且不冲突的正式依据:先在 `docs/报告/STEP-098_公共契约阻塞报告_v1.md` 写出当前 SHA、冲突条目、拟实施语义与所需协调决策,**停止功能修改及 push**,状态记为 `BLOCKED_CONTRACT`,交回粒子负责人。不得绕过闸门做 Android/iOS 两套实现。
|
||||||
|
5. 若 Integration 已更新且批准,记录精确文档路径、版本与 commit SHA,后续实现以正式版本为准;如正式结论与本文不同,应停止并回报,不自行择一。
|
||||||
|
|
||||||
|
## 2. 开始前必须核对的 Mac 实际基线
|
||||||
|
|
||||||
|
先读根项目规范、`PARTICLE_BASELINE.md`(如存在)、`docs/00_项目规范/`、`docs/00_项目总则/README.md` 指定总则、与 STEP-097 的正在实施范围。读取并建立文件/函数对照表:
|
||||||
|
|
||||||
|
- `include/mbgl/particle/field/field_runtime.hpp`、`src/mbgl/particle/field/field_runtime.cpp`:`FieldSeries`、`EncodedFieldStore`、`resolve`、容量及 revision/identity。
|
||||||
|
- `src/mbgl/particle/field/field_binding.cpp`、`src/mbgl/particle/field/field_projection_materialization.cpp`:单/双帧投影、独立绑定和物化。
|
||||||
|
- `src/mbgl/style/sources/raster_array_source.cpp` 及相关头文件:`submitEncodedField`、`setRequestedForecastTime`、`refreshV4RendererState`、现有 projection equality/notification。
|
||||||
|
- `src/mbgl/renderer/layers/render_weather_color_layer.cpp`、`render_particle_validation_layer.cpp` 及 shader/texture 资源:单帧/双帧下颜色、粒子、轨迹和 GPU 复用。
|
||||||
|
- `packages/maplibre-particle-rn/src/FieldRuntime.ts`、`NativeWeatherRuntimeBridge.ts`、Android Kotlin bridge/JNI、iOS `WeatherRuntimeBridge.mm`/`MLNParticleFieldRuntime.mm`:确认两个现有公开调用完整传递结果。
|
||||||
|
- `test/particle/field_runtime.test.cpp`、`test/particle/field_binding.test.cpp`、`test/source/raster_array_v4_host_api.test.cpp` 及现有 Renderer 定向验证入口。
|
||||||
|
|
||||||
|
编写任务书时源码事实:`FieldSeries::submit` 对第 9 个不同时间直接 `RejectedCapacity`;`resolve` 对 `frames.size()<2` 返回空;`submitEncodedField` 已会刷新投影但不修改 forecast time。以上仅供定位,若新 HEAD 有变化应以实际代码为准。若 STEP-097 仍在改相同的 runtime/projection/Renderer 文件,先确认依赖顺序,不得并行覆盖或回滚已完成的修复;尤其保留 STEP-097 的输入校验、forecast clock、GPU 身份、资源生命周期等新修正。
|
||||||
|
|
||||||
|
## 3. 正式目标语义
|
||||||
|
|
||||||
|
### 3.1 缓存所有权、容量与排序
|
||||||
|
|
||||||
|
- `EncodedFieldStore` 按 `fieldKey` 分别持有 `FieldSeries`。`wind`、`wave`、`rain`、`tide` 使用不同 key 时分别最多 8 帧;同 key 的多个 Layer/Binding 共享该序列。不能把四类天气合为“全局 8 帧”。
|
||||||
|
- 每帧按 `frameContentIdentity.validTimeMs` **升序**保存,提交顺序不影响排序;不得假设固定一小时/三小时间隔。
|
||||||
|
- 已有相同 `validTime`:先走现有 Duplicate、identity conflict、stale、合法 revision 替换语义;不额外占一个槽,不因为满额淘汰别的帧。
|
||||||
|
- 不同时间且不足 8 帧:先完整校验,按时间有序插入。
|
||||||
|
- 不同时间且已满 8 帧:在成功通过解码、profile、grid/plane、身份和相邻兼容性校验后,选择淘汰一帧并原子提交新有序集合。正常满额替换成功返回现有 `ACCEPTED_INSERTED`(或正式合同要求的**既有**成功语义),不返回 `REJECTED_CAPACITY`。为了 TypeScript/ABI 向后兼容可以保留旧结果枚举,但正常容量=8的有效插入路径不再产生它;不可偷偷更名公共结果。
|
||||||
|
- 淘汰只作用于正在提交的 `fieldKey`,不影响其他天气种类、Bindings、requestedForecastTime、V3 数据或 Style。
|
||||||
|
|
||||||
|
### 3.2 双向淘汰,按新提交帧时间取中心
|
||||||
|
|
||||||
|
由于既定调用顺序是先 `submitEncodedField()`、后 `setEncodedForecastTime()`,**提交阶段不能依赖尚未下发的新目标时间**。本版使用“新提交帧 validTime 为中心,淘汰时间距离最远的端点”的确定性规则;新帧本身必须留下。对于已升序的满额序列,只在最早端或最晚端候选之间选择;若距离相同,按正式合同规定稳定决胜,允许优先避免淘汰当前显示端点,但不可牺牲新提交帧。不得永远只删除最旧帧。
|
||||||
|
|
||||||
|
必须覆盖:
|
||||||
|
|
||||||
|
- 原有示意 `13:00 ... 21:00` 共 8 帧;提交 `22:00` → 删除最早端 `13:00`,插入 `22:00`,最终仍 8 帧。
|
||||||
|
- 已向后滚动后提交 `12:00` → 删除最远的最晚端,重新保留 `12:00`;向过去拖动同样成立。
|
||||||
|
- 已满时提交位于内部的新时刻 → 仍保持有序、8 帧和确定的端点淘汰。
|
||||||
|
- 今天跳到后天某一时刻 → 接收该时刻 QBIN,而不是要求 RN 先清空旧窗口或填齐 8 帧。之后逐帧提交,Native 自行滚动。
|
||||||
|
- 乱序提交、Duplicate、同时间新 revision 不应造成意外淘汰。
|
||||||
|
- 插入不兼容数据或解码失败时,**零淘汰、零 Store/Projection/Clock 修改**;不得先删除旧帧再验证。
|
||||||
|
|
||||||
|
### 3.3 单帧首显与双帧升级
|
||||||
|
|
||||||
|
- 当请求时间精确对应某个已验证 `validTime`,即使整个 Store 只有一帧,或它与其余 7 帧相隔很远无法形成合理双帧窗口,也应能够显示该帧:颜色为这一帧的值;有有效 U/V(或规范允许的方向向量)时粒子可持续运动,**天气时间不变不等于粒子停止**;仅标量帧不能伪装成可平流向量。
|
||||||
|
- 单帧在运行时具有真实单端点语义。可在内部使用同一 Snapshot 作为 T0/T1、`mix=0` 来复用现有双纹理 shader,但不得向 RN 要求伪造第二个 QBIN 或捏造第二个不同 validTime;优先共享解码结果、物化 Snapshot 和 GPU 纹理,避免同一单帧无意义地双份常驻。
|
||||||
|
- 第二帧补入后,如果目标时间被合法的相邻端点覆盖,则自动升级为双帧插值,保持既有 `T0/T1/MIX`,端点、mix 与实际预报时间对应;如果并未覆盖,保持合法单帧或 fail-closed,不能强行把不相关的旧帧插值进去。
|
||||||
|
- 目标时间落在两帧之间且有合法覆盖时执行正常时间插值;目标时间既不精确匹配可用单帧、也没有合法双帧覆盖时返回明确 unavailable,不使用“最近旧帧”冒充该时刻。RN 若选择最近预报帧首显,必须以该帧实际 `validTime` 作为已显示时刻;用户日历选中时间与实际显示时间不可混称。例如用户初始选择 13:30,只有最近的 12:00 QBIN 时,RN 可先提交该帧并调用现有 `setEncodedForecastTime(12:00)` 显示 12:00 静态预报,仍将 13:30 保留为业务层待完成目标;等 15:00 帧到达后再设置 `setEncodedForecastTime(13:30)` 进入插值。不得在 Native 悄悄修改 `requestedForecastTime`,也不得将临时 12:00 画面标成 13:30 的插值天气。
|
||||||
|
- 不能把跨 dataset/reference/Profile 的帧混合成一个时间序列;沿用已有权威身份契约。不凭固定时间间隔猜测有效帧,也不能用任意远离的旧时间片推断新的天气。若当前元数据不足以明确区分“同一批次的合法双端点”与“跨批次伪兼容”,作为契约阻塞项报告,不能自行新造隐含协议。
|
||||||
|
- 明确 `fieldKey` 之间可有不同 validTime 覆盖。颜色与粒子在同一全局请求时刻分别 resolve,各自可处于单帧、双帧或 unavailable;不得因一个 fieldKey 无数据清空其他 key 的合法投影。
|
||||||
|
|
||||||
|
### 3.4 两个现有 RN API 的行为不变
|
||||||
|
|
||||||
|
正常时间轴操作为:
|
||||||
|
|
||||||
|
1. RN 从本地缓存或服务器取得对应 QBIN,先 `await FieldRuntime.submitEncodedField(...)`。
|
||||||
|
2. 仅在 `ACCEPTED_INSERTED`、`ACCEPTED_REPLACED_REVISION` 或可确认的 `DUPLICATE` 等成功结果后,`await FieldRuntime.setEncodedForecastTime(reactTag, targetTimeMs)`。
|
||||||
|
3. Native 的 submit **不擅自修改目标时间**,setTime 不触发网络下载。已缓存时间直接 setTime 即可;无需每次重新 submit。
|
||||||
|
4. 两调用非原子,可能在两次调用之间短暂显示旧时间、单帧或暂时不可用,**不可对外保证无闪烁**。不得把旧时刻天气伪标为新目标。RN 侧 latest-wins、目标版本和展示状态属于后续 Host 接入范围;本任务不私改 NavSea。
|
||||||
|
5. 不新增 `submitAndSetTime`、Native→RN 数据请求事件、逐帧删除 RN API 或新的 provider 参数;保留现有 `clearEncodedField()` / `clearAllEncodedFields()` 作为显式重置能力,而不是正常滚动手段。
|
||||||
|
|
||||||
|
### 3.5 Renderer 与资源安全
|
||||||
|
|
||||||
|
- 正常向前/向后补帧且当前已选 pair 和 mix 未变化时,避免无条件 rebuild、重复 GPU 上传、清空 trail 或发布无意义 Source revision;复用 existing `sameProjection`/GPU content identity。
|
||||||
|
- 单帧→双帧、双帧窗口变化和 revision 更新必须正确刷新,shader 无非法 T1/null;若相同 Snapshot 对应 T0/T1,避免重复上传同一内容。支持 GL/Metal 共享语义,不能只修 Android Renderer。
|
||||||
|
- 淘汰旧帧不能销毁仍被当前 projection/GPU 更新流程持有的不可变 Snapshot;通过正常引用计数和资源所有权安全释放。切换不同窗口时遵守现有 trail/particle reset 规则,不错误承诺跨任意远跳跃保留轨迹。
|
||||||
|
- 坏帧不允许覆盖已验证的好投影;生命周期关闭、clear、style reload、map 销毁与异步提交有明确所有权,不使旧请求重新点亮已 OFF 的画面。
|
||||||
|
|
||||||
|
### 3.6 内存边界
|
||||||
|
|
||||||
|
- 8 帧是**每 fieldKey 的数量上限**,不是无限制内存保证。每个 decoded frame 由原始 Plane 字节、validity bitset 和元数据构成;参与显示的端点另有 Float32 物化数组、图像、纹理和粒子/trail 资源。不得错误地用压缩 QBIN 大小表示运行内存。
|
||||||
|
- 不在本任务未经批准添加任意固定全局 MiB 上限或第二套容量公开错误码。为风/浪/雨/潮各 8 帧、单端点/双端点和跳跃替换提供 CPU/GPU 常驻及峰值测量入口或现有计数;记录可计算的 decoded byte 占用、资源复用/释放及最坏边界风险。
|
||||||
|
- 评估单帧 T0=T1 情况下重复物化/重复上传;保证淘汰与资源 release 后无持续增长。若测量显示 32 帧和显示资源会触及移动端内存风险,报告下一步“全局字节预算/多 fieldKey 优先级”设计建议,**不可把内存申请失败伪装成正常 REJECTED_CAPACITY**。
|
||||||
|
|
||||||
|
## 4. 实施顺序与测试矩阵
|
||||||
|
|
||||||
|
阶段 A:只读复核与契约闸门 → 阶段 B:Core Store 滚动淘汰及单帧 resolver → 阶段 C:Binding、物化、Renderer 单/双帧和资源复用 → 阶段 D:已有 RN Bridge 端到端语义检查 → 阶段 E:文件、报告、Git 收尾。前一阶段未明确通过不进入后一阶段;命令 timeout/进程未知不能算通过。发生公共 ABI、QBIN、profile、dataset 身份或无法证实跨平台一致性的阻塞时停止扩大范围。
|
||||||
|
|
||||||
|
至少具备以下定向验证;优先扩展既有 C++ 源级测试和可复用 Test Host 场景,不新建无关测试框架:
|
||||||
|
|
||||||
|
| 编号 | 场景 | 必须得到的结果 |
|
||||||
|
|---|---|---|
|
||||||
|
| T01 | 只有一帧,target=validTime | 投影合法;色场可显示;有 U/V 时粒子可动;实际时刻正确 |
|
||||||
|
| T02 | 首帧后补入第二帧,target 在两者中间 | 单帧→双帧,MIX 正确;不用伪造第二帧 |
|
||||||
|
| T03 | 8 帧未满乱序提交 | 按 validTime 升序;各 key 相互隔离 |
|
||||||
|
| T04 | 满 8 帧,向未来提交新帧 | 淘汰最早端、保留新帧,计数仍 8,无正常容量拒绝 |
|
||||||
|
| T05 | 满 8 帧,向过去提交新帧 | 淘汰最晚端、保留新帧,计数仍 8 |
|
||||||
|
| T06 | 满 8 帧,提交中间时刻 | 确定性端点淘汰;排序/唯一性不变 |
|
||||||
|
| T07 | 今天→后天单帧→后续补帧→返回今天 | 新时刻单帧可用,后续升级双帧;旧数据需 RN 重交时可正常滚动,不越界、不混批 |
|
||||||
|
| T08 | Duplicate、同时间合法新 revision、stale、identity conflict | 保留现有判定;重复/替换不淘汰其他时刻 |
|
||||||
|
| T09 | 错 QBIN、Profile/Grid/Plane 不兼容、无效向量 | 拒绝且 Store、forecast clock、原投影不被破坏 |
|
||||||
|
| T10 | 目标时间位于已缓存相邻两帧之间 | 正常插值,mix 首/中/末精确 |
|
||||||
|
| T11 | 目标不被合法帧覆盖也不精确匹配 | unavailable,不把最近帧冒充目标时间 |
|
||||||
|
| T12 | 新帧在当前 pair 外围、当前投影不变 | requestedTime、当前 pair/mix 保持;避免无意义资源重建 |
|
||||||
|
| T13 | 新帧改变当前 bracket,或当前端点被淘汰 | 正确更新;不沿用失效旧 GPU 数据、错误轨迹 |
|
||||||
|
| T14 | `wind` 满额后提交 `wave`;同 key Color+Particle | 独立 8 帧;其他 key 不受影响;同 key 不复制 Store |
|
||||||
|
| T15 | 单/双端点 Materialization/GL/Metal 资源 | 单帧无需两份独立 QBIN/CPU/GPU 内容;复用、release 与上下文丢失恢复正确 |
|
||||||
|
| T16 | 连续 100+ 次双向滚动、反复 clear、切 Runtime | 帧上限不破、无持续内存增长和悬挂引用 |
|
||||||
|
| T17 | RN 两步调用成功与失败分支 | 既有 API/TypeScript/Android/iOS bridge 类型兼容,失败不继续 setTime;不新增公开 API |
|
||||||
|
|
||||||
|
注意:Native 静态/定向测试只能证明对应源码路径,不等于手机 GPU 首帧或 NavSea UI 通过。对尚未执行的 Android/iOS 真机、滚动拖动、跨天回退、内存峰值必须标 `NOT RUN / WAITING_USER_VALIDATION`。
|
||||||
|
|
||||||
|
## 5. 修改范围与明确禁止项
|
||||||
|
|
||||||
|
允许根据当前真实源码在上述 Shared Core/Source/Renderer 及对应现有测试文件做必要的最小修改;仅在证明是既有 API 传递障碍时修改 Android/iOS bridge,保持方法签名和结果语义。可在 `docs/30_设计/` 记录淘汰不变量与单帧/双帧选择表,在 `docs/50_Step记录/` 记录实施进度。
|
||||||
|
|
||||||
|
禁止:修改 NavSea、Server、天气 provider、下载缓存、Manifest、日历 UI;重新生成 PBF、修改 Style Z-order、强行更改 QBIN/QBV3 格式;新建第二套 FieldStore/Timeline、额外 JS Normalizer/Wrapper、私有 Native 通道、自动请求 RN 数据、提交单帧伪造第二份不同时间、给四类天气合用 8 帧、让正常满额仍返回容量拒绝。不允许顺带完成 STEP-097 的未授权项,也不允许覆盖现有发布 tag/manifest 或把源码通过称为正式发布通过。
|
||||||
|
|
||||||
|
## 6. 构建、交付与 Git
|
||||||
|
|
||||||
|
- 本次编译、Gradle/Pod/Xcode 构建、APK/IPA 安装、ADB/设备操作及实际 GPU 验收由用户人工执行。Codex 可做源码、`git diff --check`、格式/静态检查及环境许可的低成本 C++ 定向测试;**不得擅自启动完整 Android/iOS 编译或设备操作**,也不得把未运行项目写成 PASS。
|
||||||
|
- 准备一份 Windows 11 可直接执行的 Android 手动验证清单,ADB 命令如需涉及当前虚拟机必须显式 `adb -s 192.168.200.139 ...`;iOS 只列 Mac 用户手工构建/设备验收条件。合并一轮验证 T01–T17,不为单个按钮或日志要求反复重装。
|
||||||
|
- 必做输出:`docs/30_设计/STEP-098_QBIN_V4单帧与八帧滚动缓存设计_v1.md`、`docs/50_Step记录/STEP-098_QBIN_V4单帧与八帧滚动缓存_v1.md`、`docs/报告/STEP-098_QBIN_V4单帧与八帧滚动缓存_Codex完成报告_v1.md`。目录不存在先创建;同主题报告已存在则递增版本,不覆盖旧报告。全部中文。
|
||||||
|
- 报告明确记录:当前 HEAD、获接受的 Integration 协议版本/commit、真实改动列表、API/ABI 是否变化、每项 T01–T17 执行状态、`REJECTED_CAPACITY` 新语义、单帧显示实际范围、CPU/GPU 内存测量与未验证项、下一步 Android/iOS/Host 责任。最多只能报告 `IMPLEMENTED / WAITING_PLATFORM_RUNTIME_VALIDATION`;契约闸门未过则 `BLOCKED_CONTRACT`。
|
||||||
|
- 完成可实施阶段且检查通过后,只提交本次任务相关变更到 **Mac 当前项目 `particle/main`**,并 push 到公开 `origin/particle/main`。提交前检查未跟踪/用户既有文件,不得 `git add -A` 混入无关变更;先核验 `origin` 是否前进,冲突时停下处理而非强推。完成报告必须写最终 commit hash、commit message、push 结果和远端 branch/HEAD;push 失败不能声称完成。若被契约闸门阻塞,不提交或 push 功能代码。
|
||||||
|
|
||||||
|
## 7. 最终验收口径
|
||||||
|
|
||||||
|
用户能在 RN 维持两个现有 API 和顺序:第一帧即显示其真实有效时间;第二帧具备合法覆盖时升级插值;后续任意顺序提交,Native 每 key 自动保持最多 8 帧。向未来提交淘汰最早,向过去提交淘汰最晚;正常已满不会 `REJECTED_CAPACITY`;四类天气互不挤占。无效帧零副作用、时间语义不造假、Core 不掌握下载/日历。Renderer 不因正常补帧无谓清屏或持续泄漏。Android 和 iOS 分别通过用户实际设备验证后,才能由 Integration 更新兼容性及正式状态。
|
||||||
690
docs/40_任务/STEP-100_SourceScoped_RenderSegment局部失效_Codex任务_v2.md
Normal file
690
docs/40_任务/STEP-100_SourceScoped_RenderSegment局部失效_Codex任务_v2.md
Normal file
@@ -0,0 +1,690 @@
|
|||||||
|
# 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 发布。
|
||||||
405
docs/40_任务/STEP-101_RenderOrchestrator销毁生命周期修复_Codex任务_v1.md
Normal file
405
docs/40_任务/STEP-101_RenderOrchestrator销毁生命周期修复_Codex任务_v1.md
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# STEP-101 RenderOrchestrator teardown 生命周期修复 — Codex 任务 v1
|
||||||
|
|
||||||
|
## 0. 任务目的
|
||||||
|
|
||||||
|
修复 Particle V18 Android 在正常 Renderer teardown / MapView reset 时发生的 Native SIGABRT。
|
||||||
|
|
||||||
|
现场已在 NavSea Android x86_64 设备稳定复现:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Fatal signal 6 (SIGABRT)
|
||||||
|
thread: RenderThread 11
|
||||||
|
|
||||||
|
FORTIFY:
|
||||||
|
pthread_mutex_lock called on a destroyed mutex
|
||||||
|
|
||||||
|
libmaplibre.so
|
||||||
|
std::__ndk1::mutex::lock()
|
||||||
|
...
|
||||||
|
mbgl::android::MapRenderer::resetRenderer()
|
||||||
|
```
|
||||||
|
|
||||||
|
V17 正常,V18 接入后稳定在启动约 9~11 秒触发。
|
||||||
|
|
||||||
|
本任务只修 Particle Native 生命周期;禁止在 NavSea 增加绕路、private wrapper、禁止阻止正常 MapView reset。
|
||||||
|
|
||||||
|
未经用户明确要求:不加入、不执行 Jest。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 冻结基线
|
||||||
|
|
||||||
|
工作树:
|
||||||
|
|
||||||
|
`/Users/tei/dev/MapLibreParticleLab-step101`
|
||||||
|
|
||||||
|
分支:
|
||||||
|
|
||||||
|
`step101-render-orchestrator-teardown`
|
||||||
|
|
||||||
|
起点必须是:
|
||||||
|
|
||||||
|
`origin/particle/main = d29e84c1a90daeea104b389d28b000c4f017d08d`
|
||||||
|
|
||||||
|
即正式 V18 Final HEAD。
|
||||||
|
|
||||||
|
V18 已发布,不得覆盖、重发或移动:
|
||||||
|
|
||||||
|
- `maplibre-particle/rn/13.4.1-particle.18`
|
||||||
|
- Android `13.4.1-particle.18`
|
||||||
|
- iOS `6.28.0-particle.18`
|
||||||
|
|
||||||
|
修复通过后由 RDC/ChatGPT 发布新不可变版本 V19。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 已知根因证据
|
||||||
|
|
||||||
|
V18 STEP-100 新增:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
RenderSegmentRevisionBook retainedSegmentRevisions;
|
||||||
|
mutable std::mutex readinessDiagnosticsMutex;
|
||||||
|
uint64_t readinessEventSequence;
|
||||||
|
std::unordered_map<std::string, SourceReadinessEventState> sourceReadinessEvents;
|
||||||
|
RenderMutationDiagnostics lastReadinessMutation;
|
||||||
|
```
|
||||||
|
|
||||||
|
多个 RenderSource / Tile observer callback 会进入:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
advanceRetainedSourceRevision(...)
|
||||||
|
recordReadinessMutation(...)
|
||||||
|
onTileChanged(...)
|
||||||
|
onTileAction(...)
|
||||||
|
buildReadinessDiagnostics(...)
|
||||||
|
retainedSegmentSourceState(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
并 lock:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
readinessDiagnosticsMutex
|
||||||
|
```
|
||||||
|
|
||||||
|
V18 当前成员声明中:
|
||||||
|
|
||||||
|
```text
|
||||||
|
renderSources
|
||||||
|
renderLayers
|
||||||
|
...
|
||||||
|
retainedSegmentRevisions
|
||||||
|
...
|
||||||
|
readinessDiagnosticsMutex
|
||||||
|
sourceReadinessEvents
|
||||||
|
...
|
||||||
|
threadPool
|
||||||
|
```
|
||||||
|
|
||||||
|
C++ 成员按声明反序析构,因此 readiness state / mutex 会早于 renderSources 销毁。
|
||||||
|
|
||||||
|
而 RenderSource 在创建时:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
renderSource->setObserver(this);
|
||||||
|
```
|
||||||
|
|
||||||
|
observer 指向 RenderOrchestrator。
|
||||||
|
|
||||||
|
当前 `RenderOrchestrator::~RenderOrchestrator()` 只:
|
||||||
|
|
||||||
|
1. contextLost 时标记 layer;
|
||||||
|
2. `threadPool.waitForEmpty()`;
|
||||||
|
|
||||||
|
没有在 readiness state 仍有效时显式 detach RenderSource observer / 清理 renderSources。
|
||||||
|
|
||||||
|
因此 V18 存在明确生命周期危险:
|
||||||
|
|
||||||
|
```text
|
||||||
|
readinessDiagnosticsMutex destroyed
|
||||||
|
↓
|
||||||
|
renderSources still destroying/alive
|
||||||
|
↓
|
||||||
|
late tile/source callback
|
||||||
|
↓
|
||||||
|
RenderOrchestrator::onTile*
|
||||||
|
↓
|
||||||
|
lock destroyed mutex
|
||||||
|
↓
|
||||||
|
FORTIFY SIGABRT
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 修复原则
|
||||||
|
|
||||||
|
不要只依赖调整成员声明顺序。
|
||||||
|
|
||||||
|
本任务应建立显式 teardown contract:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RenderOrchestrator destructor begins
|
||||||
|
↓
|
||||||
|
stop / drain deferred scheduler work
|
||||||
|
↓
|
||||||
|
detach observer from every RenderSource
|
||||||
|
↓
|
||||||
|
destroy/clear renderSources while all orchestrator state is still alive
|
||||||
|
↓
|
||||||
|
only then continue member teardown
|
||||||
|
```
|
||||||
|
|
||||||
|
同时 `RenderSource::setObserver(nullptr)` 必须安全恢复到 NullObserver。
|
||||||
|
|
||||||
|
目标是即使未来成员声明顺序变化,Source callback 也不能在 owner teardown 后访问 RenderOrchestrator。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 必做实现
|
||||||
|
|
||||||
|
### 4.1 RenderSource nullptr observer 语义
|
||||||
|
|
||||||
|
当前:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void RenderSource::setObserver(RenderSourceObserver* observer_) {
|
||||||
|
observer = observer_;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
改成与 Style Source / Layer 一致的安全语义:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
observer = observer_ ? observer_ : &nullObserver;
|
||||||
|
```
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- constructor 仍默认 NullObserver;
|
||||||
|
- `setObserver(nullptr)` 是合法 detach;
|
||||||
|
- onTileChanged / onTileError / onTileAction 不需要加 null 分支;
|
||||||
|
- 不吞掉正常有 observer 时的回调。
|
||||||
|
|
||||||
|
必须加非 Jest C++ 回归测试验证 nullptr detach 后 callback 不 crash、不调用旧 observer。
|
||||||
|
|
||||||
|
### 4.2 RenderOrchestrator 显式 teardown
|
||||||
|
|
||||||
|
在 `RenderOrchestrator::~RenderOrchestrator()` 中,在 readiness mutex/state 仍确定存活时:
|
||||||
|
|
||||||
|
1. 保留现有 contextLost layer handling;
|
||||||
|
2. 等待已有 deferred work 完成;
|
||||||
|
3. 对所有 `renderSources` 执行 observer detach;
|
||||||
|
4. 显式 `renderSources.clear()`;
|
||||||
|
5. 必要时对 glyph/image observer 也做已有 API 支持的 detach,但不要扩大范围;
|
||||||
|
6. 不允许 teardown callback 再进入 RenderOrchestrator source callback。
|
||||||
|
|
||||||
|
顺序必须结合当前 TaggedScheduler / RenderSource / TilePyramid 的真实生命周期检查,而不是机械照抄上述编号。
|
||||||
|
|
||||||
|
如果存在“clear renderSources 本身可能产生新的 scheduler work”,需要设计第二次 drain 或更安全顺序,并在报告解释。
|
||||||
|
|
||||||
|
### 4.3 生命周期状态可选保护
|
||||||
|
|
||||||
|
如果仅 detach + explicit clear 仍无法证明并发安全,允许增加最小 teardown flag,例如 atomic bool,但只作为二次防线。
|
||||||
|
|
||||||
|
禁止用 flag 代替 observer detach。
|
||||||
|
|
||||||
|
禁止 catch FORTIFY;destroyed mutex abort 不是 C++ exception。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 不得修改
|
||||||
|
|
||||||
|
- STEP-100 Source→Segment locality 语义;
|
||||||
|
- per-segment revision;
|
||||||
|
- per-segment readiness;
|
||||||
|
- mixed Build/Reuse/Fallback;
|
||||||
|
- Fast Path gate;
|
||||||
|
- singleContinuousSegment;
|
||||||
|
- Source::isLoaded;
|
||||||
|
- Style Z-order;
|
||||||
|
- provider/network;
|
||||||
|
- NavSea;
|
||||||
|
- MapRenderer::resetRenderer 正常生命周期;
|
||||||
|
- Android Activity 为避免 reset 而加 workaround。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 必做测试
|
||||||
|
|
||||||
|
### 6.1 Native 非 Jest
|
||||||
|
|
||||||
|
至少覆盖:
|
||||||
|
|
||||||
|
1. RenderSource 默认 NullObserver 可安全接 callback;
|
||||||
|
2. setObserver(real) 正常转发;
|
||||||
|
3. setObserver(nullptr) 后不再调用旧 observer;
|
||||||
|
4. detach 后 onTileChanged / onTileError / onTileAction 安全;
|
||||||
|
5. RenderOrchestrator teardown 有至少一个含 RenderSource 的场景;
|
||||||
|
6. source callback 与 teardown 边界不访问已销毁 readiness state;
|
||||||
|
7. STEP-100 locality 相关 focused tests 不回归。
|
||||||
|
|
||||||
|
如果现有测试设施不方便直接构造完整 RenderOrchestrator teardown,允许提取最小可测试 teardown helper,但禁止为了测试重构大范围生产结构。
|
||||||
|
|
||||||
|
### 6.2 Android Test App
|
||||||
|
|
||||||
|
在现有 ParticleCoreLayerValidationActivity / Validation App 增加专门 STEP-101 teardown 场景。
|
||||||
|
|
||||||
|
场景必须真实触发 Native Renderer teardown,而不是只 clear field。
|
||||||
|
|
||||||
|
至少提供:
|
||||||
|
|
||||||
|
```text
|
||||||
|
T1 RESET_ONCE
|
||||||
|
T2 RESET_LOOP_20
|
||||||
|
T3 RESET_WITH_DYNAMIC_GEOJSON
|
||||||
|
T4 RESET_WITH_DIAGNOSTICS
|
||||||
|
```
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- recreate/destroy MapView 或等价真实 renderer.reset;
|
||||||
|
- 每轮等待足够让 source/tile work 进入正常运行;
|
||||||
|
- T3 同时保持动态 GeoJSON/source mutation;
|
||||||
|
- T4 在 reset 前调用一次 RendererRuntime diagnostics,模拟 NavSea probe 时序;
|
||||||
|
- 20 次循环无 SIGABRT / crash / ANR;
|
||||||
|
- reset 后新 renderer 能继续正常显示 / 获取 diagnostics。
|
||||||
|
|
||||||
|
不得通过延迟 reset、禁用 observer、关闭 GeoJSON 来逃避问题。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. NavSea 现场等价验收
|
||||||
|
|
||||||
|
RDC 在 Codex 完成后独立执行。
|
||||||
|
|
||||||
|
Android 设备:
|
||||||
|
|
||||||
|
`192.168.200.139:5555`
|
||||||
|
|
||||||
|
必须显式 `adb -s`。
|
||||||
|
|
||||||
|
最终至少执行:
|
||||||
|
|
||||||
|
1. Validation App STEP-101 T1~T4;
|
||||||
|
2. crash buffer 检查;
|
||||||
|
3. 20 次 reset/recreate;
|
||||||
|
4. 安装 V19 进 NavSea 后做真实启动;
|
||||||
|
5. 保留 NavSea 当前 `key={map-style-generation-...}` 正常 reset 触发条件;
|
||||||
|
6. 保留/启用一次 RenderSegment diagnostics probe A/B;
|
||||||
|
7. 连续至少 5 次冷启动,每次观察 >30 秒;
|
||||||
|
8. 不允许出现:
|
||||||
|
- `pthread_mutex_lock called on a destroyed mutex`
|
||||||
|
- SIGABRT RenderThread
|
||||||
|
- `MapRenderer::resetRenderer` teardown crash。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. STEP-100 回归验收
|
||||||
|
|
||||||
|
修复 teardown 后必须确认 V18 的核心 locality 没被破坏。
|
||||||
|
|
||||||
|
至少复测 T4 R-C-R:
|
||||||
|
|
||||||
|
无关 R0:
|
||||||
|
|
||||||
|
```text
|
||||||
|
buildDelta = 0
|
||||||
|
invalidationDelta = 0
|
||||||
|
reuseDelta > 0
|
||||||
|
```
|
||||||
|
|
||||||
|
动态 R1 正常 build/invalidation。
|
||||||
|
|
||||||
|
禁止通过删除 mutex/readiness/revision 功能来修 crash。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Codex 执行边界
|
||||||
|
|
||||||
|
Codex负责:
|
||||||
|
|
||||||
|
- 只读检查真实 V18 代码;
|
||||||
|
- 实现最小 Native 修复;
|
||||||
|
- 编写/更新非 Jest C++ tests;
|
||||||
|
- 更新 Android Validation STEP-101 场景;
|
||||||
|
- 静态检查;
|
||||||
|
- 生成报告。
|
||||||
|
|
||||||
|
Codex不负责:
|
||||||
|
|
||||||
|
- NavSea 修改;
|
||||||
|
- V19 release;
|
||||||
|
- tag;
|
||||||
|
- publish;
|
||||||
|
- 覆盖 V18;
|
||||||
|
- Jest。
|
||||||
|
|
||||||
|
Mac Native 开发/编译原则保持;不要使用 Linux Native 环境。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 完成报告
|
||||||
|
|
||||||
|
生成:
|
||||||
|
|
||||||
|
`docs/报告/STEP-101_RenderOrchestrator销毁生命周期修复_Codex完成报告_v1.md`
|
||||||
|
|
||||||
|
Step 记录:
|
||||||
|
|
||||||
|
`docs/50_Step记录/STEP-101_RenderOrchestrator销毁生命周期修复_v1.md`
|
||||||
|
|
||||||
|
报告必须写:
|
||||||
|
|
||||||
|
- root cause;
|
||||||
|
- start HEAD;
|
||||||
|
- 修改文件;
|
||||||
|
- destructor teardown 顺序;
|
||||||
|
- observer detach 语义;
|
||||||
|
- 为什么不依赖成员声明顺序;
|
||||||
|
- tests added;
|
||||||
|
- 实际运行 / NOT RUN;
|
||||||
|
- STEP-100 locality 是否保持;
|
||||||
|
- 风险;
|
||||||
|
- 未发布 V19(Codex 阶段)。
|
||||||
|
|
||||||
|
Codex阶段不 push,由 RDC 验收后统一 commit/push。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. V19 发布门槛
|
||||||
|
|
||||||
|
仅当:
|
||||||
|
|
||||||
|
- Native compile PASS;
|
||||||
|
- focused C++ tests PASS;
|
||||||
|
- Android Validation teardown loop PASS;
|
||||||
|
- STEP-100 locality regression PASS;
|
||||||
|
- NavSea V19 真实 5 次冷启动 >30s PASS;
|
||||||
|
- crash buffer 无 destroyed mutex;
|
||||||
|
|
||||||
|
才允许发布:
|
||||||
|
|
||||||
|
- RN / Android:`13.4.1-particle.19`
|
||||||
|
- iOS:`6.28.0-particle.19`
|
||||||
|
- tag:`maplibre-particle/rn/13.4.1-particle.19`
|
||||||
|
|
||||||
|
V18 保持不可变,不覆盖、不删除。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 最终原则
|
||||||
|
|
||||||
|
正常:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MapView unmount
|
||||||
|
Style generation replacement
|
||||||
|
renderer.reset()
|
||||||
|
Activity recreate
|
||||||
|
```
|
||||||
|
|
||||||
|
都是合法生命周期。
|
||||||
|
|
||||||
|
Particle Native 必须做到:
|
||||||
|
|
||||||
|
> Renderer teardown 永远不能因为 observer late callback 访问已销毁 mutex/state 而杀掉宿主 App。
|
||||||
510
docs/40_任务/STEP-101_RenderOrchestrator销毁生命周期修复_Codex任务_v2.md
Normal file
510
docs/40_任务/STEP-101_RenderOrchestrator销毁生命周期修复_Codex任务_v2.md
Normal file
@@ -0,0 +1,510 @@
|
|||||||
|
# STEP-101 RenderOrchestrator teardown 生命周期修复 — Codex 任务 v1
|
||||||
|
|
||||||
|
## 0. 任务目的
|
||||||
|
|
||||||
|
修复 Particle V18 Android 在正常 Renderer teardown / MapView reset 时发生的 Native SIGABRT。
|
||||||
|
|
||||||
|
现场已在 NavSea Android x86_64 设备稳定复现:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Fatal signal 6 (SIGABRT)
|
||||||
|
thread: RenderThread 11
|
||||||
|
|
||||||
|
FORTIFY:
|
||||||
|
pthread_mutex_lock called on a destroyed mutex
|
||||||
|
|
||||||
|
libmaplibre.so
|
||||||
|
std::__ndk1::mutex::lock()
|
||||||
|
...
|
||||||
|
mbgl::android::MapRenderer::resetRenderer()
|
||||||
|
```
|
||||||
|
|
||||||
|
V17 正常,V18 接入后稳定在启动约 9~11 秒触发。
|
||||||
|
|
||||||
|
本任务只修 Particle Native 生命周期;禁止在 NavSea 增加绕路、private wrapper、禁止阻止正常 MapView reset。
|
||||||
|
|
||||||
|
未经用户明确要求:不加入、不执行 Jest。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 冻结基线
|
||||||
|
|
||||||
|
工作树:
|
||||||
|
|
||||||
|
`/Users/tei/dev/MapLibreParticleLab-step101`
|
||||||
|
|
||||||
|
分支:
|
||||||
|
|
||||||
|
`step101-render-orchestrator-teardown`
|
||||||
|
|
||||||
|
起点必须是:
|
||||||
|
|
||||||
|
`origin/particle/main = d29e84c1a90daeea104b389d28b000c4f017d08d`
|
||||||
|
|
||||||
|
即正式 V18 Final HEAD。
|
||||||
|
|
||||||
|
V18 已发布,不得覆盖、重发或移动:
|
||||||
|
|
||||||
|
- `maplibre-particle/rn/13.4.1-particle.18`
|
||||||
|
- Android `13.4.1-particle.18`
|
||||||
|
- iOS `6.28.0-particle.18`
|
||||||
|
|
||||||
|
修复通过后由 RDC/ChatGPT 发布新不可变版本 V19。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 已知根因证据
|
||||||
|
|
||||||
|
V18 STEP-100 新增:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
RenderSegmentRevisionBook retainedSegmentRevisions;
|
||||||
|
mutable std::mutex readinessDiagnosticsMutex;
|
||||||
|
uint64_t readinessEventSequence;
|
||||||
|
std::unordered_map<std::string, SourceReadinessEventState> sourceReadinessEvents;
|
||||||
|
RenderMutationDiagnostics lastReadinessMutation;
|
||||||
|
```
|
||||||
|
|
||||||
|
多个 RenderSource / Tile observer callback 会进入:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
advanceRetainedSourceRevision(...)
|
||||||
|
recordReadinessMutation(...)
|
||||||
|
onTileChanged(...)
|
||||||
|
onTileAction(...)
|
||||||
|
buildReadinessDiagnostics(...)
|
||||||
|
retainedSegmentSourceState(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
并 lock:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
readinessDiagnosticsMutex
|
||||||
|
```
|
||||||
|
|
||||||
|
V18 当前成员声明中:
|
||||||
|
|
||||||
|
```text
|
||||||
|
renderSources
|
||||||
|
renderLayers
|
||||||
|
...
|
||||||
|
retainedSegmentRevisions
|
||||||
|
...
|
||||||
|
readinessDiagnosticsMutex
|
||||||
|
sourceReadinessEvents
|
||||||
|
...
|
||||||
|
threadPool
|
||||||
|
```
|
||||||
|
|
||||||
|
C++ 成员按声明反序析构,因此 readiness state / mutex 会早于 renderSources 销毁。
|
||||||
|
|
||||||
|
而 RenderSource 在创建时:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
renderSource->setObserver(this);
|
||||||
|
```
|
||||||
|
|
||||||
|
observer 指向 RenderOrchestrator。
|
||||||
|
|
||||||
|
当前 `RenderOrchestrator::~RenderOrchestrator()` 只:
|
||||||
|
|
||||||
|
1. contextLost 时标记 layer;
|
||||||
|
2. `threadPool.waitForEmpty()`;
|
||||||
|
|
||||||
|
没有在 readiness state 仍有效时显式 detach RenderSource observer / 清理 renderSources。
|
||||||
|
|
||||||
|
因此 V18 存在明确生命周期危险:
|
||||||
|
|
||||||
|
```text
|
||||||
|
readinessDiagnosticsMutex destroyed
|
||||||
|
↓
|
||||||
|
renderSources still destroying/alive
|
||||||
|
↓
|
||||||
|
late tile/source callback
|
||||||
|
↓
|
||||||
|
RenderOrchestrator::onTile*
|
||||||
|
↓
|
||||||
|
lock destroyed mutex
|
||||||
|
↓
|
||||||
|
FORTIFY SIGABRT
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 修复原则
|
||||||
|
|
||||||
|
不要只依赖调整成员声明顺序。
|
||||||
|
|
||||||
|
本任务应建立显式 teardown contract:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RenderOrchestrator destructor begins
|
||||||
|
↓
|
||||||
|
stop / drain deferred scheduler work
|
||||||
|
↓
|
||||||
|
detach observer from every RenderSource
|
||||||
|
↓
|
||||||
|
destroy/clear renderSources while all orchestrator state is still alive
|
||||||
|
↓
|
||||||
|
only then continue member teardown
|
||||||
|
```
|
||||||
|
|
||||||
|
同时 `RenderSource::setObserver(nullptr)` 必须安全恢复到 NullObserver。
|
||||||
|
|
||||||
|
目标是即使未来成员声明顺序变化,Source callback 也不能在 owner teardown 后访问 RenderOrchestrator。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 必做实现
|
||||||
|
|
||||||
|
### 4.1 RenderSource nullptr observer 语义
|
||||||
|
|
||||||
|
当前:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void RenderSource::setObserver(RenderSourceObserver* observer_) {
|
||||||
|
observer = observer_;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
改成与 Style Source / Layer 一致的安全语义:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
observer = observer_ ? observer_ : &nullObserver;
|
||||||
|
```
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- constructor 仍默认 NullObserver;
|
||||||
|
- `setObserver(nullptr)` 是合法 detach;
|
||||||
|
- onTileChanged / onTileError / onTileAction 不需要加 null 分支;
|
||||||
|
- 不吞掉正常有 observer 时的回调。
|
||||||
|
|
||||||
|
必须加非 Jest C++ 回归测试验证 nullptr detach 后 callback 不 crash、不调用旧 observer。
|
||||||
|
|
||||||
|
### 4.2 RenderOrchestrator 显式 teardown
|
||||||
|
|
||||||
|
在 `RenderOrchestrator::~RenderOrchestrator()` 中,在 readiness mutex/state 仍确定存活时:
|
||||||
|
|
||||||
|
1. 保留现有 contextLost layer handling;
|
||||||
|
2. 等待已有 deferred work 完成;
|
||||||
|
3. 对所有 `renderSources` 执行 observer detach;
|
||||||
|
4. 显式 `renderSources.clear()`;
|
||||||
|
5. 必要时对 glyph/image observer 也做已有 API 支持的 detach,但不要扩大范围;
|
||||||
|
6. 不允许 teardown callback 再进入 RenderOrchestrator source callback。
|
||||||
|
|
||||||
|
顺序必须结合当前 TaggedScheduler / RenderSource / TilePyramid 的真实生命周期检查,而不是机械照抄上述编号。
|
||||||
|
|
||||||
|
如果存在“clear renderSources 本身可能产生新的 scheduler work”,需要设计第二次 drain 或更安全顺序,并在报告解释。
|
||||||
|
|
||||||
|
### 4.3 生命周期状态可选保护
|
||||||
|
|
||||||
|
如果仅 detach + explicit clear 仍无法证明并发安全,允许增加最小 teardown flag,例如 atomic bool,但只作为二次防线。
|
||||||
|
|
||||||
|
禁止用 flag 代替 observer detach。
|
||||||
|
|
||||||
|
禁止 catch FORTIFY;destroyed mutex abort 不是 C++ exception。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 不得修改
|
||||||
|
|
||||||
|
- STEP-100 Source→Segment locality 语义;
|
||||||
|
- per-segment revision;
|
||||||
|
- per-segment readiness;
|
||||||
|
- mixed Build/Reuse/Fallback;
|
||||||
|
- Fast Path gate;
|
||||||
|
- singleContinuousSegment;
|
||||||
|
- Source::isLoaded;
|
||||||
|
- Style Z-order;
|
||||||
|
- provider/network;
|
||||||
|
- NavSea;
|
||||||
|
- MapRenderer::resetRenderer 正常生命周期;
|
||||||
|
- Android Activity 为避免 reset 而加 workaround。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 必做测试
|
||||||
|
|
||||||
|
### 6.1 Native 非 Jest
|
||||||
|
|
||||||
|
至少覆盖:
|
||||||
|
|
||||||
|
1. RenderSource 默认 NullObserver 可安全接 callback;
|
||||||
|
2. setObserver(real) 正常转发;
|
||||||
|
3. setObserver(nullptr) 后不再调用旧 observer;
|
||||||
|
4. detach 后 onTileChanged / onTileError / onTileAction 安全;
|
||||||
|
5. RenderOrchestrator teardown 有至少一个含 RenderSource 的场景;
|
||||||
|
6. source callback 与 teardown 边界不访问已销毁 readiness state;
|
||||||
|
7. STEP-100 locality 相关 focused tests 不回归。
|
||||||
|
|
||||||
|
如果现有测试设施不方便直接构造完整 RenderOrchestrator teardown,允许提取最小可测试 teardown helper,但禁止为了测试重构大范围生产结构。
|
||||||
|
|
||||||
|
### 6.2 Android Test App
|
||||||
|
|
||||||
|
在现有 ParticleCoreLayerValidationActivity / Validation App 增加专门 STEP-101 teardown 场景。
|
||||||
|
|
||||||
|
场景必须真实触发 Native Renderer teardown,而不是只 clear field。
|
||||||
|
|
||||||
|
至少提供:
|
||||||
|
|
||||||
|
```text
|
||||||
|
T1 RESET_ONCE
|
||||||
|
T2 RESET_LOOP_20
|
||||||
|
T3 RESET_WITH_DYNAMIC_GEOJSON
|
||||||
|
T4 RESET_WITH_DIAGNOSTICS
|
||||||
|
```
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- recreate/destroy MapView 或等价真实 renderer.reset;
|
||||||
|
- 每轮等待足够让 source/tile work 进入正常运行;
|
||||||
|
- T3 同时保持动态 GeoJSON/source mutation;
|
||||||
|
- T4 在 reset 前调用一次 RendererRuntime diagnostics,模拟 NavSea probe 时序;
|
||||||
|
- 20 次循环无 SIGABRT / crash / ANR;
|
||||||
|
- reset 后新 renderer 能继续正常显示 / 获取 diagnostics。
|
||||||
|
|
||||||
|
不得通过延迟 reset、禁用 observer、关闭 GeoJSON 来逃避问题。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. NavSea 现场等价验收
|
||||||
|
|
||||||
|
RDC 在 Codex 完成后独立执行。
|
||||||
|
|
||||||
|
Android 设备:
|
||||||
|
|
||||||
|
`192.168.200.139:5555`
|
||||||
|
|
||||||
|
必须显式 `adb -s`。
|
||||||
|
|
||||||
|
最终至少执行:
|
||||||
|
|
||||||
|
1. Validation App STEP-101 T1~T4;
|
||||||
|
2. crash buffer 检查;
|
||||||
|
3. 20 次 reset/recreate;
|
||||||
|
4. 安装 V19 进 NavSea 后做真实启动;
|
||||||
|
5. 保留 NavSea 当前 `key={map-style-generation-...}` 正常 reset 触发条件;
|
||||||
|
6. 保留/启用一次 RenderSegment diagnostics probe A/B;
|
||||||
|
7. 连续至少 5 次冷启动,每次观察 >30 秒;
|
||||||
|
8. 不允许出现:
|
||||||
|
- `pthread_mutex_lock called on a destroyed mutex`
|
||||||
|
- SIGABRT RenderThread
|
||||||
|
- `MapRenderer::resetRenderer` teardown crash。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. STEP-100 回归验收
|
||||||
|
|
||||||
|
修复 teardown 后必须确认 V18 的核心 locality 没被破坏。
|
||||||
|
|
||||||
|
至少复测 T4 R-C-R:
|
||||||
|
|
||||||
|
无关 R0:
|
||||||
|
|
||||||
|
```text
|
||||||
|
buildDelta = 0
|
||||||
|
invalidationDelta = 0
|
||||||
|
reuseDelta > 0
|
||||||
|
```
|
||||||
|
|
||||||
|
动态 R1 正常 build/invalidation。
|
||||||
|
|
||||||
|
禁止通过删除 mutex/readiness/revision 功能来修 crash。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Codex 执行边界
|
||||||
|
|
||||||
|
Codex负责:
|
||||||
|
|
||||||
|
- 只读检查真实 V18 代码;
|
||||||
|
- 实现最小 Native 修复;
|
||||||
|
- 编写/更新非 Jest C++ tests;
|
||||||
|
- 更新 Android Validation STEP-101 场景;
|
||||||
|
- 静态检查;
|
||||||
|
- 生成报告。
|
||||||
|
|
||||||
|
Codex不负责:
|
||||||
|
|
||||||
|
- NavSea 修改;
|
||||||
|
- V19 release;
|
||||||
|
- tag;
|
||||||
|
- publish;
|
||||||
|
- 覆盖 V18;
|
||||||
|
- Jest。
|
||||||
|
|
||||||
|
Mac Native 开发/编译原则保持;不要使用 Linux Native 环境。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 完成报告
|
||||||
|
|
||||||
|
生成:
|
||||||
|
|
||||||
|
`docs/报告/STEP-101_RenderOrchestrator销毁生命周期修复_Codex完成报告_v1.md`
|
||||||
|
|
||||||
|
Step 记录:
|
||||||
|
|
||||||
|
`docs/50_Step记录/STEP-101_RenderOrchestrator销毁生命周期修复_v1.md`
|
||||||
|
|
||||||
|
报告必须写:
|
||||||
|
|
||||||
|
- root cause;
|
||||||
|
- start HEAD;
|
||||||
|
- 修改文件;
|
||||||
|
- destructor teardown 顺序;
|
||||||
|
- observer detach 语义;
|
||||||
|
- 为什么不依赖成员声明顺序;
|
||||||
|
- tests added;
|
||||||
|
- 实际运行 / NOT RUN;
|
||||||
|
- STEP-100 locality 是否保持;
|
||||||
|
- 风险;
|
||||||
|
- 未发布 V19(Codex 阶段)。
|
||||||
|
|
||||||
|
Codex阶段不 push,由 RDC 验收后统一 commit/push。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. V19 发布门槛
|
||||||
|
|
||||||
|
仅当:
|
||||||
|
|
||||||
|
- Native compile PASS;
|
||||||
|
- focused C++ tests PASS;
|
||||||
|
- Android Validation teardown loop PASS;
|
||||||
|
- STEP-100 locality regression PASS;
|
||||||
|
- NavSea V19 真实 5 次冷启动 >30s PASS;
|
||||||
|
- crash buffer 无 destroyed mutex;
|
||||||
|
|
||||||
|
才允许发布:
|
||||||
|
|
||||||
|
- RN / Android:`13.4.1-particle.19`
|
||||||
|
- iOS:`6.28.0-particle.19`
|
||||||
|
- tag:`maplibre-particle/rn/13.4.1-particle.19`
|
||||||
|
|
||||||
|
V18 保持不可变,不覆盖、不删除。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 最终原则
|
||||||
|
|
||||||
|
正常:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MapView unmount
|
||||||
|
Style generation replacement
|
||||||
|
renderer.reset()
|
||||||
|
Activity recreate
|
||||||
|
```
|
||||||
|
|
||||||
|
都是合法生命周期。
|
||||||
|
|
||||||
|
Particle Native 必须做到:
|
||||||
|
|
||||||
|
> Renderer teardown 永远不能因为 observer late callback 访问已销毁 mutex/state 而杀掉宿主 App。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 13. Codex 6-Sol High 架构评审修订(v2,优先级高于前文冲突表述)
|
||||||
|
|
||||||
|
只读评审结论:`APPROVE_WITH_CHANGES`。
|
||||||
|
|
||||||
|
## 13.1 根因表述修正
|
||||||
|
|
||||||
|
当前已经**代码层证实**存在生命周期漏洞:`readinessDiagnosticsMutex` / readiness state 的析构顺序早于 `renderSources`,而 `RenderSource` observer 仍指向 `RenderOrchestrator`;source/tile teardown 可同步或延迟回调进入 `onTileAction/onTileChanged` 并访问 mutex。
|
||||||
|
|
||||||
|
该漏洞与现场 `pthread_mutex_lock called on a destroyed mutex` 高度吻合;在没有完整符号化 crash stack 精确到具体 callback 前,不把“唯一现场根因”写成数学意义上的唯一证明。
|
||||||
|
|
||||||
|
## 13.2 teardown 必须双 drain
|
||||||
|
|
||||||
|
最低安全顺序调整为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
contextLost layer handling
|
||||||
|
↓
|
||||||
|
第一次 threadPool.waitForEmpty()
|
||||||
|
↓
|
||||||
|
detach RenderSource observers
|
||||||
|
↓
|
||||||
|
detach GlyphManager / ImageManager observers
|
||||||
|
↓
|
||||||
|
renderSources.clear() // readiness mutex/state 此时仍存活
|
||||||
|
↓
|
||||||
|
第二次 threadPool.waitForEmpty()
|
||||||
|
↓
|
||||||
|
继续既有 backend/context render-job teardown
|
||||||
|
↓
|
||||||
|
成员析构
|
||||||
|
```
|
||||||
|
|
||||||
|
原因:source/tile destruction 本身可能投递新的 tagged cleanup/deferred work,第一次 drain 不能覆盖 clear 过程中产生的新任务。
|
||||||
|
|
||||||
|
同时必须审计并在报告说明:`TaggedScheduler::waitForEmpty()` 不等于清空 `runOnRenderThread` 队列;后者依赖现有 backend/context render-job 生命周期。不得声称双 drain 自动证明所有 render-thread job 已清空。
|
||||||
|
|
||||||
|
## 13.3 Observer detach 范围
|
||||||
|
|
||||||
|
除了所有 `RenderSource`:
|
||||||
|
|
||||||
|
- `glyphManager->setObserver(nullptr)`;
|
||||||
|
- `imageManager->setObserver(nullptr)`;
|
||||||
|
|
||||||
|
也应在 owner teardown 时显式 detach(前提是现有 API 的 nullptr 已安全回 NullObserver)。
|
||||||
|
|
||||||
|
`RenderSource::setObserver(nullptr)` 必须改为 NullObserver fallback。
|
||||||
|
|
||||||
|
不得直接给 `TilePyramid` / `Tile` observer 写 nullptr,除非其 API 本身有同等安全语义;本任务不扩展到这一步。
|
||||||
|
|
||||||
|
## 13.4 teardown flag
|
||||||
|
|
||||||
|
默认不增加 teardown atomic flag。
|
||||||
|
|
||||||
|
若实现审计发现真实并发 callback 不受 renderer thread / tagged scheduler 串行保证约束,必须先报告;atomic flag 只能二次防护,不能替代 observer detach、drain 和可等待取消。
|
||||||
|
|
||||||
|
## 13.5 Android Validation 证据升级
|
||||||
|
|
||||||
|
T1~T4 每一轮必须能证明发生了**真实 Native renderer reset**:
|
||||||
|
|
||||||
|
- 旧 MapView detach / SurfaceView detach;
|
||||||
|
- `nativeReset()`;
|
||||||
|
- `MapRenderer::resetRenderer()` 进入/完成;
|
||||||
|
- 新 renderer style ready;
|
||||||
|
- 新 renderer diagnostics 可用。
|
||||||
|
|
||||||
|
不能把 `setStyle()`、clear field、只读 diagnostics 当成 teardown 测试。
|
||||||
|
|
||||||
|
推荐按代次销毁并重建 MapView,或 Activity recreate;T2 至少 20 次已确认 reset。
|
||||||
|
|
||||||
|
T3:每轮 reset 前保持动态 GeoJSON/source mutation。
|
||||||
|
|
||||||
|
T4:每轮 reset 前调用一次 Native diagnostics,模拟 NavSea 8 秒 probe 的等价条件。
|
||||||
|
|
||||||
|
每轮应等待 source/tile 确实进入运行态,而不是只按固定 sleep 机械计数。
|
||||||
|
|
||||||
|
## 13.6 测试必须覆盖真实 source teardown
|
||||||
|
|
||||||
|
空 `RenderOrchestrator` 析构测试不够。
|
||||||
|
|
||||||
|
非 Jest C++ 测试至少需要一个含 RenderSource / tile callback 的 teardown 场景,使 source cancellation / callback 能在销毁边界发生,并验证旧 observer 不再收到回调。
|
||||||
|
|
||||||
|
## 13.7 正常 source erase / clearData 审计
|
||||||
|
|
||||||
|
必须只读检查 `clearData()` 和正常 source erase/replacement 路径,因为它们也会销毁 RenderSource。
|
||||||
|
|
||||||
|
本任务主要修 owner teardown;不得未经证明把 destructor 修复泛化为“所有 source replacement 异步问题均已解决”。若发现相同 observer 生命周期漏洞在正常 erase 路径存在,应做最小同源修复或明确 BLOCK,不得隐藏。
|
||||||
|
|
||||||
|
## 13.8 V19 候选验收顺序
|
||||||
|
|
||||||
|
V19 发布门槛修正为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
STEP-101 source code PASS
|
||||||
|
→ 构建未公开的 V19 candidate artifacts
|
||||||
|
→ Validation App 实机 PASS
|
||||||
|
→ NavSea 使用 candidate 做 5 次冷启动/teardown 验收
|
||||||
|
→ 公开发布不可变 V19
|
||||||
|
→ public readback
|
||||||
|
→ finalize/tag
|
||||||
|
```
|
||||||
|
|
||||||
|
不得先公开 V19 再做 NavSea 验收。
|
||||||
262
docs/40_任务/STEP-102_RenderSegment无Placement段局部复用_Codex任务_v1.md
Normal file
262
docs/40_任务/STEP-102_RenderSegment无Placement段局部复用_Codex任务_v1.md
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
# STEP-102 RenderSegment 无 Placement 段局部复用 Codex 任务 v1
|
||||||
|
|
||||||
|
## 1. Step 编号和目的
|
||||||
|
|
||||||
|
STEP-102:修复 full CPU 多 Retained Segment 下 placement aggregate gate 的不必要扩散,使**结构上不具备 placement 能力**的 Retained Segment 能在其他 placement-bearing Segment fallback/live 时继续独立 composite/reuse。
|
||||||
|
|
||||||
|
本 Step 的设计依据:
|
||||||
|
`docs/20_调查/RenderSegment多Retained段全局Placement门槛_粒子管理员审查说明_v3.md`。
|
||||||
|
|
||||||
|
## 2. 当前已冻结基线
|
||||||
|
|
||||||
|
- 仓库:`/Users/tei/dev/MapLibreParticleLab-step102`
|
||||||
|
- branch:`step102-placement-free-retained-locality`
|
||||||
|
- start HEAD:`6df21dac38f072c5ad35b8dfc7ec5217509602bc`
|
||||||
|
- upstream base:MapLibre Native core 既有冻结基线,不升级
|
||||||
|
- V19/V20 release worktree:冻结,只读,不修改
|
||||||
|
- STEP-100 source-scoped locality:必须保持
|
||||||
|
- STEP-101 teardown 修复:必须保持
|
||||||
|
- Fast Path:本 Step 不修改
|
||||||
|
- C-001:不升级
|
||||||
|
|
||||||
|
## 3. 本 Step 目标
|
||||||
|
|
||||||
|
必须实现:
|
||||||
|
1. 增加结构性 placement-capable range 查询;
|
||||||
|
2. full CPU 每个 Retained Segment 独立计算 placement gate;
|
||||||
|
3. placement-free Retained 不再被其他 Segment 的 placementChanged/symbolFadeChange 纯 aggregate gate 拖入 fallback;
|
||||||
|
4. placement-bearing Retained 继续使用现有保守 placement gate;
|
||||||
|
5. 从 full CPU `globalLiveFallback` 移除纯 aggregate placement 条件;
|
||||||
|
6. 保持真正全局 fail-closed 条件;
|
||||||
|
7. diagnostics 增加结构 placement、per-segment gate、实际 composite/live 与全局 main-buffer execution 信息;
|
||||||
|
8. RN diagnostics TS 类型做 additive 扩展;
|
||||||
|
9. 增加对应 C++ 测试源码与 Android Validation 场景源码;
|
||||||
|
10. 不改变 Style Z-order、mixed executor、C-001 和 RN method surface。
|
||||||
|
|
||||||
|
## 4. 非目标 / 禁止事项
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
- placement-bearing Segment 之间的局部碰撞缓存;
|
||||||
|
- per-segment collision graph / symbol fade / placement fingerprint;
|
||||||
|
- Fast Path 改造;
|
||||||
|
- multi-continuous Fast Path;
|
||||||
|
- 重写 `visitRenderSegmentExecution()`;
|
||||||
|
- 修改 NavSea;
|
||||||
|
- 新增 NavSea 私有 Native API;
|
||||||
|
- 修改 C-001;
|
||||||
|
- 修改 V19/V20 release worktree/tag;
|
||||||
|
- 升级 MapLibre、NDK、Gradle、AGP、Java、Node 或其他工具链;
|
||||||
|
- 为通过测试降低 fail-closed 门槛;
|
||||||
|
- Android Gradle/build、APK/AAR build、ADB、设备操作;
|
||||||
|
- Jest。用户未要求 Jest,本任务不得新增或执行 Jest。
|
||||||
|
|
||||||
|
## 5. 允许修改范围
|
||||||
|
|
||||||
|
生产代码优先限制在:
|
||||||
|
- `src/mbgl/renderer/render_orchestrator.hpp`
|
||||||
|
- `src/mbgl/renderer/render_orchestrator.cpp`
|
||||||
|
- `src/mbgl/renderer/renderer_impl.cpp`
|
||||||
|
- `include/mbgl/renderer/render_segment.hpp`
|
||||||
|
- `src/mbgl/renderer/retained_underlay_runtime_state.hpp`
|
||||||
|
- `src/mbgl/renderer/render_segment_json.hpp`
|
||||||
|
- `packages/maplibre-particle-rn/src/types.ts`
|
||||||
|
|
||||||
|
测试/Validation 允许:
|
||||||
|
- `test/renderer/retained_underlay_cache.test.cpp`
|
||||||
|
- 与现有 Particle Validation 场景直接相关的最小 Android Test App 文件
|
||||||
|
|
||||||
|
文档允许:
|
||||||
|
- 本任务书
|
||||||
|
- `docs/50_Step记录/STEP-102_RenderSegment无Placement段局部复用_v1.md`
|
||||||
|
- `docs/报告/STEP-102_RenderSegment无Placement段局部复用_Codex完成报告_v1.md`
|
||||||
|
- `粒子开发记录.md`
|
||||||
|
- v3 审查说明只读,除非发现明确事实错误;若需改,停止并先报告
|
||||||
|
|
||||||
|
如果实现必须扩大生产代码范围,先停止并报告原因,不得自行扩张架构。
|
||||||
|
|
||||||
|
## 6. 禁止修改范围
|
||||||
|
|
||||||
|
- NavSea 仓库
|
||||||
|
- `navsea-particle-integration` contract/RFC/ADR
|
||||||
|
- Fast Path 逻辑、`tryRenderParticleFastFrame()`、`singleContinuousSegment()` 语义
|
||||||
|
- `retainedRangeUsesPlacement()` 现有动态语义
|
||||||
|
- mixed executor 算法
|
||||||
|
- Source `isLoaded()` 定义
|
||||||
|
- STEP-100 source revision/locality 语义
|
||||||
|
- STEP-101 teardown 生命周期语义
|
||||||
|
- renderer/backend 架构
|
||||||
|
- release/version/tag/publish 坐标
|
||||||
|
|
||||||
|
## 7. 实施要求
|
||||||
|
|
||||||
|
### 7.1 结构性 placement-capable 查询
|
||||||
|
|
||||||
|
新增 full CPU retained 专用查询,必须基于 layer 结构,而不是当前帧 placementData。
|
||||||
|
|
||||||
|
最小保守语义:
|
||||||
|
- range 中存在 `symbol` layer → placement-capable
|
||||||
|
- 无 `symbol` layer → placement-free
|
||||||
|
- custom/custom-drawable 继续由 unsupported gate 处理
|
||||||
|
|
||||||
|
不得用 `needsPlacement()==false` 证明 placement-free。
|
||||||
|
|
||||||
|
### 7.2 per-segment placement gate
|
||||||
|
|
||||||
|
每个 Retained Segment 至少形成两个明确事实:
|
||||||
|
- structural placement capability
|
||||||
|
- current full-CPU placement gate result
|
||||||
|
|
||||||
|
placement-free Segment 不受纯 global placement gate 阻塞。
|
||||||
|
placement-bearing Segment 本 Step 继续沿用旧保守 gate,不承诺稳定后 reuse。
|
||||||
|
|
||||||
|
变量/字段命名不得把 `symbolFadeChange==0` 错称为严格 placement stable。
|
||||||
|
|
||||||
|
### 7.3 full CPU mixed execution
|
||||||
|
|
||||||
|
`segmentSupported` 改为使用对应 Segment placement gate。
|
||||||
|
|
||||||
|
`globalLiveFallback` 仅移除纯 aggregate placement 条件,其他真正 global 条件继续保留。
|
||||||
|
|
||||||
|
不得重写 `visitRenderSegmentExecution()`。
|
||||||
|
|
||||||
|
### 7.4 fallback reason
|
||||||
|
|
||||||
|
`static-placement` 必须按 Segment 自身 placement capability/gate 判定,不能从 aggregate reason 无差别传播。
|
||||||
|
|
||||||
|
### 7.5 diagnostics
|
||||||
|
|
||||||
|
每个 Segment 最少新增等价语义:
|
||||||
|
- `placementCapable`
|
||||||
|
- `placementGatePassed`
|
||||||
|
- `actualExecution = composite | live`
|
||||||
|
|
||||||
|
全局最少新增等价语义:
|
||||||
|
- `placementChanged`
|
||||||
|
- `symbolFadeChange`
|
||||||
|
- `placementTransitionActive`
|
||||||
|
- `crossSourceCollisions`
|
||||||
|
- `mainBufferExecutionMode = mixed | global-live`
|
||||||
|
- `globalLiveFallbackReason`
|
||||||
|
|
||||||
|
`actualExecution` 必须反映本帧 main-buffer 最终实际执行,不能根据 cache finalDecision 推导。
|
||||||
|
Diagnostics 查询必须无副作用、不得推进 revision。
|
||||||
|
|
||||||
|
### 7.6 RN 类型
|
||||||
|
|
||||||
|
只扩展现有 diagnostics 返回类型,不新增 method,不改变 `setRenderSegmentPlan()` 输入 contract。
|
||||||
|
Android/iOS Shared Core JSON 语义保持一致。
|
||||||
|
|
||||||
|
## 8. 构建要求
|
||||||
|
|
||||||
|
Codex 本阶段**不得执行任何 Android 编译/Gradle/APK/AAR build**,也不得启动长时间 CMake/Ninja/Bazel 构建。
|
||||||
|
|
||||||
|
Codex 完成代码后只需提供精确验证命令,由后续 RDC/用户按规则执行。其中 Android 编译由用户手工执行。
|
||||||
|
|
||||||
|
Codex 可执行的机械静态检查仅限:
|
||||||
|
- `git diff --check`
|
||||||
|
- 只读 grep/rg/git diff/status
|
||||||
|
- 不产生构建产物的格式/静态检查(若仓库已有且无需安装依赖)
|
||||||
|
|
||||||
|
不得安装或升级系统依赖来制造测试 PASS。
|
||||||
|
|
||||||
|
## 9. 测试要求
|
||||||
|
|
||||||
|
Codex 必须新增/补强测试源码,但本阶段不执行大型测试。
|
||||||
|
|
||||||
|
至少覆盖:
|
||||||
|
1. placement-free R0 + placement-bearing R1:R0 可 composite,R1 live;
|
||||||
|
2. 含 symbol 但当前 placementData 为空:仍判定 placementCapable=true;
|
||||||
|
3. 两个 placement-free Retained:持续 reuse/composite;
|
||||||
|
4. source-local fading/mutation 回归不误伤 R0;
|
||||||
|
5. placement-bearing 在 transition disabled / `symbolFadeChange=1` 等状态仍不被本 Step 放宽;
|
||||||
|
6. 真正 global gate 继续 global-live/fail-closed;
|
||||||
|
7. topology 变化释放旧 resource;
|
||||||
|
8. cache decision=reuse 但 global-live 时,diagnostics 的 actualExecution 必须为 live;
|
||||||
|
9. diagnostics JSON/TS 字段语义一致且查询无副作用。
|
||||||
|
|
||||||
|
Android Validation 场景应能在后续手工构建后明确输出 STEP-102 验收所需字段,不依赖 NavSea。
|
||||||
|
|
||||||
|
## 10. 验收标准
|
||||||
|
|
||||||
|
代码审计门槛:
|
||||||
|
- [ ] 新增结构性 placement-capable 查询
|
||||||
|
- [ ] 未改变 `retainedRangeUsesPlacement()` 旧语义
|
||||||
|
- [ ] full CPU per-segment placement gate 生效
|
||||||
|
- [ ] placement-bearing 继续保守
|
||||||
|
- [ ] `globalLiveFallback` 只移除纯 aggregate placement 条件
|
||||||
|
- [ ] 真正 global gate 保留
|
||||||
|
- [ ] mixed executor 未重写
|
||||||
|
- [ ] Fast Path 无语义修改
|
||||||
|
- [ ] per-segment fallback reason 正确
|
||||||
|
- [ ] diagnostics 有 actualExecution 与 main-buffer mode/fallback reason
|
||||||
|
- [ ] RN method surface 不变
|
||||||
|
- [ ] C-001 不变
|
||||||
|
- [ ] 测试源码覆盖设计矩阵
|
||||||
|
- [ ] `git diff --check` PASS
|
||||||
|
|
||||||
|
运行态 PASS 不在 Codex 阶段宣称。Android 手工编译与真机验证未完成前,STEP-102 状态只能写“代码完成 / 等待验证”或同等非 PASS 状态。
|
||||||
|
|
||||||
|
## 11. Git 要求
|
||||||
|
|
||||||
|
- 工作分支:`step102-placement-free-retained-locality`
|
||||||
|
- 禁止修改 particle/main 工作区
|
||||||
|
- 禁止 `git add -A`、`git add .`、`git reset --hard`、`git clean`
|
||||||
|
- 只 scoped staging 本任务文件
|
||||||
|
- 不提交无关 dirty/untracked 文件
|
||||||
|
- Codex 可在实现完成、静态检查通过后创建 scoped implementation commit,但**不得 merge 到 particle/main、不得 push release 分支/tag、不得发布制品**
|
||||||
|
- 是否 push/merge 由后续独立验收决定
|
||||||
|
|
||||||
|
## 12. 必做输出
|
||||||
|
|
||||||
|
Codex 完成后必须交付:
|
||||||
|
1. 生产代码修改;
|
||||||
|
2. C++ 测试源码;
|
||||||
|
3. Android Validation STEP-102 场景源码/日志字段;
|
||||||
|
4. `docs/50_Step记录/STEP-102_RenderSegment无Placement段局部复用_v1.md`;
|
||||||
|
5. `docs/报告/STEP-102_RenderSegment无Placement段局部复用_Codex完成报告_v1.md`;
|
||||||
|
6. 追加 `粒子开发记录.md`;
|
||||||
|
7. 精确列出后续验证命令;
|
||||||
|
8. 明确列出所有 NOT RUN / BLOCKED 项;
|
||||||
|
9. Git diff/status/commit(若创建)事实。
|
||||||
|
|
||||||
|
## 13. 执行报告完整文件名
|
||||||
|
|
||||||
|
固定:
|
||||||
|
`docs/报告/STEP-102_RenderSegment无Placement段局部复用_Codex完成报告_v1.md`
|
||||||
|
|
||||||
|
同主题后续报告只递增 `_v2/_v3`,不得另造近义文件名。
|
||||||
|
|
||||||
|
## 14. 执行报告固定内容
|
||||||
|
|
||||||
|
至少包含:
|
||||||
|
1. 执行结论:只能按实际写代码完成/等待验证、PARTIAL、BLOCKED 或 FAIL;未运行 Android 不得写运行态 PASS
|
||||||
|
2. Git 基线:branch/start HEAD/end HEAD/status
|
||||||
|
3. 实际修改文件
|
||||||
|
4. 新增文件
|
||||||
|
5. 实现内容逐条对照任务书
|
||||||
|
6. 与 v3 设计差异
|
||||||
|
7. Fast Path / mixed executor / C-001 未修改审计
|
||||||
|
8. diagnostics 新字段与语义
|
||||||
|
9. 测试源码覆盖矩阵
|
||||||
|
10. 实际执行的静态检查
|
||||||
|
11. 未执行的 build/test/Android/设备项目
|
||||||
|
12. 用户手工 Android 构建命令
|
||||||
|
13. 后续 RDC/真机验收命令与硬门槛
|
||||||
|
14. 已知风险与未完成事项
|
||||||
|
15. Git commit 信息(若有)
|
||||||
|
|
||||||
|
## 15. 阻塞与停止条件
|
||||||
|
|
||||||
|
遇到以下任一情况必须停止扩大修改并报告:
|
||||||
|
- 结构性 placement capability 不能仅靠既有 layer type 安全判定,需要新增更大公共抽象;
|
||||||
|
- 必须修改 Fast Path 才能完成本任务;
|
||||||
|
- 必须重写 mixed executor;
|
||||||
|
- 必须改变 collision/Placement 核心算法;
|
||||||
|
- 必须修改 C-001 / RN method surface;
|
||||||
|
- 必须修改 NavSea;
|
||||||
|
- 必须扩大到 renderer/backend 架构重构;
|
||||||
|
- 发现 global revision 仍有未预期的 placement 推进,需要新设计;
|
||||||
|
- 需要升级工具链/依赖;
|
||||||
|
- 需要删除或弱化既有测试/安全门槛。
|
||||||
|
|
||||||
|
若实现范围保持在 v3 冻结边界内,可直接完成代码并停在“等待手工构建/运行验证”。
|
||||||
39
docs/40_任务/UPSTREAM-001_MapLibre官方升级兼容性架构审计_Codex任务_v1.md
Normal file
39
docs/40_任务/UPSTREAM-001_MapLibre官方升级兼容性架构审计_Codex任务_v1.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# UPSTREAM-001 MapLibre 官方升级兼容性架构审计(只读代码,2026-10-09)
|
||||||
|
|
||||||
|
## 授权与模式
|
||||||
|
- 用户明确授权 Mac 使用 **GPT-6 Astra High** 做以未来跟随官方升级为目的的架构审计。
|
||||||
|
- Codex 执行器须显式指定 `gpt-6-astra`、`model_reasoning_effort=high`,实际配置从 Codex JSONL 开始事件核实;如不一致,报告 BLOCKED,不能伪称。
|
||||||
|
- 只读源代码。仅允许生成/修改本任务书和 `docs/报告/UPSTREAM-001_MapLibre官方升级兼容性架构审计_Codex完成报告_v1.md`;禁止改 C++、RN、Java、ObjC、构建脚本和依赖。
|
||||||
|
- 仓库为独立干净审计 worktree,基于正式远端 `origin/particle/main` 的 `.21`,不可操作其他已有脏工作树。无需构建 APK、AAR、Xcode,无需 Jest。不能伪造运行验收。
|
||||||
|
- 不修改 Integration COORD/RFC/ADR/Contract;如建议跨平台契约变化仅列出待协调项。
|
||||||
|
|
||||||
|
## 权威输入
|
||||||
|
- `/root/sourceserver/navsea-particle-integration` 为 Linux Integration;已由 RDC 同步到 `80667d6c343411d03a760425438e3d292f452118`。Mac 上可读取到的合同以已存在文件为准,不要猜测。
|
||||||
|
- 阅读本仓库 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/10_架构/20_粒子系统总体架构设计_v1.md`、`docs/00_项目总则/粒子系统项目规则与设计决策_v1.md`、`docs/30_设计/STEP-094_通用RenderSegment模型与Style分类Diagnostics设计_v1.md` 和相关 STEP-098/099/100/101/102/103 实现记录。
|
||||||
|
- 自定义现行发布:`630ec657687bad4200c059bbccbb950d9c6f7ce9` (`13.4.1-particle.21`)。
|
||||||
|
- 初始 upstream Core:`18545ca10d296704b2cba675b182ff2c8da24588` (见 `PARTICLE_BASELINE.md`);验证实际 merge-base。
|
||||||
|
- 官方 MapLibre Native Android `android-v13.6.1` peeled commit `c7506d6aff3800e946f89e1a51432fc22d1a6534`。
|
||||||
|
- 官方 MapLibre Native iOS `ios-v6.31.0` peeled commit `01791ad02c1bfe3b22c42a97619b972aba962f82`。
|
||||||
|
- 官方 MapLibre React Native `v11.5.0`;现有 Particle RN `11.3.6` peer/dev 锁及 Android AAR replacement、iOS SPM/Podspec 需独立审计。不能混淆 RN 11.5.0 与 Native 13.6.1。
|
||||||
|
|
||||||
|
## 必查问题
|
||||||
|
1. 原始目标“官方可持续升级、自研粒子和 RenderSegment 可重复移植”离目前真实代码有多远?用逐文件证据分类:**纯新增扩展**、**不可避免的官方接入 hook**、**可外提的官方修改**、**深耦合必须重构**,另列构建/平台桥接。不要用“62 个修改文件 = 62 个冲突”。
|
||||||
|
2. 在 `git diff` 与 `git merge-tree` dry-run 基础上,对 Android 13.6.1 和 iOS 6.31.0 分别量化冲突文件与交叉文件、可自动合并与疑似行为冲突。绝不可真实 merge/rebase 任何生产分支。优先核对 `renderer_impl.cpp`、`render_orchestrator.cpp`、`render_target.cpp`、`render_layer.hpp`、`render_source.cpp`、`paint_parameters.cpp`、RenderSegment/Retained cache、GPU backend、Style/Layer factory、JNI/Android/iOS 对接代码。
|
||||||
|
3. 以源代码里的函数/类名证明现有 Particle Core/Field/WeatherColorLayer/GPU 模块是否独立;列出真正只需 hook 的文件和未来易碎调用。用修复方案区分 thin hook、抽象接口、适配器、source-to-segment dependency、offscreen target/placement 等,不得建议一刀切“重写”。
|
||||||
|
4. RenderSegment 与 Particle Fast Path 的数据依赖、分层失效、Style 顺序、Opaque/Translucent、Placement、Context loss、资源所有权、Top/Bottom Retained、multi-segment 未来兼容约束。区分已实现功能和目前 fail-closed 限制。
|
||||||
|
5. 核查 `packages/maplibre-particle-rn` 的 `package.json`、`app.plugin.js`、`MapLibreParticleRN.podspec`、Native API 等;给出 RN 11.5.0 受官方 Android 13.6.1/iOS 6.31.0 依赖推动的单 SDK 依赖收敛风险、代码适配点、功能验证清单。
|
||||||
|
6. 准备升级顺序和可回滚 Gate:什么必须先重构、什么可以直接合并、哪些要不同平台分别验证,什么情况下必须停止并回 Integration 新 RFC/Contract。建议未来 upstream 版本跟踪工作流和“patch footprint budget”;明确对长期维护代价影响。
|
||||||
|
7. 逐项目给出 P0/P1/P2 表,包含路径、当前耦合证据、方案、风险、工作量相对估计、验收标准、Android/iOS影响;结论为 GO / CONDITIONAL GO / BLOCKED,不得把未编译当 PASS。
|
||||||
|
|
||||||
|
## 调查方法与客观限制
|
||||||
|
- 最少执行 `git merge-base`、`git diff --name-status`、`git merge-tree --write-tree --messages` 对两个官方 Native 目标作三方模拟;记录命令、exit code、冲突路径。merge-tree 会在 Git object db 创建对象但不改源码或分支,允许。
|
||||||
|
- 深入看源码定义和调用,不只扫描关键词;尽量提供可读且可复核的 `path:line` 和 commit hash。
|
||||||
|
- 官方 tag 比较若产生无共同祖先、shallow、无法下载等障碍,如实记录并提出可复现替代方案。
|
||||||
|
- 不强行运行常规编译、Android 实机、iOS 设备或 Jest。
|
||||||
|
- 对官方 RN 版本差异使用 release changelog、已有源码;未下载的 RN 文件不得假称已审查。
|
||||||
|
- 审计报告必须中文,章节包含:结论摘要、核对的版本与 SHA、方法和数据可信度、两条 Native 升级线冲突统计与具体文件、Particle 耦合清单、RenderSegment 耦合清单、RN 11.5.0 适配风险、改造优先级矩阵、阶段性迁移与验收、阻塞和未知、实际模型和文件修改清单。
|
||||||
|
|
||||||
|
## 必做输出和 Git 收尾
|
||||||
|
- 写入 `docs/报告/UPSTREAM-001_MapLibre官方升级兼容性架构审计_Codex完成报告_v1.md`;如不存在 `docs/报告/` 则创建。
|
||||||
|
- 工作树中仅允许审计任务 MD 和审计报告两份新增文档;完成 `git diff --check`、路径与报告完整性检查。
|
||||||
|
- **不要自行 commit 或 push**;执行器将先独立核对报告,再将仅这两份文档以隔离分支提交、确认快进后 push 到 `origin/particle/main`,最终回填 Git 状态。若未收到执行器的最终 Git 回执,报告必须写 `Git 收尾待 RDC 验收`,不能自称已推送。
|
||||||
156
docs/40_任务/粒子连续渲染静态底图缓存与动画快路径优化规划_v1.md
Normal file
156
docs/40_任务/粒子连续渲染静态底图缓存与动画快路径优化规划_v1.md
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
# MapLibreParticleLab 粒子连续渲染静态底图缓存与动画快路径优化规划 v1
|
||||||
|
|
||||||
|
## 1. 目标
|
||||||
|
STEP-086 已确认:Particle / WeatherColor 的 needsRepaint 会通过全局 repaint 让完整 MapLibre scene 连续出帧。React 不会每帧重新提交 PBF,但 Native Renderer 会重复进入 source/layer orchestration,并重新提交静态 PBF/fill/line/symbol draw。
|
||||||
|
|
||||||
|
本规划目标:
|
||||||
|
- 地图真实变化时仍走 Full Frame。
|
||||||
|
- Camera、Style、Tile 稳定且只有 WeatherColor/Particle 动画时,复用 Static Underlay。
|
||||||
|
- NavSea / React 不参与 Renderer cache 管理。
|
||||||
|
- 不改变 NavSea ↔ Particle C-001 公共边界。
|
||||||
|
|
||||||
|
## 2. 当前关键代码
|
||||||
|
必须以当前 origin/particle/main 实际代码为准,重点包括:
|
||||||
|
- src/mbgl/renderer/layers/render_particle_validation_layer.cpp
|
||||||
|
- src/mbgl/renderer/layers/render_weather_color_layer.cpp
|
||||||
|
- src/mbgl/renderer/render_orchestrator.cpp/.hpp
|
||||||
|
- src/mbgl/renderer/renderer.cpp
|
||||||
|
- src/mbgl/renderer/renderer_impl.cpp/.hpp
|
||||||
|
- src/mbgl/map/map_impl.cpp
|
||||||
|
- include/mbgl/renderer/render_target.hpp
|
||||||
|
- src/mbgl/renderer/render_target.cpp
|
||||||
|
- src/mbgl/renderer/layers/render_heatmap_layer.cpp
|
||||||
|
- src/mbgl/renderer/layers/render_hillshade_layer.cpp
|
||||||
|
|
||||||
|
## 3. 核心结构
|
||||||
|
不增加永久 public static/dynamic layer flag。采用 Renderer 内部 retained dynamic boundary:
|
||||||
|
- WeatherColor 是 boundary candidate。
|
||||||
|
- RasterParticle 是 boundary candidate。
|
||||||
|
- 同时存在时取最小 layerIndex。
|
||||||
|
- boundary 之前连续 LayerGroup 前缀作为 Static Underlay。
|
||||||
|
- boundary 及之后继续实时绘制。
|
||||||
|
- 没有 boundary 时完全保持原 Renderer。
|
||||||
|
|
||||||
|
Layer 顺序、parameters.currentLayer、depthRangeSize、opaque/translucent 原始 ordinal 必须保持,不允许对子集从 0 重新编号而改变透明/depth 语义。
|
||||||
|
|
||||||
|
## 4. Static Underlay Cache
|
||||||
|
建议 Renderer-owned helper:
|
||||||
|
- src/mbgl/renderer/retained_underlay_cache.hpp
|
||||||
|
- src/mbgl/renderer/retained_underlay_cache.cpp
|
||||||
|
|
||||||
|
职责:
|
||||||
|
- offscreen renderable / texture
|
||||||
|
- viewport size
|
||||||
|
- boundary
|
||||||
|
- valid/invalid
|
||||||
|
- invalidation reason
|
||||||
|
- build/reuse/fallback 计数
|
||||||
|
- compositor resources
|
||||||
|
|
||||||
|
不改变 Heatmap/Hillshade RenderTarget 现有行为;复用现有 gfx::OffscreenTexture / RenderTarget / Drawable / shader abstraction。
|
||||||
|
|
||||||
|
## 5. Texture Composite
|
||||||
|
使用独立语义的 RetainedUnderlayCompositeShader:
|
||||||
|
- viewport/fullscreen quad
|
||||||
|
- sample cached RGBA texture
|
||||||
|
- 正确 premultiplied alpha
|
||||||
|
- 不做 map transform / color ramp / Particle 业务
|
||||||
|
|
||||||
|
不要长期复用 LocationIndicatorTexturedShader。
|
||||||
|
|
||||||
|
## 6. STEP-088
|
||||||
|
先只消除静态地图重复 GPU draw,仍保留:
|
||||||
|
- Map::Impl::onUpdate
|
||||||
|
- UpdateParameters
|
||||||
|
- createRenderTree
|
||||||
|
- source update / TilePyramid update
|
||||||
|
- source/layer prepare
|
||||||
|
- updateLayers
|
||||||
|
- LayerGroup tweaker/upload
|
||||||
|
|
||||||
|
Render 阶段:
|
||||||
|
1. cache invalid:boundary 前 LayerGroups render 到 offscreen。
|
||||||
|
2. cache valid:跳过 boundary 前静态 draw。
|
||||||
|
3. main framebuffer clear。
|
||||||
|
4. composite cached underlay。
|
||||||
|
5. boundary 及之后 WeatherColor/Particle/动态 overlay 正常绘制。
|
||||||
|
6. present。
|
||||||
|
|
||||||
|
## 7. STEP-089
|
||||||
|
在 STEP-088 实机通过后再做 production invalidation:
|
||||||
|
- Camera center/zoom/bearing/pitch
|
||||||
|
- viewport/pixelRatio
|
||||||
|
- style/layer add/remove/reorder/change
|
||||||
|
- source/tile/bucket/drawable 变化
|
||||||
|
- sprite/image/glyph
|
||||||
|
- symbol placement/fade
|
||||||
|
- context/surface recreation
|
||||||
|
- 3D/terrain/custom/debug fallback
|
||||||
|
- OpenGL/Metal 一致生命周期
|
||||||
|
|
||||||
|
## 8. STEP-090 条件步骤
|
||||||
|
只有 STEP-089 后真实 NavSea profile 仍证明主要 CPU 落在:
|
||||||
|
- Map::Impl::onUpdate
|
||||||
|
- createRenderTree
|
||||||
|
- Vector/PBF source update
|
||||||
|
- TilePyramid update
|
||||||
|
- source/layer prepare
|
||||||
|
才进入 Animation-only fast path。
|
||||||
|
|
||||||
|
如果 STEP-089 已满足性能目标,不执行 STEP-090。
|
||||||
|
|
||||||
|
## 9. STEP-088 安全边界
|
||||||
|
PoC reuse 至少要求:
|
||||||
|
- 2D
|
||||||
|
- Camera fingerprint 连续一致
|
||||||
|
- viewport 不变
|
||||||
|
- style loaded
|
||||||
|
- boundary 不变
|
||||||
|
- context/resource 正常
|
||||||
|
|
||||||
|
以下至少 fallback full render:
|
||||||
|
- has3D
|
||||||
|
- Debug Overdraw
|
||||||
|
- cache texture/compositor 创建失败
|
||||||
|
- viewport resize
|
||||||
|
- boundary change
|
||||||
|
- context 不可用
|
||||||
|
- legacy RenderItem ordering 无法证明安全
|
||||||
|
- 其他不能证明 cache 正确的状态
|
||||||
|
|
||||||
|
原则:不确定就 full render,不用 stale cache 换性能。
|
||||||
|
|
||||||
|
## 10. 观测
|
||||||
|
复用 include/mbgl/particle/renderer_debug_counters.hpp,建议增加:
|
||||||
|
- FullRendererFrame
|
||||||
|
- RetainedUnderlayBuild
|
||||||
|
- RetainedUnderlayReuse
|
||||||
|
- RetainedUnderlayFallback
|
||||||
|
- RetainedUnderlayInvalidation
|
||||||
|
- RetainedUnderlayStaticGroupDraw
|
||||||
|
- RetainedDynamicGroupDraw
|
||||||
|
|
||||||
|
固定 Camera + Particle 约 30 FPS 时,理想:
|
||||||
|
- RetainedUnderlayBuild ≈ 1
|
||||||
|
- RetainedUnderlayReuse 持续增长
|
||||||
|
- ParticleSimulationAdvance 持续增长
|
||||||
|
- StaticGroupDraw 建 cache 后不随 Particle FPS 增长
|
||||||
|
- DynamicGroupDraw 持续增长
|
||||||
|
|
||||||
|
## 11. 不允许
|
||||||
|
- React RAF 驱动
|
||||||
|
- Host renderFastFrame/cacheMapNow/invalidateBasemap API
|
||||||
|
- NavSea 手工管理 renderer cache
|
||||||
|
- 修改 C-001
|
||||||
|
- 永久 public static/dynamic flag
|
||||||
|
- 改变 Style layer ordering
|
||||||
|
- STEP-088 就跳过 source/update/prepare
|
||||||
|
- Android/iOS 不同 renderer semantics
|
||||||
|
- stale cache
|
||||||
|
|
||||||
|
## 12. 执行顺序
|
||||||
|
1. STEP-088:PoC + observability
|
||||||
|
2. 用户 Android/NavSea 真机验证
|
||||||
|
3. STEP-089:production invalidation/fallback
|
||||||
|
4. 用户再次 profile
|
||||||
|
5. 只有 CPU orchestration 仍是瓶颈时才 STEP-090
|
||||||
11
docs/50_Step记录/README.md
Normal file
11
docs/50_Step记录/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Step 记录目录
|
||||||
|
|
||||||
|
保存各阶段已发生事实和阶段权威状态。历史 Step 不因后续版本升级而改写。
|
||||||
|
|
||||||
|
## 当前 Android / RN 操作入口
|
||||||
|
|
||||||
|
当前 QBIN V4 / Android `.3` 的构建、pbf 公网发布、Win11 Test Host、Metro/ADB 与人工验收流程:
|
||||||
|
|
||||||
|
`STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md`
|
||||||
|
|
||||||
|
`STEP-080 ... v1` 及 STEP-060~072 中的 MavenLocal、`.1/.2`、旧 Win11 路径/端口均保留为历史证据,不作为当前命令来源。
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user