docs: preserve particle task history and RDC workflow rules
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
tei
2026-09-23 11:57:44 +09:00
parent f8f84da6a1
commit e70b795098
6 changed files with 2099 additions and 0 deletions

View File

@@ -89,3 +89,66 @@ Codex 的核心角色是“实现者”,不作为其自身修改结果的唯
每一阶段必须有 `docs/50_Step记录/STEP-xxx_*.md`。完成报告固定写入 `docs/报告/STEP-xxx_<主题>_Codex完成报告_vN.md`(仅在任务要求 Codex 完成报告时)。 每一阶段必须有 `docs/50_Step记录/STEP-xxx_*.md`。完成报告固定写入 `docs/报告/STEP-xxx_<主题>_Codex完成报告_vN.md`(仅在任务要求 Codex 完成报告时)。
修改结束必须按当前构建规范验证、更新 Step、追加 `粒子开发记录.md`,并明确所有未完成/阻塞。Git 提交必须 scoped,禁止把既有无关 dirty/untracked 一并提交。 修改结束必须按当前构建规范验证、更新 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 修。

View 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。

View File

@@ -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 不提前进入该范围。

View File

@@ -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。

View 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. 最终响应
直接逐项报告:
- 已完成
- 未完成
- 无法验证
不要再输出实施计划。

View 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