Compare commits

...

92 Commits

Author SHA1 Message Date
tei
f47469d2ab test: integrate particle.10 online QBIN host
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 20:21:16 +09:00
tei
822663cd5d release: finalize unified particle.10
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 19:11:02 +09:00
tei
d5ce2006be fix: validate iOS release slice without pipefail false negative 2026-09-21 18:52:20 +09:00
tei
73bebcf827 release: prepare unified particle.10 2026-09-21 15:52:51 +09:00
tei
3f7ec99f85 fix: preserve canonical color semantics after QBIN decode 2026-09-21 15:52:22 +09:00
tei
a4cb727b16 docs: close STEP-087 particle.9 release
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 10:02:33 +09:00
tei
63047540cb release: finalize unified particle.9
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 10:01:40 +09:00
tei
0e8c87fe49 feat: add QBIN V3 decoder for particle.9 2026-09-21 08:03:39 +09:00
tei
b2eac3e0bf build: publish iOS device arm64 only 2026-09-21 08:03:39 +09:00
tei
5ed49c06f9 release: prepare unified particle.8 2026-09-21 08:03:39 +09:00
OpenAI Codex
c0bad3df6e docs: record STEP-086 continuous repaint investigation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 06:20:05 +08:00
tei
70f112ea27 fix: close STEP-085 V4 renderer efficiency
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-19 22:16:36 +09:00
tei
bc6b8dd84b fix: disable particle speed color by default
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-19 21:58:48 +09:00
tei
91d4859121 release: finalize unified particle.7
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-19 10:31:18 +09:00
tei
de935818d9 release: prepare unified particle.7 2026-09-19 07:52:16 +09:00
tei
8e807982b1 fix: decouple release from generated test host podfile 2026-09-19 07:52:00 +09:00
tei
fd5595ebbc build: add unified particle release pipeline 2026-09-19 07:50:31 +09:00
tei
9e20233288 docs: close STEP-083 ios RN validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 22:42:47 +09:00
tei
d8bb9f547b feat: validate ios RN particle metal path
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 22:21:00 +09:00
tei
bdd7f40f90 feat: extend ios particle field runtime parity facade
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 17:57:02 +09:00
tei
44af5e68b8 docs: close STEP-082 ios xcframework validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 17:05:45 +09:00
tei
136ed726cf fix: close ios xcframework packaging blockers 2026-09-18 17:03:05 +09:00
tei
dd9687f5ed docs: record STEP-082 ios arm64 build
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 09:16:14 +09:00
tei
1af5aab6f8 docs: record STEP-082 ios runtime distribution baseline
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 09:14:56 +09:00
tei
d94f2bffd6 feat: add ios particle field runtime facade 2026-09-18 09:13:50 +09:00
tei
3e03949d34 docs: record STEP-081 final validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 07:59:31 +09:00
tei
f1c0f92b2b fix: close STEP-081 iOS Metal particle rendering
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 07:58:51 +09:00
tei
8fd517bac2 fix: validate STEP-081 ios device build
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-17 14:51:22 +09:00
tei
79c0ff89f1 feat: bring up particle renderer on ios metal
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-17 09:27:35 +09:00
OpenAI Codex
f23f4143b9 feat: connect QBIN V4 renderer and release particle.5
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-15 14:40:17 +08:00
OpenAI Codex
3896c461fa docs: define Codex and RDC working roles
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-14 10:26:29 +08:00
OpenAI Codex
13a3d9e1e6 docs: align QBIN V4 particle.3 project baseline
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-14 10:03:37 +08:00
OpenAI Codex
de8d230b87 docs: record public AAR to RN Test Host validation flow
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-14 09:17:22 +08:00
OpenAI Codex
618ad59ab8 fix: align QBIN V4 Android particle.3 validation baseline 2026-09-14 09:16:44 +08:00
OpenAI Codex
8d49d943f3 chore: add Android RN build pipeline
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 21:31:28 +08:00
OpenAI Codex
d490578b97 docs: complete STEP-079 QBIN fixture handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 20:07:30 +08:00
OpenAI Codex
f1ea88b0ce feat: add V4 QBIN golden fixture test host 2026-09-13 20:05:59 +08:00
OpenAI Codex
a83ab9cc94 docs: record STEP-078 color binding revision
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 19:47:24 +08:00
OpenAI Codex
e02353820a fix: expose V4 velocity component color binding
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 19:46:53 +08:00
OpenAI Codex
dd1d701471 docs: finalize STEP-079 blocker record
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 17:44:01 +08:00
OpenAI Codex
afcc74066f docs: record STEP-079 V4 fixture API blocker
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 17:43:35 +08:00
OpenAI Codex
2ea8f03064 docs: record STEP-078 guard audit delivery
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 17:15:39 +08:00
OpenAI Codex
75a974e775 fix: close STEP-078 V4 bridge guard audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 17:14:50 +08:00
OpenAI Codex
73779b89bc feat: complete STEP-078 V4 bridge
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 16:10:49 +08:00
OpenAI Codex
4b5192e224 docs: add STEP-077 completion report
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 15:26:09 +08:00
OpenAI Codex
e0088a5f08 docs: complete STEP-077 V4 bridge investigation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 15:25:34 +08:00
OpenAI Codex
0759a06104 test: close STEP-076 V4 host API audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 15:02:17 +08:00
OpenAI Codex
ae03d8bfd2 feat: wire V4 QBIN host API into RasterArraySource
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 14:43:51 +08:00
OpenAI Codex
b148fb6a0c docs: record STEP-075 v3 push result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 14:22:25 +08:00
OpenAI Codex
2645bd725f fix: close STEP-075 binding selector identity audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 14:21:41 +08:00
OpenAI Codex
653e6f2f15 docs: record STEP-075 push result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 14:11:07 +08:00
OpenAI Codex
90d05f596a fix: close STEP-075 projection identity audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 14:10:32 +08:00
OpenAI Codex
3616daec30 docs: record STEP-075 push result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 13:58:57 +08:00
OpenAI Codex
c465b804ce feat: add V4 field binding projection identities
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 13:58:26 +08:00
OpenAI Codex
31783f25a8 docs: finalize STEP-074 git record
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 12:03:11 +08:00
OpenAI Codex
bba99f8acf docs: record STEP-074 delivery
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 12:02:18 +08:00
OpenAI Codex
3274c11a66 test: finalize STEP-074 QBIN2 padding boundary
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 12:01:54 +08:00
OpenAI Codex
f4a495ff38 test: close STEP-074 QBIN2 decoder coverage
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 11:51:45 +08:00
OpenAI Codex
56b8dab90f fix: close STEP-074 QBIN2 decoder audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 11:26:13 +08:00
OpenAI Codex
83a84452f2 feat: add QBIN2 V4 encoded field ingress
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 11:05:57 +08:00
OpenAI Codex
b8a629b7b6 fix: close STEP-073 validator runner audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 10:32:45 +08:00
OpenAI Codex
0b66a5cd30 docs: record STEP-073 v3 audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 10:19:02 +08:00
OpenAI Codex
4b8d200831 fix: close STEP-073 QBIN2 V4 validator audit 2026-09-13 10:18:33 +08:00
OpenAI Codex
1a453a6623 docs: record STEP-073 audit result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 10:02:13 +08:00
OpenAI Codex
d6a784a791 fix: correct STEP-073 QBIN2 V4 core contract
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 10:01:20 +08:00
OpenAI Codex
c3033eb5a4 docs: update STEP-073 completion report
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 09:45:07 +08:00
OpenAI Codex
3526e324ce fix: enforce validity descriptor class
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 09:44:43 +08:00
OpenAI Codex
6537996d05 docs: add STEP-073 completion report
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 09:43:34 +08:00
OpenAI Codex
0a190adff3 feat: add QBIN2 V4 generic field profile validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 09:42:41 +08:00
OpenAI Codex
a0aa367f30 fix: close V3-08 Android validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 18:10:33 +08:00
OpenAI Codex
1e84de3dd5 docs: record STEP-072 android validation preparation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 12:43:49 +08:00
OpenAI Codex
11cf58d344 chore: prepare V3-08 android validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 12:42:49 +08:00
OpenAI Codex
dd356a277d docs: record STEP-071 git handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 11:52:34 +08:00
OpenAI Codex
a4bf6b7aea test: close V3 Linux freeze
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 11:51:34 +08:00
OpenAI Codex
fcc533dc95 fix: finalize STEP-070 fixture validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 11:22:44 +08:00
OpenAI Codex
8588e6fcb8 feat: add V3 field fixtures and RN regression host
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 09:56:53 +08:00
OpenAI Codex
3b01275983 docs: finalize STEP-069 git handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 09:02:13 +08:00
OpenAI Codex
12827489f4 docs: record STEP-069 v2 field ingress
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 09:01:20 +08:00
OpenAI Codex
148bf80fe4 feat: add v2 multi-plane field ingress 2026-09-11 09:00:29 +08:00
OpenAI Codex
9e9ea07bc7 docs: record STEP-068 git handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 08:28:35 +08:00
OpenAI Codex
f3c33c6727 feat: add direction particle advection
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 08:27:28 +08:00
OpenAI Codex
89c72554c5 docs: close STEP-067 scalar GPU renderer
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 07:05:46 +08:00
OpenAI Codex
06fcbef9cc docs: record STEP-067 scalar GPU renderer
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 22:42:19 +08:00
OpenAI Codex
09ead7f201 renderer: add generic scalar field color path 2026-09-10 22:41:55 +08:00
OpenAI Codex
9dabb95e44 docs: close STEP-066 task handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 22:20:34 +08:00
OpenAI Codex
9a09278a28 docs: record STEP-066 push handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 22:16:54 +08:00
OpenAI Codex
b366a2d161 docs: record STEP-066 core contract
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 22:16:00 +08:00
OpenAI Codex
6d1f3e051c core: preserve fixture times in generic snapshots 2026-09-10 22:15:44 +08:00
OpenAI Codex
379c9ed0e4 core: add scalar vector field snapshots 2026-09-10 22:12:17 +08:00
OpenAI Codex
d23ae2079d docs: record STEP-065 git handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 21:54:26 +08:00
OpenAI Codex
44999edb25 docs: freeze V3 scalar vector field contract
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 21:53:56 +08:00
OpenAI Codex
2e3ab26275 docs: close STEP-064 RN release
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 09:40:37 +08:00
315 changed files with 24202 additions and 1094 deletions

1
.gitignore vendored
View File

@@ -13,6 +13,7 @@
/platform/android/build
/platform/android/MapLibreAndroid/src/main/assets/sdk_versions/com.mapbox.mapboxsdk
/platform/android/MapLibreAndroid/android-binaries-*.zip
/artifacts/maplibre-particle/
*.code-workspace
local.properties

View File

@@ -1,11 +1,91 @@
# MapLibreParticleLab — Codex 总规则
# MapLibreParticleLab — Codex / ChatGPT RDC 总规则
任何修改前必须阅读 `PARTICLE_BASELINE.md`、`docs/00_项目规范/`(包括官方基线来源)、当前 Step 和当前任务。
本文件是 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;第一阶段只用离线 fixture;不得复制 Mapbox 新源码或 Windy APK 内代码。
固定边界:正式实现不使用 React Native/WebView/外部 Overlay;粒子模块高度隔离;官方原文件只做薄接入;Android/iOS 共用 C++ Core;WeatherColorLayer 与 RasterParticleLayer 分离;Wind=GLOBAL;Wave/Tide/Current=OCEAN_ONLY 且共用 Land/Ocean Mask;不得复制 Mapbox 新源码或 Windy APK 内代码。
每一阶段必须有 `docs/50_Step记录/STEP-xxx_*.md`。完成报告固定写入 `docs/报告/STEP-xxx_<主题>_Codex完成报告_vN.md`。
Host/NavSea 负责 provider、HTTP、manifest、下载、缓存和业务调度;MapLibre Native 从宿主提交的 canonical/encoded field 开始负责校验、解码、Runtime Field Window 与 GPU 渲染。
修改结束必须按构建规范验证、更新 Step、追加 `粒子开发记录.md`、生成中文完成报告,并明确所有未完成/阻塞。
## 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 一并提交。

View File

@@ -213,12 +213,28 @@ list(APPEND SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/offline_fixture_particle_adapter.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/offline_fixture_particle_adapter.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_data_adapter.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_runtime.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_binding.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_projection_materialization.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_profile_validator.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_profile_contract_json.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/ingress/qbin_v2_decoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/ingress/qbin_v3_decoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/local_particle_field_feed.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/shader_program_base.cpp
)
list(APPEND INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_data_adapter.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_profile_contract.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_profile_validator.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_profile_contract_json.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_runtime.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_binding.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/renderer_debug_counters.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/ingress/qbin_v2_decoder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/ingress/qbin_v3_decoder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/ingress/encoded_field_transport.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/local_particle_field_feed.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/actor/actor_ref.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/actor/actor.hpp

View File

@@ -64,7 +64,6 @@ MLN_GENERATED_STYLE_SOURCE = [
"src/mbgl/style/layers/color_relief_layer.cpp",
"src/mbgl/style/layers/background_layer_properties.cpp",
"src/mbgl/style/layers/background_layer.cpp",
"src/mbgl/style/layers/particle_validation_layer.cpp",
"src/mbgl/style/layers/location_indicator_layer_properties.cpp",
"src/mbgl/style/layers/location_indicator_layer.cpp",
"src/mbgl/style/light.cpp",
@@ -100,6 +99,7 @@ MLN_GENERATED_OPENGL_SHADER_HEADERS = [
"include/mbgl/shaders/gl/fill_extrusion_pattern_instanced.hpp",
"include/mbgl/shaders/gl/heatmap.hpp",
"include/mbgl/shaders/gl/heatmap_texture.hpp",
"include/mbgl/shaders/gl/particle_gpu_validation.hpp",
"include/mbgl/shaders/gl/hillshade_prepare.hpp",
"include/mbgl/shaders/gl/hillshade.hpp",
"include/mbgl/shaders/gl/color_relief.hpp",
@@ -164,6 +164,8 @@ MLN_CORE_SOURCE = [
"src/mbgl/gfx/vertex_vector.hpp",
"src/mbgl/layermanager/background_layer_factory.cpp",
"src/mbgl/layermanager/particle_validation_layer_factory.cpp",
"src/mbgl/layermanager/raster_particle_layer_factory.cpp",
"src/mbgl/layermanager/weather_color_layer_factory.cpp",
"src/mbgl/layermanager/circle_layer_factory.cpp",
"src/mbgl/layermanager/custom_layer_factory.cpp",
"src/mbgl/layermanager/fill_extrusion_layer_factory.cpp",
@@ -203,6 +205,17 @@ MLN_CORE_SOURCE = [
"src/mbgl/map/zoom_history.hpp",
"src/mbgl/math/log2.cpp",
"src/mbgl/platform/settings.cpp",
"src/mbgl/particle/field/offline_fixture_particle_adapter.cpp",
"src/mbgl/particle/field/offline_fixture_particle_adapter.hpp",
"src/mbgl/particle/field/field_data_adapter.cpp",
"src/mbgl/particle/field/field_runtime.cpp",
"src/mbgl/particle/field/field_binding.cpp",
"src/mbgl/particle/field/field_projection_materialization.cpp",
"src/mbgl/particle/field/field_profile_validator.cpp",
"src/mbgl/particle/field/field_profile_contract_json.cpp",
"src/mbgl/particle/ingress/qbin_v2_decoder.cpp",
"src/mbgl/particle/ingress/qbin_v3_decoder.cpp",
"src/mbgl/particle/field/local_particle_field_feed.cpp",
"src/mbgl/renderer/backend_scope.cpp",
"src/mbgl/renderer/bucket.hpp",
"src/mbgl/renderer/bucket_parameters.cpp",
@@ -237,6 +250,9 @@ MLN_CORE_SOURCE = [
"src/mbgl/renderer/layers/render_background_layer.hpp",
"src/mbgl/renderer/layers/render_particle_validation_layer.cpp",
"src/mbgl/renderer/layers/render_particle_validation_layer.hpp",
"src/mbgl/renderer/layers/render_raster_particle_layer.hpp",
"src/mbgl/renderer/layers/render_weather_color_layer.cpp",
"src/mbgl/renderer/layers/render_weather_color_layer.hpp",
"src/mbgl/renderer/layers/render_circle_layer.cpp",
"src/mbgl/renderer/layers/render_circle_layer.hpp",
"src/mbgl/renderer/layers/render_custom_layer.cpp",
@@ -297,6 +313,7 @@ MLN_CORE_SOURCE = [
"src/mbgl/renderer/sources/render_raster_dem_source.hpp",
"src/mbgl/renderer/sources/render_raster_source.cpp",
"src/mbgl/renderer/sources/render_raster_source.hpp",
"src/mbgl/renderer/sources/render_raster_array_source.hpp",
"src/mbgl/renderer/sources/render_tile_source.cpp",
"src/mbgl/renderer/sources/render_tile_source.hpp",
"src/mbgl/renderer/sources/render_vector_source.cpp",
@@ -410,7 +427,13 @@ MLN_CORE_SOURCE = [
"src/mbgl/style/layer_observer.hpp",
"src/mbgl/style/layers/background_layer_impl.cpp",
"src/mbgl/style/layers/background_layer_impl.hpp",
"src/mbgl/style/layers/particle_validation_layer.cpp",
"src/mbgl/style/layers/particle_validation_layer_impl.hpp",
"src/mbgl/style/layers/particle_render_layer_properties.hpp",
"src/mbgl/style/layers/raster_particle_layer.cpp",
"src/mbgl/style/layers/raster_particle_layer_impl.hpp",
"src/mbgl/style/layers/weather_color_layer.cpp",
"src/mbgl/style/layers/weather_color_layer_impl.hpp",
"src/mbgl/style/layers/circle_layer_impl.cpp",
"src/mbgl/style/layers/circle_layer_impl.hpp",
"src/mbgl/style/layers/custom_layer.cpp",
@@ -462,6 +485,8 @@ MLN_CORE_SOURCE = [
"src/mbgl/style/sources/raster_dem_source.cpp",
"src/mbgl/style/sources/tile_source.cpp",
"src/mbgl/style/sources/raster_source.cpp",
"src/mbgl/style/sources/raster_array_source.cpp",
"src/mbgl/style/sources/raster_array_source_impl.hpp",
"src/mbgl/style/sources/tile_source_impl.cpp",
"src/mbgl/style/sources/tile_source_impl.hpp",
"src/mbgl/style/sources/vector_source.cpp",
@@ -628,6 +653,18 @@ MLN_CORE_SOURCE = [
})
MLN_CORE_HEADERS = [
"include/mbgl/particle/field/field_data_adapter.hpp",
"include/mbgl/particle/field/field_profile_contract.hpp",
"include/mbgl/particle/field/field_profile_validator.hpp",
"include/mbgl/particle/field/field_profile_contract_json.hpp",
"include/mbgl/particle/field/field_projection_materialization.hpp",
"include/mbgl/particle/field/field_runtime.hpp",
"include/mbgl/particle/field/field_binding.hpp",
"include/mbgl/particle/renderer_debug_counters.hpp",
"include/mbgl/particle/ingress/qbin_v2_decoder.hpp",
"include/mbgl/particle/ingress/qbin_v3_decoder.hpp",
"include/mbgl/particle/ingress/encoded_field_transport.hpp",
"include/mbgl/particle/field/local_particle_field_feed.hpp",
"include/mbgl/gfx/command_encoder.hpp",
"include/mbgl/gfx/debug_group.hpp",
"include/mbgl/gfx/context.hpp",
@@ -666,6 +703,8 @@ MLN_CORE_HEADERS = [
"include/mbgl/i18n/number_format.hpp",
"include/mbgl/layermanager/background_layer_factory.hpp",
"include/mbgl/layermanager/particle_validation_layer_factory.hpp",
"include/mbgl/layermanager/raster_particle_layer_factory.hpp",
"include/mbgl/layermanager/weather_color_layer_factory.hpp",
"include/mbgl/layermanager/circle_layer_factory.hpp",
"include/mbgl/layermanager/custom_layer_factory.hpp",
"include/mbgl/layermanager/fill_extrusion_layer_factory.hpp",
@@ -680,6 +719,10 @@ MLN_CORE_HEADERS = [
"include/mbgl/layermanager/raster_layer_factory.hpp",
"include/mbgl/layermanager/symbol_layer_factory.hpp",
"include/mbgl/style/layers/particle_validation_layer.hpp",
"include/mbgl/style/layers/raster_particle_layer.hpp",
"include/mbgl/style/layers/weather_color_layer.hpp",
"include/mbgl/style/layers/weather_color_stop.hpp",
"include/mbgl/style/sources/raster_array_source.hpp",
"include/mbgl/map/bound_options.hpp",
"include/mbgl/map/camera.hpp",
"include/mbgl/map/change.hpp",
@@ -1013,6 +1056,7 @@ MLN_DRAWABLES_HEADERS = [
"include/mbgl/shaders/fill_extrusion_layer_ubo.hpp",
"include/mbgl/shaders/heatmap_layer_ubo.hpp",
"include/mbgl/shaders/heatmap_texture_layer_ubo.hpp",
"include/mbgl/shaders/particle_gpu_validation_ubo.hpp",
"include/mbgl/shaders/hillshade_layer_ubo.hpp",
"include/mbgl/shaders/color_relief_layer_ubo.hpp",
"include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp",
@@ -1094,6 +1138,7 @@ MLN_DRAWABLES_MTL_SOURCE = [
"src/mbgl/shaders/mtl/color_relief.cpp",
"src/mbgl/shaders/mtl/line.cpp",
"src/mbgl/shaders/mtl/location_indicator.cpp",
"src/mbgl/shaders/mtl/particle_gpu_validation.cpp",
"src/mbgl/shaders/mtl/raster.cpp",
"src/mbgl/shaders/mtl/symbol.cpp",
"src/mbgl/shaders/mtl/widevector.cpp",
@@ -1137,6 +1182,7 @@ MLN_DRAWABLES_MTL_HEADERS = [
"include/mbgl/shaders/mtl/color_relief.hpp",
"include/mbgl/shaders/mtl/line.hpp",
"include/mbgl/shaders/mtl/location_indicator.hpp",
"include/mbgl/shaders/mtl/particle_gpu_validation.hpp",
"include/mbgl/shaders/mtl/raster.hpp",
"include/mbgl/shaders/mtl/shader_group.hpp",
"include/mbgl/shaders/mtl/shader_program.hpp",

View File

@@ -47,6 +47,7 @@ list(APPEND
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/color_relief.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/line.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/location_indicator.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/particle_gpu_validation.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/raster.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/shader_group.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/shader_program.hpp
@@ -90,6 +91,7 @@ list(APPEND
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/color_relief.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/line.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/location_indicator.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/particle_gpu_validation.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/raster.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/symbol.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/widevector.cpp

View File

@@ -7,7 +7,9 @@
1. `粒子系统项目规则与设计决策_v1.md`
2. `开发环境与系统构建规范_v1.md`
3. `Step指令书与执行报告规范_v1.md`
4. `当前基线与开发路线_v1.md`
4. `当前基线与开发路线_v2.md`(当前)
`当前基线与开发路线_v1.md` 保留为 2026-08-29 历史冻结,不再作为当前构建/发布依据。
## 阅读顺序
`AGENTS.md` → 本目录 README → 四份当前有效总则 → 当前 Step 指令书 → 上一 Step 执行报告 → 其他阶段文档。

View File

@@ -1,5 +1,8 @@
# 当前基线与开发路线 v1
> **历史冻结:2026-08-29。** 当前基线已由 `当前基线与开发路线_v2.md` 取代;本文仅保留当时事实,不用于当前 Android/RN 构建。
## 1. 冻结日期
2026-08-29

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

View File

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

View 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 和隔离测试消除。

View 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 优化实现。

View File

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

View File

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

View 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 收尾中继续重构。

View File

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

View 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 采样提供证据。

View File

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

View File

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

View File

@@ -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 失败不得声称完成。
完成后直接按必做清单报告“已完成 / 未完成 / 无法验证”,不要输出实施计划。

View File

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

View File

@@ -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 记录和完成报告。

View File

@@ -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 失败不得声称完成。

View File

@@ -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. 是否修改了任何生产代码(必须为“否”)。

View File

@@ -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。
完成后只报告修改内容和是否修改生产代码。

View File

@@ -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 验证;完成文件修改后停止。

View File

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

View File

@@ -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 未执行”。

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

View 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 上做运行验收。

View 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”当成完成方案。
除此之外,应完成实现,不要因为普通工程细节反复向用户确认。

View File

@@ -1,3 +1,11 @@
# Step 记录目录
保存阶段权威状态,初始化脚本生成 STEP-000。
保存各阶段已发生事实和阶段权威状态。历史 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 路径/端口均保留为历史证据,不作为当前命令来源。

View File

@@ -1,6 +1,6 @@
# STEP-064 RN 纯 Field Renderer 接口 V2
状态:完成(Consumer 验证通过,待正式发布冻结)
状态:冻结(13.4.1-particle.2 正式发布完成)
## 目标
@@ -40,3 +40,12 @@
- 修正后 `FieldRuntime attached; style ready`;T0–T3 依次返回 Pending / InitialWindowReady / NextSnapshot / NextSnapshot,Runtime READY。
- `Field OFF + Particle ON` 与 `Field ON + Particle OFF` 均实测通过,并恢复两者 ON。
- hard gate 未见 WEATHER_OBJECTS_NOT_READY、FATAL、SIGSEGV、JNI DETECTED ERROR、NoSuchMethodError、UnsatisfiedLinkError 或非法 layer property。
## 正式发布关闭
- Release source commit:`e43b58707cfd0f87965424477ed3f95625b3f724`。
- Release tag:`maplibre-particle/rn/13.4.1-particle.2`,已 push。
- 正式 HTTPS:`https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.2.tgz`。
- 正式 tgz:10,660 bytes;SHA256 `ace9e0401824839d18a93cc4201c1f5d4cccbff0636570cfde82fd80b60779b5`;公网 HTTP 200 且下载字节完全一致。
- Win11 Consumer 已从正式 HTTPS URL 安装 `.2`,TypeScript 通过,MLRN 11.3.6 单份 dedupe。
- STEP-064 正式冻结;后续修改必须升 `13.4.1-particle.3`,不得覆盖 `.2` tgz 或移动 release tag。

View File

@@ -0,0 +1,25 @@
# STEP-065 MapLibreParticleLab V3 通用 Scalar / Vector Field 架构冻结
状态:冻结
## 目标与基线
在 `2e3ab262` 对当前真实实现做只读调查,冻结 V3.0 generic Scalar/Vector Contract 和迁移路线;不实现。开始与结束均为 `particle/main`,开始时 `HEAD == origin/particle/main == 2e3ab262`。
## 必做项结果
- [x] 读取全部规范、STEP-064 基线,复核 branch/HEAD/origin/dirty worktree。
- [x] 调查 descriptor/snapshot/adapter、WIND/GLOBAL、Timeline/MIX/window reuse、WeatherColor `speedMps`、Particle velocity m/s、RN→Android→JNI→Core。
- [x] 只读核对 WeatherV2 Wind/Tide/Rain/Wave NSFT 语义。
- [x] 冻结 0/1 scalar + 0/1 vector + shared validity、`FieldValueSource`、`VectorSemantics`、独立 plane file refs。
- [x] 冻结 `maplibre-particle-field/v2`、v1 thin normalize compatibility、分批路线和单次 Android 综合验收。
- [x] 生成调查、设计、Step、报告并追加开发记录。
- [x] 确认未修改生产代码、WeatherV2、NavSea、STEP-062 或历史无关 untracked 文件。
## 允许/禁止与验证
仅新增/更新任务指定文档;不改 C++、shader、JNI、Android、RN API 或 fixture,不生成 AAR/tgz。未执行 Android Gradle、APK、ADB、Jest,原因是任务明确禁止。执行只读代码/文本审计与 `git diff --check`/精确 staged 文件审计;不宣称构建或设备 PASS。
## 当前结果与下一步
详见调查及设计文档。下一任务建议:`STEP-066 V3-02 Core Contract + Scalar Snapshot/Adapter`,只实现 v2/v1 normalize 与 Core fixture/验证,先不进入 GPU 或 Android。未解决风险:v2 具体 Kotlin/JNI ABI 形态、plane 总内存预算、direction 视觉 scale 默认值须在实施前用目标设备约束量化。

View File

@@ -0,0 +1,35 @@
# STEP-066 MapLibreParticleLab V3-02 Core Contract 与 Scalar Snapshot Adapter
状态:完成
## 目标与基线
在 `particle/main` 的 STEP-065 冻结设计上,仅实现 Core Contract、Snapshot、Adapter 与最小 Core 测试。开工时 `HEAD == origin/particle/main == d23ae2079dcfef3ce48517f734fe487207e28d23`;工作区已有 STEP-062 两处用户修改及历史 untracked/build 内容,均未纳入本 Step。
## 完成内容
- [x] 阅读规范、STEP-065、任务书与当前实际源码。
- [x] 定义通用 `ParticleFieldPayloadBundle`、v2 plane descriptor 与 `VectorSemantics::Velocity|Direction`。
- [x] Snapshot 升级为 optional scalar、optional interleaved U/V vector、shared `valid`;移除 Core 对 `WIND/GLOBAL` 的 capability hardcode。
- [x] 实现 v2 vector-only、scalar-only、scalar+vector;neither、plane presence/长度/encoding、validity、semantics 与 finite/noData 错误 fail-closed。
- [x] 保留 v1 单 payload parser;在 ingress 派生 shared validity,并归一为 vector-only + `VELOCITY`。
- [x] 收紧 T0/T1 compatibility 至 grid/bounds、scalar/vector presence 与 vector semantics;不改变 Source-owned Timeline、T0/T1/MIX、pause 或 rollover ownership。
- [x] `sameFieldGpuContent()` 逐值比较 decoded scalar/vector/validity plane,不依赖 fieldId/dataVersion/legacy hash 猜测内容。
- [x] 最小 renderer 适配:速度纹理读取改为 generic vector 成员;scalar-only 输入清空/不创建 drawables,V3-03 前 fail-closed;未改 shader、纹理编码、GPU 计算或粒子运动语义。
- [x] `source.test.cpp` 覆盖 v1 normalize、三种 v2 合法组合、neither、三种长度错误、missing semantics、DIRECTION without vector、NaN/Inf、invalid finite placeholder、compatibility 与三 plane identity。
## 验证
- `ninja -C build-linux-opengl -j3 mbgl-test`:PASS。
- `ninja -C build-linux-opengl -j3 test/CMakeFiles/mbgl-test.dir/style/source.test.cpp.o`:PASS。
- `ninja -C build-linux-opengl -j3 mbgl-test-runner`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter=Source.ParticleFieldV3*`:PASS(退出码 0)。
- `git diff --check`:PASS。
全部 build/test 输出经 `build-output-throttle` 限流。未执行 Android Gradle、AAR/APK、ADB 或 Jest。
## 范围确认、阻塞与下一步
没有 Android/JNI/RN、WeatherV2、NavSea、shader 或 Test Host JS/TS 修改;STEP-062 用户修改和历史 untracked 内容未暂存。Scalar GPU texture、generic `FieldValueSource`、DIRECTION advection、v2 JNI/RN ingress 均未实现,按冻结路线留给 V3-03/V3-04/V3-05;scalar snapshot 在现有 renderer 为 unavailable/fail-closed。
下一步:STEP-067(V3-03 Scalar GPU Texture + Generic Field Renderer)。

View File

@@ -0,0 +1,37 @@
# STEP-067 MapLibreParticleLab V3-03 Scalar GPU 与 Generic Field Renderer
状态:完成(CLOSED / PASS)
## 目标与基线
在 `particle/main` 的 `9dabb95e4425ffbcb863c520923f5d4b537a3feb` 上实现 V3 scalar GPU field bundle 的 WeatherColor 消费,保持 vector magnitude 的 Wind 色场和既有粒子 Velocity 语义。
## 完成内容
- [x] `WeatherColorLayer` 新增显式 `FieldValueSource::Scalar|VectorMagnitude`,默认 `VectorMagnitude`。
- [x] WeatherColor 单一 GPU renderer 根据明确 source 编码 scalar 或 U/V,shared validity 始终写入 alpha;T0/T1 均无效或缺少所选 channel 时清理 drawable 并 fail-closed。
- [x] shader 对 scalar 或 vector magnitude 分别先作 T0/T1 时间插值,再进入同一 generic color ramp;没有 scalar 伪装为 U/V。
- [x] `WeatherColorStop.speedMps` 收敛为 generic `value`,校验为 finite 且严格递增;日志、UBO 注释和 shader 去除固定 m/s 语义。
- [x] Particle renderer 明确仅接受 vector present 且 `VectorSemantics::Velocity`;scalar-only 与 `Direction` 立即清理其 GPU resource 并 fail-closed。
- [x] WeatherColor rollover 仍以 decoded bundle content identity 复用 OLD_T1→NEW_T0;切换 value source 时不误复用不同 GPU 编码,只有新内容上传。
## 允许修改
WeatherColor style/render、Particle renderer guard、实际 GLSL/生成的 OpenGL shader header、对应 UBO 注释和 C++ layer test。
## 禁止修改确认
未修改 Android/JNI/Java/Kotlin、RN package/API/version、Test Host、WeatherV2、NavSea、Timeline/Playback/Runtime Window;未实现 DIRECTION advection、Wave physics、v2 JNI/RN ingress;未运行 Gradle、AAR、APK、ADB 或 Jest。
## 验证
- `node shaders/generate_shader_code.mjs`:PASS(经 build-output-throttle)。
- 精确受影响对象:`render_weather_color_layer.cpp.o`、`render_particle_validation_layer.cpp.o`、`style_layer.test.cpp.o`:PASS(经 build-output-throttle;ccache 临时目录使用 `/tmp/maplibre-particle-ccache-tmp`)。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldV3*:Layer.WeatherColorUsesIndependentFixedOpacity'`:PASS,退出码 0。
- `git diff --check`:PASS。
- 用户于当前 `HEAD`(`06fcbef9ccb820e595f06acbd356950de7e3a73f`)手工执行 `ninja -C build-linux-opengl -j3 mbgl-test mbgl-test-runner`,输出为 `ninja: no work to do.`。确认 `mbgl-test` 与 `mbgl-test-runner` 两个完整目标及其依赖均已是最新,没有待处理的构建或重链接工作。
- 上述结果仅确认目标构建/重链接状态;本次没有重新执行全量测试用例,不能将其表述为全量测试用例重跑 PASS。
## 阻塞与下一步
无功能阻塞。本 Step 的 Linux 全量目标重链收尾已确认;Android 验收按 V3-08 的单独周期执行,且不属于本 Step 的关闭条件。

View File

@@ -0,0 +1,61 @@
# STEP-068 MapLibreParticleLab V3-04 DIRECTION 粒子推进
状态:完成(CLOSED / PASS)
## 目标与基线
基线为 `particle/main` / `89c72554c5fb5cd469bdd8ba06ff4b03a1a67ff9`,与 `origin/particle/main` 一致。实现通用 `DIRECTION` 向量推进及独立 renderer `advectionScale`,不引入任何 Wave、WeatherV2 或 provider 分支。
## 已完成
- [x] RasterParticleLayer 新增 `raster-particle-advection-scale` 与 C++ `getAdvectionScale()/setAdvectionScale()`;默认 `1.0`,非 finite 或非正值 fail-closed。
- [x] State fragment 与正式 line vertex 共享同一语义:分别解码 T0/T1、分量 MIX、检查有限性;DIRECTION 对混合结果归一化并乘 scale,长度 `<= 1e-5` 无效;VELOCITY 保留原始分量 MIX 与 magnitude。
- [x] UBO 新增明确 `advection` vec4(Direction flag、scale、epsilon),C++ struct/static_assert、两份 GLSL 和 generated OpenGL header 同步。
- [x] renderer 接受 vector VELOCITY 与 DIRECTION;scalar-only、缺 semantics 或 T0/T1 semantics 不同仍清理并 fail-closed。
- [x] `advectionScale` 仅更新 renderer uniform,不上传 field texture、不重置 Timeline/State/Trail。
- [x] C++ oracle 测试覆盖 east/north/diagonal、east/west MIX 归零、scale 改变 magnitude,以及 VELOCITY 不归一化且不消费 scale;Layer 测试覆盖默认、set/get 与非法值。
## 禁止范围确认
未修改 Android、JNI、Java/Kotlin、RN、WeatherV2、NavSea、Timeline/Playback/Runtime Window、STEP-062 用户修改或历史无关文件;未执行 Gradle、AAR/APK/ADB 或 Jest。
## 验证
- [x] `node shaders/generate_shader_code.mjs`:PASS(输出节流)。
- [x] 第一轮受影响对象 `render_particle_validation_layer.cpp.o`、`raster_particle_layer.cpp.o`、`style_layer.test.cpp.o`:PASS(输出节流;发生在新增 CPU oracle 后的最终重编译前)。
- [x] 既有过滤测试 `Layer.RasterParticle*:Source.ParticleFieldV3*`:PASS(输出节流;发生在新增 CPU oracle 后的最终重链前)。
- [x] 最终受影响精确对象:`render_particle_validation_layer.cpp.o`、`render_weather_color_layer.cpp.o`、`raster_particle_layer.cpp.o`、`source.test.cpp.o`、`style_layer.test.cpp.o`:PASS。
- [x] `ninja -C build-linux-opengl -j3 mbgl-test mbgl-test-runner`:PASS,完整目标重编译/重链完成(本轮约 899.5 秒)。
- [x] STEP-068 相关过滤测试 `Source.ParticleFieldV3*:Layer.RasterParticle*`:8/8 PASS;其中包含 DIRECTION east/north/diagonal、MIX northeast、opposite fail-closed、VELOCITY 不归一化及 advectionScale 属性。
- [x] `git diff --check`:PASS。
- [x] shader generator:此前本 Step 已 PASS;WeatherColor UBO 最小修复未修改 shader/generator 输入,无需再次生成。
## 2026-09-11 全量 Linux 重编译发现的 UBO 初始化遗漏
全量 Linux 重编译在 `src/mbgl/renderer/layers/render_weather_color_layer.cpp:108` 以
`-Wmissing-field-initializers` 发现 `ParticleGpuValidationUBO::advection` 新增后
WeatherColor 的 designated aggregate initializer 未同步。已仅在该 color-only pass
显式补入安全中性值 `{Direction flag=0, scale=1, epsilon=1e-5, reserved=0}`;WeatherColor
不执行 advection,色场取样和颜色路径不变。
搜索确认 C++ 中仅有两个 `ParticleGpuValidationUBO` 聚合初始化器:Particle renderer
已初始化 `advection`,WeatherColor 为本次唯一遗漏。此项修复后的 shader generator
不需要重跑(未修改 shader/generator 输入)。修复后的 WeatherColor 精确对象编译与
`git diff --check` 均已 PASS;编译日志按输出节流保存。随后外部终端完成完整
`mbgl-test` / `mbgl-test-runner` 重编译与重链并 PASS。
## 独立既有测试问题与下一步
全量目标构建 PASS 后,额外扩大过滤范围时发现当前 `HEAD` 已存在的 `Layer.WeatherColorUsesIndependentFixedOpacity` 断言错误:测试先成功调用 `setColorStops({{-2,...},{3,...}})` 改变状态,最后却仍断言等于更早的 `{0,10,20}`。`git show HEAD:test/style/style_layer.test.cpp` 已确认该错误在 STEP-068 开工前即存在,且本 Step 对该测试的 diff 只有新增 RasterParticle 测试。为保持任务隔离,本 Step 不修改该既有 WeatherColor 测试,也不把它作为 DIRECTION 关闭门槛。
STEP-068 相关实现、精确对象、完整 Linux 目标重链、8 个相关过滤测试与 `git diff --check` 均已通过,因此本 Step 正式 `CLOSED / PASS`。Android Gradle/AAR/APK/ADB 与 Jest 仍按任务要求未执行。下一步进入 V3-05:Android/JNI/RN generic v2 multi-plane ingress。
## Git 收尾
- 实现提交:`f3c33c67275b24f61b80924abbb9d10d68b2cb7c`
- commit message:`feat: add direction particle advection`
- push:成功
- 远端分支:`origin/particle/main`
- 实现提交 push 后:`HEAD == origin/particle/main == f3c33c67275b24f61b80924abbb9d10d68b2cb7c`
- STEP-062 两处用户修改及历史 untracked/build/cache/tgz 均未暂存、未提交。

View File

@@ -0,0 +1,23 @@
# STEP-069 MapLibreParticleLab V3-05 Android/JNI/RN v2 多 Plane 入口
状态:完成(IMPLEMENTED / WAITING_ANDROID_VALIDATION)
## 已完成
- [x] RN 包升为 `13.4.1-particle.3`,导出 `FieldPayloadRefs`、`FieldValueSource`;v2 主入口只接受 refs。
- [x] 保留 v1 `submitWindField` ABI,并提供明确的 `submitLegacyVectorField` 公共兼容入口。
- [x] Native TurboModule Spec 改为 codegen 支持的三个明确 string plane 参数;公共 `FieldRuntime.submitField(..., FieldPayloadRefs)` 保持不变,空 scalar/vector string 仅为 Native ABI 的 optional marker。Kotlin 在读取前校验 validity、local file、非空和三 plane aggregate 64 MiB;Java/JNI 用 presence flags 和长度恢复 optional bundle。
- [x] Java `WeatherRuntime.submitField` 给 nullable scalar/vector 标注 `@Nullable`,并独立拒绝 total 超过 64 MiB,避免绕过 Kotlin 后分配 oversized packed array。
- [x] `RasterArraySource` v1/v2 decode 后共用同一 duplicate、monotonic、compatibility、initial-window 与 Timeline 路径。
- [x] 桥接 `SCALAR|VECTOR_MAGNITUDE` 与 positive finite `advectionScale`,只更新既有 renderer config。
- [x] Core source 测试覆盖三种合法 v2 bundle、缺 validity/no channel 与 semantics incompatibility,以及 v1 legacy 与 compatible v2 vector-only 进入同一 `RasterArraySource` Timeline。
## 范围与验证
未改 WeatherV2、NavSea、Timeline/T0/T1/MIX/rollover/lifecycle,也未改 STEP-062 dirty 或无关 untracked 内容。以下均经 build-output-throttle:外部复核指定的 `combine-js-to-schema-cli.js /tmp/step069-schema.json packages/maplibre-particle-rn/src/NativeWeatherRuntimeBridge.ts` 实际 PASS;`./node_modules/.bin/tsc --noEmit -p packages/maplibre-particle-rn/tsconfig.json` PASS;`source.test.cpp.o` 精确编译 PASS;`ninja -C build-linux-opengl -j3 mbgl-test` 重链 PASS;`Source.HostFieldV2BundleUsesTheSameTimelineIngress:Source.HostFieldLegacyAndCompatibleV2VectorOnlyShareOneTimeline` 2/2 PASS。`git diff --check` PASS。Android Gradle/AAR/APK/ADB、Jest 均未执行;Android compile 留给 V3-08,不能标记 Android PASS。
## Git 收尾
- implementation commit:`148bf80fe41ba566de6c1d4b439cfe372a8a453c`(`feat: add v2 multi-plane field ingress`)。
- 本 Step 仅显式暂存实现文件;STEP-062 两处用户 dirty、历史 untracked、`.codex-build-logs/`、旧 tgz/build/cache 均未纳入。
- 文档 handoff commit 与 push/Final HEAD 在完成报告最终提交后回填。

View File

@@ -0,0 +1,38 @@
# STEP-070 MapLibreParticleLab V3-06 四类场 Fixture 与 RN 通用回归
状态:IMPLEMENTED / WAITING_ANDROID_VALIDATION
## 目标与边界
在 `particle/main` `3b012759` 上增加离线 V3 fixture、转换/校验工具、RN Test Host `.3` 公共 API 接线和最小 Core contract test。不修改 Timeline/Playback/T0/T1/MIX/rollover、schema、shader、WeatherV2、NavSea 或 STEP-062 两份用户 dirty 文档;不运行 Android Gradle/AAR/APK/ADB/Jest。
## 完成内容
- [x] `tools/particle_v3_fixture` 生成并校验 v2 metadata、shared validity、可选 scalar/vector、长度与 SHA-256。validator 对每一个 validity=1 的 cell 检查所有存在 plane:finite 且不等于 noData。
- [x] `wind-real` 从本地 canonical v1 `field.f32` 重新封装;shared validity 严格派生为 `finite(U) && finite(V) && U != -9999 && V != -9999`。
- [x] `wave-real` 由 WeatherV2 `ecmwf-wam/waves/2026090912/wave_bin` 的正式 `2026090918/2026090919` BIN 和 header sidecar 解码:scalar=`height_m`,vector=规范化 `unit_to_u/unit_to_v`,语义 `DIRECTION`;`period_s` 和原始 degree 不进入 canonical。
- [x] `tide-real` 由 WeatherV2 `cmems/tide/2026090900/current_bin` 的正式 `2026090901/2026090902` BIN 和 header sidecar 解码为 vector-only `VELOCITY`。
- [x] Wave/Tide 每帧 summary 记录正式源 BIN 与 sidecar SHA-256、target_time、referenceTime、bounds、grid、projection、included/excluded channels 和 validity 派生规则;两者 generation 为小且严格递增的 `1/2`。
- [x] Rain 保持 `rain-decoder-fixture`,manifest/provenance 明确为非真实 field values,因为本 Step 未实现 PMTiles reader;fallback generation 为严格递增的 `1/2`。
- [x] Test Host 的 `fieldDatasets` 已指向 `wave-real` 和 `tide-real`,继续仅经 package `FieldRuntime` 公共 API 提交 local file refs。
- [x] 保留并验证 Wave synthetic scalar/vector independence;新增 C++ provider-neutral 四类 shape contract test。
## 验证
- [x] `python3 tools/particle_v3_fixture/generate_fixtures.py --write-reference-fixtures --validate`:PASS。
- [x] package `.3` `tsc --noEmit`:PASS。
- [x] RN Test Host `npm run typecheck`:PASS(不含 Jest)。
- [x] RN TurboModule `combine-js-to-schema-cli.js`:PASS。
- [x] 使用 `CCACHE_DIR=/tmp/step070-ccache`,重新编译 `source.test.cpp.o`:PASS。
- [x] `mbgl-test` / `mbgl-test-runner` 完整 Linux OpenGL 重编译/重链:459/459 完成;`Source.ParticleFieldV3*` 过滤测试 6/6 PASS;验证链退出码 0。
- [x] `git diff --check`:PASS(提交前复核)。
- [ ] Android Gradle/AAR/APK/ADB/Jest:按任务范围未运行。
## 当前结果、阻塞与下一步
fixture 数据契约、TypeScript/codegen、Linux 完整重链与 V3 Source 过滤测试均已完成。Android/JNI 真实编译和设备视觉验收仍由后续统一周期完成。Rain 要升级为真实数值场,需要另行实现只读 PMTiles reader;不得把现有 fallback 改称 real。
## Git 收尾
- implementation:`8588e6fcb4f3f5d874159c2e42619c5227cb7921`(`feat: add V3 field fixtures and RN regression host`),已 push 至 `origin/particle/main`。
- docs-only Git handoff 在 `git add` 创建 `.git/index.lock` 时因只读文件系统被阻断,未提交、未推送;STEP-062 两份用户 dirty 文档与历史 untracked/build/cache/tgz 均未暂存。

View File

@@ -0,0 +1,37 @@
# STEP-071 — MapLibreParticleLab V3-07 Linux 综合冻结
状态:CLOSED / PASS(Linux)
日期:2026-09-11
基线:`fcc533dc9518395d0c25794131f53f0fae20d51c`
## 目标
在进入 V3-08 Android 统一验收前,对 V3-02~V3-06 的 canonical field、Host ingress、Timeline、RasterParticle 与 RN 公共桥接做一次 Linux 综合冻结。
## 冻结链结果
- [x] canonical fixture validator:PASS。
- [x] `packages/maplibre-particle-rn` TypeScript:PASS。
- [x] RN Test Host `npm run typecheck`:PASS。
- [x] RN TurboModule codegen:PASS。
- [x] `mbgl-test` / `mbgl-test-runner` Linux OpenGL target:PASS / up-to-date。
- [x] V3 综合过滤测试:21/21 PASS。
- [x] `git diff --check`:PASS。
- [ ] Android Gradle/AAR/APK/ADB/Jest:本 Step 不运行。
## 综合测试覆盖
过滤范围:`Source.ParticleField*`、`Source.HostField*`、`Source.RasterArrayTimeline*`、`Source.RasterArraySharesCanonicalSnapshotsWithWeatherColorLayer`、`Layer.RasterParticle*`。
最终 18 个 Source + 3 个 Layer,共 21 项全部 PASS。
## 冻结中发现并修正的测试问题
1. `HostFieldSubmissionRejectsInvalidWithoutChangingLastValidField` 旧测试用同一 `uniform-east` payload 构造 T0/T1,正确 Runtime 会先返回 Duplicate。测试改为 east→north 两份兼容但不同内容。
2. `RasterArrayTimelineCandidatesReachReadyThroughFieldAdapter` 使用的 east/north/vortex 旧 fixture 均为 `timeIndex=0`;当前 Adapter 保留 `validTime=timeIndex+1`,导致 Timeline 正确 fail-closed。测试仅在局部 metadata 副本中改为 0/1/2,未修改共享 fixture。
3. 两处均为 test fixture 陈旧,不修改 Runtime、Duplicate 判定顺序、Timeline fail-closed 或生产 API。
对应 Codex 任务:
- `docs/40_任务/STEP-071A_V3-07_Linux综合冻结_测试Fixture修正_Codex任务_v1.md`
- `docs/40_任务/STEP-071A_V3-07_Linux综合冻结_测试Fixture修正_Codex任务_v2.md`
## 边界
- 未修改 `src/`、`include/`、shader、JNI、Android、RN runtime、WeatherV2 或 NavSea。
- 已知既有 `Layer.WeatherColorUsesIndependentFixedOpacity` 断言债不属于本 Step,不修、不作为 V3-07 gate。
- STEP-062 用户 dirty 与历史 untracked/build/cache/tgz 均保持隔离。
- 下一步:V3-08,一次 Android 编译/安装/实体机综合验收。

View File

@@ -0,0 +1,57 @@
# STEP-072 — V3-08 Android Native v2 与单次综合验收准备
状态:IMPLEMENTED / WAITING_ANDROID_VALIDATION
日期:2026-09-11
基线:`dd356a277db05ce0075bee43f431ddeb2aba19cc`
## 目标
在不执行 Android 编译/安装的前提下,完成 V3-08 前的 Native/RN 版本坐标准备、Test Host 单 Bridge 收口与单次实体机验收流程冻结。
## 已完成
- [x] Native Android `VERSION`:`13.4.1-particle.1 → 13.4.1-particle.2`。
- [x] RN package `.3` 的 Android dependency 与 Expo plugin 统一指向 Native `.2`。
- [x] RN Test Host plugin 统一指向 Native `.2`。
- [x] release manifest 升为 RN `.3` + Native `.2`,Android 状态保持 `not_run`,并保留 `.2` 已发布历史追溯。
- [x] 删除 Test Host legacy local `@maplibre-particle/weather-runtime-bridge` tracked 源码/配置。
- [x] 删除 Test Host 根 `react-native.config.js`,`tsconfig.json` 只保留 `App.tsx` 与 `src`。
- [x] Expo autolinking 复核:只剩正式 `@navsea/maplibre-particle-rn` 提供 `WeatherRuntimeBridge`。
- [x] 建立 `docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md`。
## 静态验证
- [x] RN Test Host `npm run typecheck`:PASS。
- [x] package TypeScript:PASS。
- [x] RN TurboModule codegen:仅生成 `NativeWeatherRuntimeBridge`,PASS。
- [x] canonical fixture validator:PASS。
- [x] Expo autolinking:3 dependencies;包含 MLRN、正式 particle package、Expo;不再包含 legacy bridge。
- [x] 两个 Expo plugin `node --check`:PASS。
- [x] release manifest JSON:PASS。
- [x] `git diff --check`:PASS。
## 关键发现与修正
准备阶段发现 Test Host 会同时 autolink legacy local bridge 与正式 package bridge;二者 Native module name 都是 `WeatherRuntimeBridge`、codegen library 都是 `WeatherRuntimeBridgeSpec`。STEP-072A 删除了旧本地 bridge 注册与源码,使 V3-08 clean prebuild 前只剩正式 package bridge。
验收文档同时修正了 AAR `javap` 方法、Makefile `MLN_ANDROID_GRADLE` 的命令行覆盖方式、Linux→Win11 Maven Local handoff,以及 Win11 `gradlew.bat`/Metro/固定 ADB 设备流程。
## 未执行
- Android Gradle / AAR build / Maven publish / APK build / ADB / 实体机验收:未执行。
- Jest:未执行。
- 因此本 Step 不声明 Android PASS,也不创建 `.3` release tag。
## 下一步
用户按 `docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md` 只执行一次 Android build/install 周期,覆盖 Wind、Rain、Wave、Tide 与 synthetic Wave 五组场数据。
## Git 实现基线
- implementation/preparation commit:`11cf58d344e64e6aff77deceb2c34c8eda045f1b`(`chore: prepare V3-08 android validation`)。
- 已成功 push 到 `origin/particle/main`;push 后实现基线 HEAD 与远端一致。
## 2026-09-11 V3-08 最终关闭
状态:**CLOSED / PASS**。
Win11/CARBON7i 使用 Gradle 8.14.3、AGP 8.12.0、Kotlin 2.1.20、JDK 17.0.18、NDK 27.1.12297006、compileSdk/targetSdk 36、minSdk 24 完成正式 npm 消费链与 Debug APK 构建。dependencyInsight 最终解析到 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2`。
实体机 `192.168.1.146` 上五组数据均满足 T0=`ACCEPTED_PENDING_INITIAL_WINDOW`、T1=`INITIAL_WINDOW_READY`、最终 Runtime=`READY`:Wind real、Rain scalar-only、Wave real、Tide/Current、Wave synthetic independence。
控制面 PLAY/PAUSE、HALF/NORMAL/DOUBLE、FPS AUTO/30/60、seek T0/T1、Weather/Particle 独立显隐、Field stops、Particle color、Trail color、Apply visual 均返回 `ACCEPTED`;最终 FATAL/JNI/UnsatisfiedLinkError 扫描为空。
测试期间发现 fixture 位于 Metro project root 外,临时 Win11 `D:\V308` 验收 worktree 通过 `watchFolders` 纳入 `integration/react-native/particle-v3-fields` 后 bundle 成功。该临时 Metro 修改不进入正式仓库,不属于 Native/RN Runtime 缺陷。
最终产物:Native `.2` AAR SHA-256 `f890f834692d3cacb8a281b2d0f82cf4a7f8ba7756132092be800505b961e9a2`;RN `.3` tgz SHA-256 `16e5dab86aac9832f7a48d8c35dd9821f80f52e7e53ff4e0e6c98724781eda3a`;APK SHA-256 `49824062CAB72771282294114B5A818D2067F9B786B8697D2D2A983BF0B743E2`。

View File

@@ -0,0 +1,39 @@
# STEP-073 — QBIN2 V4 Core 与通用 Field Profile Validator
状态:完成
日期:2026-09-13
## 目标
建立不接入 Decoder、Renderer、Android/JNI/RN 或 V3 生产路径的 QBIN2 V4.4 provider-neutral Core primitive,并以数据驱动 Contract Schema v1 验证 `DecodedFieldFrame`。
## 完成项
- [x] 建立 `ResolvedFieldProfileContract`、`DecodedFieldFrame`、`FieldSeries`、`EncodedFieldStore` 与独立 `ForecastClock`。
- [x] 以独立 Validity descriptor 加共享只读 payload 表达单一 Frame validity domain;descriptor 与 payload 使用同一 `shared_ptr`,不复制位图。
- [x] 实现纯数据驱动 `FieldProfileValidator`:schema、REQUIRED 双向一致、额外 plane、shared-validity 能力、时间关系及 Core 单位/速度组规则均返回结构化结果。
- [x] 保持普通 Scalar 的 semanticID/unitID opaque;无 profileID 产品分支。
- [x] 保持同 validTime 的 Duplicate、identity conflict、revision replacement 与相邻插值兼容性语义;fieldKey 独立 cadence/window。
- [x] 增加 C++ 自动化测试,并通过 Linux OpenGL 构建与 V3 Field/Timeline 定向回归。
## 允许修改
`include/mbgl/particle/field/`、`src/mbgl/particle/field/`、根/测试 CMake、`test/particle/`、本 Step、开发记录与完成报告。
## 禁止修改确认
未修改 Renderer、shader、`platform/android/`、RN/integration、Style JSON/source schema;未新增 QBIN decoder、JNI、Java/Kotlin 或 React API;旧 V3 Timeline/`submitField(...)` 路径未切换。
## 验证
- `cmake --build build-linux-opengl --target mbgl-test -j3`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:PASS。
- `git diff --check`:PASS。
以上构建/测试均通过输出节流脚本执行。未执行 Android Gradle、APK、ADB 或真机操作。
## 阻塞与下一步
无本 STEP 阻塞。下一步必须等待独立审计后再决定是否进入 QBIN2 Encoded Ingress / Decoder;本 STEP 未自行开展该工作。

View File

@@ -0,0 +1,40 @@
# STEP-073 — QBIN2 V4 Core 与通用 Field Profile Validator 审计修正 v2
状态:完成
日期:2026-09-13
## 目标
仅修正 STEP-073 v1 审计确认的 QBIN2 V4 Core/通用 Validator 三项问题:DEGREE Unit ID、Plane `(semanticID, groupID)` 唯一性与 ForecastClock HALF tick 舍入漂移。
## 完成项
- [x] `FieldUnitDegrees` 改为冻结 QBIN V2 registry 的 numeric ID `4`;`NONE=0`、`MPS=1` 未变。Direction `unitID=4` 可接受,`unitID=2` 返回 `InvalidCoreUnit`。
- [x] 实际 Frame 与 Contract `requiredPlanes[]` 均以 `(semanticID, groupID)` 判重;component 改为 descriptor 属性。semantic/group 已定位但 component 不同返回 `PlaneDescriptorMismatch`。
- [x] ForecastClock 以整数 half-millisecond remainder 累积,HALF 不再逐 tick 向上取整;seek 清除 remainder,溢出 fail-closed。
- [x] U/V 继续使用各自 semanticID,并按既有通用 U_EAST/V_NORTH velocity group 规则通过。
- [x] 新增对应 C++ 自动化覆盖并完成 Linux 定向验证与 V3 回归。
## 允许修改
仅修改 `include/mbgl/particle/field/`、`src/mbgl/particle/field/`、`test/particle/field_runtime.test.cpp`、本 Step、开发记录及完成报告。
## 禁止修改确认
未修改 Renderer、shader、`platform/android/`、RN/package/integration、现有 V3 ingress/timeline 生产路径;未实现 QBIN2 Decoder、Binding/Projection、JNI 或 Android/RN 接入。
## 验证
所有构建/测试均经 `build-output-throttle` 的 `run_limited.py` 执行:
- `cmake --build build-linux-opengl --target mbgl-test -j3`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:PASS。
- `git diff --check`:PASS。
未执行 Android Gradle、APK、ADB、真机操作或 Jest;未执行 iOS/Metal 验证。
## 阻塞与下一步
本审计修正无阻塞。后续工作须另行批准;本 STEP 在此停止,不进入 QBIN2 Decoder 或任一接入阶段。

View File

@@ -0,0 +1,40 @@
# STEP-073 — QBIN2 V4 Core 与通用 Field Profile Validator 审计修正 v3
状态:完成
日期:2026-09-13
## 目标
仅关闭独立审计发现的 Generic Field Profile Validator 协议遗漏:Native 已知 Core semantic 不得被 Contract 与 Frame 联合伪装,且 Direction Circular Quantization 必须有完整的 provider-neutral 结构约束。
## 完成项
- [x] 在 Validator 内建立静态、数据表驱动的 Native Core semantic contract:`1=VELOCITY_COMPONENT/MPS/U_EAST`、`2=VELOCITY_COMPONENT/MPS/V_NORTH`、`3=VALIDITY/NONE/NONE/group0`、`10/11=DIRECTION_ANGLE/DEGREE/NONE/group0`。表不依赖 profile/product 名称。
- [x] Contract 与实际 Frame 都在原有 descriptor 比较前核对该表;冲突返回结构化 `SemanticContractMismatch`。
- [x] Unknown `SCALAR` semanticID 和 unitID 仍为 opaque,继续经过 Contract/Frame descriptor 一致性检查而不加入 Native unit 白名单。
- [x] 所有 `DIRECTION_ANGLE` 强制 `DEGREE(4)`、`NONE`、`groupID=0`、`UINT16`、`offset=0`、`scale=360/65536`(允许极小 float32 表示误差);违反返回 `InvalidCoreUnit` 或已知 semantic 的 `SemanticContractMismatch`。
- [x] 覆盖已知 semantic 伪装、正确 Direction、Direction component/group/dtype/scale/offset/unit 拒绝、opaque Scalar、既有 plane identity、DEGREE registry 与 ForecastClock HALF 行为。
## 允许修改
仅修改 `include/mbgl/particle/field/field_profile_validator.hpp`、`src/mbgl/particle/field/field_profile_validator.cpp`、`test/particle/field_runtime.test.cpp`、本 Step、完成报告与开发记录。
## 禁止修改确认
未修改 QBIN2 Decoder、Binding/Projection、Renderer、shader、JNI、`platform/android/**`、`packages/maplibre-particle-rn/**`、`integration/react-native/**` 或现有 V3 生产入口;未实现 parser/header/CRC/packing。
## 验证
所有 noisy 命令均经 `build-output-throttle` 的 `run_limited.py` 节流执行:
- [x] `cmake --build build-linux-opengl --target mbgl-test -j3`:PASS。
- [x] `build-linux-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:PASS。
- [x] `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:PASS。
- [x] `git diff --check`:PASS。
未执行 Android Gradle、APK、ADB、真机、Jest 或 iOS/Metal。
## 当前结果、阻塞与下一步
本轮所有范围内项完成,无阻塞。本 STEP 在此停止,等待 ChatGPT 独立审计;不进入 STEP-074 或任何 QBIN2 ingress/Decoder 工作。

View File

@@ -0,0 +1,45 @@
# STEP-073 — QBIN2 V4 Core 与通用 Field Profile Validator 最终关闭修正 v4
状态:完成
日期:2026-09-13
## 基线
- 开始 HEAD:`0b66a5cd30e91423cbc864a327a53bed06cae91c`。
- 开始工作区含 STEP-062 文档修改及多项历史 untracked 文件;本 Step 未修改、暂存或提交它们。
## 目标
关闭独立审计在实际重链 `mbgl-test-runner` 后发现的三项问题:Frame 的唯一共享 validity domain、实际已知 Core semantic 的错误优先级,以及 ForecastClock 测试的 pause/resume 语义。
## 完成项
- [x] `DecodedFieldFrame` 允许空 numeric `planes[]`,完整 designated validity 仍结构合法;所缺 required numeric plane 由 Validator 返回 `MissingRequiredPlane`。
- [x] designated validity 强制 `Validity/Bitset/NONE/group0/None/REQUIRED`、cell count 与 bit payload 长度一致,并与 `validityBits` 使用同一 `shared_ptr`;任一违反均为 `InvalidFrame`。
- [x] 禁止 `planes[]` 出现第二张 Validity plane,确保一帧只有唯一共享 validity domain。
- [x] 保留静态数据表驱动的 Core semantic contract,并在 required-plane descriptor 匹配前检查实际 plane;semantic 1 Scalar 伪装、semantic 10 component/group 冲突均返回 `SemanticContractMismatch`。
- [x] unknown Scalar 及 opaque unit 的可扩展行为保持;Direction circular quantization v3 规则未变。
- [x] ForecastClock 测试在 `pause()` 后显式 `play()`,未改变生产 `pause()` 语义。
## 允许修改
仅修改 Field Runtime Core、通用 Validator、对应 C++ 测试和本 Step 文档/报告/开发记录。
## 禁止修改确认
未修改 QBIN2 Decoder、Binding/Projection、Renderer、shader、JNI、`platform/android/**`、`packages/maplibre-particle-rn/**`、`integration/react-native/**`、Style JSON/source schema 或 V3 `submitField(...)` 路径;未执行 Android Gradle、APK、ADB、真机、Jest 或 iOS/Metal。
## 验证
构建/测试完整输出均落入 `/tmp` 限流日志(仓库未提供 build-output-throttle 指定的 helper script,仅在失败时读取有限末尾窗口)。
- [x] `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:实际重编译并重链 PASS。
- [x] `mbgl-test-runner --gtest_list_tests`:确认包含 `ParticleFieldProfileValidator.` 与 `ParticleFieldRuntime.`。
- [x] `mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:实际运行 11 tests,11 PASS,0 failed;绝非 0 tests。
- [x] `mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:实际运行 11 tests,11 PASS,0 failed。
- [x] `git diff --check`:PASS。
## 当前结果、阻塞与下一步
STEP-073 v4 的关闭条件已满足,无范围内阻塞。停止于此,等待 ChatGPT 独立复验;不进入 STEP-074。

View File

@@ -0,0 +1,9 @@
# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder v1
状态:完成(Core 范围)。开始 HEAD:`b8a629b7b65067f7f561db473312484bd8f0451d`。开始工作区已有 STEP-062 文档修改及历史 untracked 文件,未清理、暂存或提交。
新增独立 `mbgl::particle::ingress::QbinV2Decoder` 和无状态 `EncodedFieldIngress`。其顺序为 QBIN structural decode、构造 `DecodedFieldFrame`、再调用既有 generic `FieldProfileValidator`;未接入 Store、Renderer、Binding、Host 或平台 bridge。
Decoder fail-closed 校验 QBV2 LE header/descriptor、保留字段、FRAME/WebMercator/node 限制、时间/边界/容量、精确 canonical packing 与零 padding、ISO-HDLC CRC(CRC 字段虚拟清零)、single shared validity、invalid-cell raw-zero,以及 per-plane local content identity。numeric payload 保持原始 bytes。
真实 CMake runner 已加入 `ParticleQbinV2Decoder`、`ParticleEncodedFieldIngress`;fixture 仅在 test 范围,涵盖 2×2 与 513×769 构造。

View File

@@ -0,0 +1,36 @@
# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder v2
状态:完成
日期:2026-09-13
## 基线
- 开始 HEAD:`83a84452f23d019cbb143630eac04631552fef89`。
- 开始前已有 STEP-062 文档修改及历史 untracked 文件;未清理、覆盖、暂存或提交。
## 完成项
- [x] per-plane `contentIdentity` 改为 raw bytes 加 descriptor 的明确 Little Endian canonical byte stream;不含 `byteOffset`。
- [x] 64 MiB 为整份 QBIN 上限,32 MiB 为各 Plane raw `byteLength` 总和上限;canonical padding 不计入 decoded data。
- [x] BITSET 与 `VALIDITY` 双向强制;构造后以 `DecodedFieldFrame::isStructurallyValid()` 作最终 fail-closed 自检。
- [x] test-only fixture writer 对整数、float32、float64 明确写 Little Endian。
- [x] decoder/ingress runner 扩为实际 18 tests,包含 header、descriptor、limits、packing、padding、CRC、validity、invalid-cell、identity、513×769 与 Contract 矩阵。
- [x] unknown Scalar opaque unit、unknown field semantic 与 `validTimeUtc < referenceTimeUtc` 的 decoder 容器行为均通过。
## 验证
- `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:PASS(实际重编译/重链)。
- `mbgl-test-runner --gtest_list_tests`:PASS,确认 suites 已注册。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:18/18 PASS。
- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:STEP-073 11/11 PASS。
- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:既有 V3 11/11 PASS。
- `git diff --check`:PASS。
## 范围确认
只修改 QBIN decoder/header 与 C++ test,未进入 Store submission、Binding/Projection、Renderer/shader、JNI、Android、RN、Test Host、URL/HTTP/download/gzip、iOS/Metal 或 Jest。
## 阻塞与下一步
无范围内阻塞。本 Step 到此关闭;提交并 push 后停止,不进入下一 Step。

View File

@@ -0,0 +1,49 @@
# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder v3
状态:完成
日期:2026-09-13
## 目标
在不扩展 STEP-074 生产范围的前提下,收口 Decoder 入口错误、Little Endian fail-closed 与 32 MiB raw decoded-data 上限的最终测试覆盖。
## 基线
- 开始 HEAD:`56b8dab90f914438e6fcaec6278146c96c8df638`。
- 开始前已有 STEP-062 文档修改及历史 untracked 文件;均未清理、覆盖、暂存或提交。
## 必做项
- [x] 空 span、1 byte、111 bytes 都经 production `QbinV2Decoder::decode` 返回 `InputTooSmall`。
- [x] 112-byte 非 `QBV2` 输入返回 `BadMagic`;空 `fieldID`、零 `generation` 返回 `InvalidRuntimeMetadata`。
- [x] 明确验证 `dataVersion=0` 的合法 frame 仍 Accepted。
- [x] Header width 写成 big-endian `00 00 00 02` 并重算 CRC 后以 `LimitExceeded` 结构化拒绝。
- [x] Descriptor count 写成 mixed-endian `00 00 00 04` 并重算 CRC 后以 `InvalidDescriptor` 结构化拒绝。
- [x] raw sum 为 31,712,633 bytes、canonical payload 为 31,712,648 bytes 的 1447×1449 FRAME Accepted;15 bytes padding 未计入 32 MiB decoded limit。
## 允许修改
- `test/particle/qbin_v2_decoder.test.cpp`。
- 本 Step 记录、完成报告与开发记录。
## 禁止修改
- Host、网络、JNI、Android/RN、Binding/Projection、Renderer、Store submission 与 production Decoder。
## 构建与测试
- `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:PASS(经输出限流器,实际重编译/重链)。
- `mbgl-test-runner --gtest_list_tests`:PASS,Decoder/Ingress suite 已注册。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:24/24 PASS。
- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:STEP-073 11/11 PASS。
- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:既有 V3 11/11 PASS。
- `git diff --check`:PASS。
## 当前结果
仅补 6 个 production decode 覆盖测试,未发现生产 Decoder 缺陷,故未修改生产实现。STEP-074 由 v2 的 18 tests 扩至实际 24 tests,全部通过。
## 阻塞与下一步
无范围内阻塞。按任务要求完成 scoped commit 与 `origin/particle/main` push 尝试后停止,不进入 STEP-075。

View File

@@ -0,0 +1,46 @@
# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder v4
状态:完成
日期:2026-09-13
## 目标
仅修正 v3 `AcceptsRawLimitWithCanonicalPaddingExcluded` 的边界 fixture,使它严格证明 decoded raw 数据不超过 32 MiB、canonical packed payload 超过 32 MiB、整个 QBIN 文件不超过 64 MiB 时 production Decoder 仍接受。
## 基线
- 开始 HEAD:`f4a495ff38b77f586f97853493dda02ab067b26d`。
- 开始前已有 STEP-062 文档修改及历史 untracked/build 文件;均未清理、覆盖、暂存或纳入本 Step。
## 必做项
- [x] `AcceptsRawLimitWithCanonicalPaddingExcluded` 改为 1101×1890(2,080,890 cells)、17 plane:1 个 single shared validity BITSET 与 16 个 UINT8 numeric plane;descriptor 依 semantic 3、4–19 canonical 排序。
- [x] 测试直接计算并断言 `rawSum=33,554,352 <= 33,554,432`、`payloadLength=33,554,448 > 33,554,432`、`payloadLength > rawSum`、`totalQbinBytes=33,555,104 <= 67,108,864`。
- [x] fixture 经 production `QbinV2Decoder::decode(...)` 返回 Accepted。
- [x] cells、planes、canonical packing、single shared validity、descriptor 与 64 MiB 文件限制均在构造及断言内满足。
## 允许修改
- `test/particle/qbin_v2_decoder.test.cpp`。
- 本 Step 记录、完成报告与粒子开发记录。
## 禁止修改
- Production Decoder、Host、网络、JNI、RN、Binding/Projection、Renderer、Store submission、Android/iOS 平台路径。
## 构建测试
- `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:PASS(输出节流器;实际目标已重新检查并确认最新)。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:实际 Running 24,24/24 PASS(XML:Decoder 22、Ingress 2)。
- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:实际 Running 11,11/11 PASS。
- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:实际 Running 11,11/11 PASS。
- `git diff --check`:PASS。
## 当前结果
旧 fixture 的 payload 仍低于 32 MiB,不能捕获把 packed payload 误当 decoded raw limit 的实现错误。新 fixture 以 16 个每个 6-byte canonical padding 的 numeric plane 将 payload 推至 limit 以上 16 bytes,同时 raw sum 比 limit 小 80 bytes;production Decoder 接受,未发现生产缺陷,故未修改 production 代码。
## 阻塞与下一步
无范围内阻塞。未执行 Android Gradle/APK/ADB/Jest;按任务要求停止,不进入下一 Step。

View File

@@ -0,0 +1,36 @@
# STEP-075 — QBIN2 V4 Binding 与 Projection Content Identity v1
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD:`31783f25a8153f48000f19ac7cc2f2113cf47818`。
- 仅实现 provider-neutral Core Binding/Projection;没有接入 Renderer、GPU、JNI、Android、RN、NavSea 或网络。
## 必做项
- [x] 新增独立 `ColorBinding`、`ParticleBinding`、`ColorProjectionContentIdentity`、`ParticleProjectionContentIdentity` 与 `BindingProjectionResolver`。
- [x] Color 可选择 Scalar/Velocity/Direction numeric plane;unit 只作 equality assertion。
- [x] Particle 支持同 fieldKey/window 的 U/V vector 与 DirectionAngle 两种模式;没有定义速度大小或 Renderer 行为。
- [x] 每次 resolve 以 binding 的 fieldKey 独立调用 `EncodedFieldStore::resolve`,保留 Core-owned requested time 与每 field window。
- [x] SampleState 仅保存 requestedForecastTimeMs/mix;content identity 不纳入 mix、播放状态或视觉参数,且保留只读 frame/storage shared_ptr。
- [x] 结构化 fail-closed 返回 InvalidBinding、FieldSeriesMissing、WindowUnavailable、PlaneMissing、Class/Unit/Component/Group mismatch 与 InterpolationIncompatible。
- [x] 测试 wind `00/01/02` 与 wave `00/03/06` 在 `01:30` 使用各自窗口;MIX `0/.25/.5/.99` 保持 identity/storage;Scalar-only revision 仅改变 Color identity;覆盖 Direction 成功及 unit/selector failure。
## 验证
- Linux OpenGL `mbgl-test-runner` 真实重建:PASS(输出节流)。
- 规定过滤组 XML:50 tests、0 failures、0 errors:Binding 4,Runtime/Profile 11,QBIN Decoder/Ingress 24,V3 Source 11。
- `git diff --check`:PASS。
## 边界与未执行
- FieldSeries 在提交阶段已拒绝不可插值相邻 frame;Resolver 仍保留 `InterpolationIncompatible` 结构化保护,正常 Store 无法形成该窗口。
- 未执行 Android Gradle/APK/ADB/Jest、iOS/Metal 或任何 Host/Renderer 验证;它们不属于本 Step,不能视为 PASS。
- 任务开始前既有 STEP-062 dirty、历史 untracked、build logs 与 RN 输出均未清理、暂存或提交。
## 下一步
STEP-075 关闭;按任务要求停止,不进入 STEP-076。

View File

@@ -0,0 +1,46 @@
# STEP-075 — QBIN2 V4 Binding 与 Projection Identity v2
状态:完成
日期:2026-09-13
## 目标与基线
- 基于 STEP-075 v1(`3616daec30079395847cc6a538fbd963641b7e12`)完成独立审计收口。
- 仅修改 provider-neutral Core Binding/Projection、其离线单元测试与本 Step 文档;没有进入 STEP-076。
## 必做项
- [x] 新增 `ProjectionFrameCoordinate`,只以 `fieldID + validTimeMs` 表达 T0/T1 window coordinate;完整 `FrameContentIdentity` 仍保留在 resolved frame 中作 revision/runtime 诊断。
- [x] Color 和 Particle identity 均统一比较 binding、T0/T1 coordinate、各自 selected plane content identity 及 T0/T1 shared validity content identity。
- [x] 移除 Resolver 公共 `InterpolationIncompatible` 错误码;`FieldSeries::submit` 已拒绝不兼容相邻 frame,`resolve` 只返回已验证 window。
- [x] 通过 Store 的 same-valid-time `ReplacedRevision` 测试 Scalar-only、U/V-only、validity-only revision:分别为 Color-only change、Particle-only change、两者均 change。
- [x] 保持各 binding 独立 fieldKey/cadence、MIX 不进入 identity、in-window storage stable,以及 DirectionAngle success/failure coverage。
## 允许修改
- `include/mbgl/particle/field/field_binding.hpp`
- `src/mbgl/particle/field/field_binding.cpp`
- `test/particle/field_binding.test.cpp`
- 本 Step v2、完成报告 v2 与 `粒子开发记录.md`
## 禁止修改
- 未修改 Decoder、FieldSeries、Profile Validator、Renderer、Host/JNI/RN、Android/iOS 或网络数据路径。
- 未执行 Android Gradle/APK/ADB/Jest、iOS/Metal、Renderer/GPU 或真实 QBIN Golden 数据。
## 验证与当前结果
- Linux OpenGL `cmake --build build-linux-opengl --target mbgl-test-runner -j3` 真实重建 PASS(输出节流)。
- 规定 GTest XML `/tmp/step075-gtest.xml`:50 tests、0 failures、0 errors。Binding 4;Runtime/Profile 11;QBIN Decoder/Ingress 24;V3 Source 11。
- `git diff --check` PASS。
## Git
- scoped commit:`90d05f596a42c157502f9a2a5f20425c999cebab`,`fix: close STEP-075 projection identity audit`。
- 已成功 push:`3616daec3..90d05f596 particle/main -> particle/main`。
## 阻塞与下一步
- 无实现阻塞。Android/Jest/iOS/Metal 等禁止范围没有验证,不能视为 PASS。
- STEP-075 已关闭;按任务书停止,不进入 STEP-076。

View File

@@ -0,0 +1,49 @@
# STEP-075 — QBIN2 V4 Binding 与 Projection Identity v3
状态:完成
日期:2026-09-13
## 目标与基线
- 基于 `653e6f2f1598b7817b7912a39877690f248d6f25`,仅关闭 inactive selector 污染 `ParticleBinding` identity 的审计问题。
- 不进入 STEP-076;测试仅使用离线 C++ fixture。
## 必做项
- [x] `ParticleBinding::operator==` 改为 mode-aware:VectorComponents 只比较 U/V selector;DirectionAngle 只比较 direction selector;两者均继续比较 mode、fieldKey 与 expectedUnitID。
- [x] selector identity 继续仅为 `(semanticID, groupID)`;component 保持为 resolved Plane descriptor assertion,不进入 identity。
- [x] VectorComponents 覆盖:仅变更 inactive `directionPlane` 时 binding equality 与 resolved `ParticleProjectionContentIdentity` 均相同;变更 active U selector 则 binding 不同且 `PlaneMissing` fail-closed。
- [x] DirectionAngle 覆盖:仅变更 inactive U/V selectors 时 binding equality 与 resolved identity 均相同;变更 active direction selector 则 binding 不同且 `PlaneMissing` fail-closed。
## 允许修改
- `include/mbgl/particle/field/field_binding.hpp`
- `src/mbgl/particle/field/field_binding.cpp`
- `test/particle/field_binding.test.cpp`
- 本 Step、完成报告 v3 与 `粒子开发记录.md`
## 禁止修改
- 未修改 Decoder、Runtime、Renderer、Host/JNI/RN/Android,未进入 STEP-076。
- 未执行 Android Gradle/APK/ADB/Jest、iOS/Metal,未使用真实服务器 QBIN。
## 验证与当前结果
- Linux OpenGL:`cmake --build build-linux-opengl --target mbgl-test-runner -j3`,真实重建 PASS(全局 build-output-throttle)。
- `ParticleFieldBinding.*`:6/6,0 failure。
- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:11/11,0 failure。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:24/24,0 failure。
- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:11/11,0 failure。
- `git diff --check` PASS。
## Git
- scoped commit:`2645bd725f63c4da12ab48a0cc11ff034c9d9878`,`fix: close STEP-075 binding selector identity audit`。
- push result:`653e6f2f1..2645bd725 particle/main -> particle/main`;remote branch:`origin/particle/main`。
- Final HEAD(scoped implementation delivery):`2645bd725f63c4da12ab48a0cc11ff034c9d9878`。
## 阻塞与下一步
- 无实现阻塞。
- STEP-075 v3 已关闭;按任务书停止,不进入 STEP-076。

View File

@@ -0,0 +1,34 @@
# STEP-076 — QBIN2 V4 Core Host API 与 RasterArraySource 接线 v1
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD:`b148fb6a0c6711182194f6778d80c8f8a60eb037`。
- 任务前 STEP-062 两处 dirty、历史 untracked 和 build 输出保持原样,未暂存、覆盖或提交。
- 本 Step 仅在 C++ Core / style source 接入 QBIN V4;V3 timeline、renderer、JNI/Java/RN/Test Host 均不变。
## 完成项
- [x] `RasterArraySource` 新增隔离 V4 owner 状态:`EncodedFieldStore`、`ForecastClock`、可选 Color/Particle binding;与 V3 `RasterArrayTimelineRuntime` 生命周期和数据完全分离。
- [x] 新增 `submitEncodedField`,严格按 fieldKey/runtime metadata → `QbinV2Decoder::decode` → `FieldProfileValidator::validate` → `EncodedFieldStore::submit` 顺序执行;失败在 Store 提交前返回,结果稳定区分 insert/replace/duplicate/decode/contract/key/identity/stale/incompatible/capacity。
- [x] 新增 binding、requested forecast time、playback state/speed、clock advance、独立 color/particle resolve、单 field / all field clear API;没有 Host 可写 T0/T1/MIX。
- [x] binding 只保存 Host intent;resolve 使用既有 `BindingProjectionResolver` fail-closed。clear 保留 binding intent,且绝不调用 V3 `clearHostFields()`。
- [x] 新增离线 QBIN deterministic suite:submission reject 后旧 window 可继续 resolve;wind `00/01/02` 与 wave `00/03/06` 各自解析 window/mix,clock in-window advance 只改变 mix 不改变 content identity,pause 和 HALF/NORMAL/DOUBLE 语义覆盖;clear/re-submit 生命周期覆盖。
## 验证
- 先通过全局 `build-output-throttle` 配置,再真实执行 `cmake --build build-linux-opengl --target mbgl-test-runner -j3`。构建环境的默认 ccache 位于只读目录,故仅将本次 ccache 临时目录置于 `/tmp`;重建成功。
- `Source.RasterArrayV4HostApi*`:3/3,0 failure。
- `ParticleFieldBinding.*`:6/6,0 failure。
- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:11/11,0 failure。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:24/24,0 failure。
- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:11/11,0 failure。
- `git diff --check`:PASS。
## 边界、阻塞与下一步
- 未执行 Android Gradle/APK/ADB/Jest、iOS/Metal;未修改 Android/JNI/RN、Renderer、shader、Test Host;未使用真实服务器 QBIN,也未新增网络/缓存/gzip 逻辑。
- 无实现阻塞。本 Step 到 C++ Host API 为止,按任务书停止,不进入 Android/JNI/RN bridge。

View File

@@ -0,0 +1,39 @@
# STEP-076 — QBIN2 V4 Core Host API 与 RasterArraySource 接线 v2
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD:`ae03d8bfd2813eb5e65074db16018e37c7cff1c4`。
- 任务开始前检测到 STEP-062 两处已有 dirty,以及历史 untracked/build 输出;均未修改、暂存、提交或清理。
- v1 production 接线保持不变;本 v2 只补 V4 Host API public-path 测试证明与任务文档。
## 完成项
- [x] 针对 `RejectedDecode`、`RejectedContract`、`RejectedInvalidFieldKey`、`RejectedStaleRevision`,先由公开 `RasterArraySource` API 建立可 resolve 的 V4 Particle projection;每次 reject 后重新 resolve,并验证 content identity、T0/T1 coordinate、U/V selected-plane storage 与 shared validity storage 仍为同一内容身份。
- [x] 保留 v1 三个 Source V4 测试,并新增 `RasterArrayV4HostApiRejectsKeepPublicProjectionStable`;该 suite 已为 4 个测试。
- [x] clear 测试先通过公开 V3 `submitInitialRuntimeFieldWindow(...)` 和既有离线 fixture 建立 V3 `field/nextField`。`clearEncodedField`、`clearAllEncodedFields` 后均核验 V3 双端 identity/generation/validTime 不变;V4 resolve 按 fail-closed 返回,重新提交 V4 后既有 binding intent 再次 resolve。
- [x] 未调用 `clearHostFields()`;原子性证明未访问 V4 `encodedFieldStore` 私有成员或私有 helper。
## 修改范围
- `test/source/raster_array_v4_host_api.test.cpp`
- 本 Step、完成报告与 `粒子开发记录.md`
- 未修改 production API、Renderer/shader、JNI、Android/RN/Test Host、QBIN decoder 或 Field Runtime/Binding production 代码。
## 验证
- [x] 真实执行 `cmake --build build-linux-opengl --target mbgl-test-runner -j3`,通过全局 `build-output-throttle`,PASS。
- [x] `Source.RasterArrayV4HostApi*`:Running 4,0 failure。
- [x] `ParticleFieldBinding.*`:Running 6,0 failure。
- [x] `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:Running 11,0 failure。
- [x] `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:Running 24,0 failure。
- [x] `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:Running 11,0 failure。
- [x] `git diff --check`:PASS。
## 边界、阻塞与停止条件
- 未执行 Android 编译、Gradle/APK、ADB、实体机、Jest 或 iOS/Metal。
- 无阻塞;仅完成本任务书的审计收口,停止,不进入下一 Step。

View File

@@ -0,0 +1,29 @@
# STEP-077 QBIN2 V4 Android / JNI / RN 桥接只读调查 v1
状态:完成
日期:2026-09-13
## 目标与结果
- [x] 基于实际 RN、Kotlin、Java、JNI、`RasterArraySource` 源码完成 V4 桥接调查。
- [x] 给出 file URI 传输、small control-plane ABI、V3/V4 timeline/clear 隔离、结果和 lifecycle 映射建议。
- [x] 生成调查报告、完成报告并追加开发记录。
## 范围
只读调查;未修改 production code、package version、Test Host、Renderer/GPU,未下载真实 QBIN,未执行 Android 编译、APK、ADB 或实体机操作。
## 核心结论
V4 必须新增独立 `submitEncodedField` / binding / forecast / clear 命名空间。V3 `submitField`、`clearFields`、forecast/playback API 当前会进入 V3 Host field / timeline,不能复用。RN 传 `qbinFileUri`,Kotlin 以现有 64 MiB local-file guard 读取,再走 Java `byte[]` 和 JNI copy;不要使用 JS number array 或 Base64。
## 验证
- [x] 对要求的全部实际源码路径、JNI registration、Core public V4 API、package codegen/typecheck config 和 `WeatherRuntimeStateTest` 做静态审计。
- [x] `git diff --check`(仅本任务文档)将在提交前执行。
- [ ] Android/Gradle/APK/ADB/实体机:任务明确不执行。
## 阻塞与下一步
无调查阻塞。后续只能按报告中拆分实现 Step,先做 Spec/ABI 与 V4-only facade/JNI 隔离测试;本 Step 到此停止。

View File

@@ -0,0 +1,44 @@
# STEP-078 — QBIN2 V4 Android / JNI / RN 桥接实现 v2
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD:`4b5192e224613a885c1d038594fe06c5eb58df50`。
- 保留 v1 未提交 bridge 草稿,并修正其已确认的 ABI/契约问题。
- 开始时已有 STEP-062 两处 dirty、历史 untracked 与 `.codex-build-logs/`;未清理、改写、暂存或提交它们。
## 完成项
- [x] RN → TurboModule → Kotlin → Java → MapLibreMap → NativeMap/NativeMapView → JNI → `RasterArraySource` 完整贯通独立 V4 submit、两类 binding、forecast time/state/speed 与两类 clear API。
- [x] `EncodedColorBindingInput.expectedUnitID` 端到端传递;不在 bridge 默认为 NONE。
- [x] `ParticleBinding` ABI 固定 U/V/direction 三组 primitive selector slot;Vector 只接受 active U/V,Direction 只接受 active direction,inactive slot 仅允许 `(0,0)`。
- [x] forecast playback state 与 speed 分别调用 `setForecastPlaybackState`、`setForecastPlaybackSpeed`,没有互相覆盖。
- [x] frozen `contractJson` 使用 `contractSchemaVersion`、`requiredFlag` 等 Core 同名字段;新增无 Android/JNI 依赖的纯 C++ parser,JNI 仅调用该 parser。
- [x] parser 单测覆盖合法 schema、缺字段、未知 schema、整数越界、重复 selector、非法 enum、requiredPlanes 上限及非法 profileID。
- [x] QBIN 仍仅通过绝对路径或 `file://` local file URI 进入 Kotlin,沿用读取前后 1..64 MiB guard;没有 URL、HTTP、Base64 或 JS number array。
- [x] Core 十种 `RasterArrayEncodedFieldSubmitResult` 保持一一 `.name` resolve;source 缺失/类型不符只在私有 JNI `0` sentinel 转为 `SOURCE_MISSING` Promise rejection,不伪装 Core result。
## 实际修改范围
- RN/TurboModule/Kotlin:`FieldRuntime.ts`、`types.ts`、`NativeWeatherRuntimeBridge.ts`、`WeatherRuntimeBridgeModule.kt`。
- Java/JNI:`WeatherRuntime`、`MapLibreMap`、`NativeMap`、`NativeMapView`、`native_map_view.hpp/.cpp`。
- 新增 provider-neutral parser:`field_profile_contract_json.hpp/.cpp` 及 `field_profile_contract_json.test.cpp`,并注册 Core/Test CMake。
- 未修改 Renderer、GPU、shader、Test Host、NavSea、STEP-073~076 已冻结 production 语义。
## Linux 与静态验证
- [x] 在 `particle-codex` tmux session 中通过全局 `build-output-throttle` 重建 `mbgl-test-runner`。
- [x] parser 单测、STEP-076 Source V4、STEP-075 Binding、STEP-074 QBIN/Ingress、STEP-073 Runtime/Profile 与 V3 Source 过滤回归:PASS。
- [x] package TypeScript `tsc --noEmit`:PASS。
- [x] TurboModule schema combine/codegen:PASS(仅 `NativeWeatherRuntimeBridge` Spec)。
- [x] Java native declarations 与 C++ JNI registration V4 name set 静态一一核对:PASS。
- [x] `git diff --check`:PASS。
## 未执行、阻塞与停止
- Android Kotlin/Java/JNI 真实编译、Gradle、APK、ADB、实体机:按任务约束未执行,待后续一次性 Android 验收。
- 未执行 iOS/Metal 或 Jest。
- 无实现阻塞;本 Step 到此停止,不进入 STEP-079。

View File

@@ -0,0 +1,44 @@
# STEP-078 — QBIN2 V4 Android / JNI / RN 桥接实现 v3
状态:完成
日期:2026-09-13
## 目标与基线
- 基线:`73779b89bcce1094eb344f4727d6d23904f667f3`。
- 仅关闭 V4 bridge 的 QBIN transport 三层 64 MiB guard 与受限 ID 的 UTF-8 byte-length guard;不进入 STEP-079。
- 任务开始前 STEP-062 两处 dirty、历史 untracked、`.codex-build-logs/` 与 Test Host generated/android/.expo/tgz 均未清理、改写、暂存或提交。
## 完成项
- [x] Java `WeatherRuntime.submitEncodedField(...)` 在 map/JNI 前拒绝空及超过 64 MiB QBIN,并以 UTF-8 1..64 bytes 检查 fieldKey、fieldID 和 contract JSON 中 profileID。
- [x] JNI 在读取 `jbyteArray` 长度后、复制到 `std::vector` 前重复检查 1..64 MiB;无效 transport 直接返回既有 `RejectedDecode`,不产生 oversized vector。
- [x] Kotlin local-file 既有 1..64 MiB read guard 保持;Kotlin fieldKey、fieldID、profileID 改为 `toByteArray(Charsets.UTF_8).size` 的 1..64 bytes 检查。
- [x] RN/TS 改用 `TextEncoder` UTF-8 bytes,fieldKey、runtime fieldID 与 profileID 在 33 个 CJK 字符(99 bytes)时 fail-fast。
- [x] 新增 provider-neutral C++ transport-size helper/test、JDK-only Java guard test 与 TS directed test;未改变 QBIN decoder 的 64 MiB file / 32 MiB decoded-raw 语义、Core 十种 submit result、binding 或 forecast state/speed。
## 修改范围
- RN:`FieldRuntime.ts` 与 private `EncodedFieldGuards.ts`、其 directed test。
- Kotlin/Java:`WeatherRuntimeBridgeModule.kt`、`WeatherRuntime.java` 与 JDK-only V4 guard/helper test。
- JNI:`native_map_view.cpp`;Core only 增加 transport-size header 和 Linux unit test。
- CMake/test registration、本 Step、本报告、`粒子开发记录.md`。
## 验证
- [x] 在 `particle-codex` tmux session,经全局 `build-output-throttle`:package `tsc --noEmit` PASS、TS UTF-8 directed test PASS、TurboModule schema combine/codegen PASS。
- [x] JDK-only Java guard test PASS:64 MiB accepted,0 与 64 MiB+1 rejected,33 CJK 字符 rejected。
- [x] `mbgl-test-runner` 实际 rebuild PASS;`ParticleFieldProfileContractJson.*`、`Source.RasterArrayV4HostApi*`、`ParticleFieldBinding.*`、`ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`、`ParticleEncodedFieldTransport.*`、`ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`、`Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*` 合并过滤 PASS。
- [x] Java declaration ↔ C++ JNI registration V4 name-set 静态核对、Kotlin/Java UTF-8 guard 静态核对、`git diff --check`:PASS。
## 未完成、阻塞与下一步
- Android Kotlin/Java/JNI 真实编译、Gradle、APK、ADB、设备验证按任务约束未执行;需要后续一次性 Android 验收。iOS/Metal 与 Jest 同样未执行。
- 无实现阻塞。本 Step 在 scoped commit/push 后立即停止,不进入 STEP-079。
## Git 收尾
- 实现 commit:`75a974e77503c56ec5bd2823ac1b689225ed80a7`(`fix: close STEP-078 V4 bridge guard audit`)。
- push:PASS,`73779b89b..75a974e77 particle/main -> particle/main`;remote branch 为 `origin/particle/main`。
- 本记录及完成报告的 Git 收尾补充将以独立、仅文档的 STEP-078 scoped commit 推送;其 Final HEAD 记录于该次文档收尾的提交输出,之后立即停止。

View File

@@ -0,0 +1,57 @@
# STEP-078 — QBIN2 V4 ColorBinding VELOCITY_COMPONENT 接口修订 v4
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD:`dd1d7014710819fe55857968558fcc9538f28d9c`。
- 开始 `origin/particle/main`:`dd1d7014710819fe55857968558fcc9538f28d9c`。
- 仅解除 STEP-079 已确认的 ColorBinding public API blocker:让 `FieldRuntime.setEncodedColorBinding(...)` 能表达既有 Core `VELOCITY_COMPONENT`;不进入 STEP-079。
- 任务开始时已有 STEP-062 两处 dirty、历史 untracked 与 build 日志;均未清理、改写、暂存或提交。
## 完成项
- [x] `EncodedColorBindingInput.expectedValueClass` 新增 `VELOCITY_COMPONENT`,TS public fail-fast validation 同步允许该值。
- [x] Kotlin `WeatherRuntimeBridgeModule` 将 `SCALAR` / `VELOCITY_COMPONENT` / `DIRECTION_ANGLE` 精确映射为 Core value class `0` / `1` / `2`。
- [x] JNI `setRasterArraySourceEncodedColorBinding(...)` 仅允许 `0..2`;`VALIDITY=3` 与其它非法值继续 fail-closed。
- [x] Core 回归证明以真实 `VelocityComponent` U-east plane 的 ColorBinding 可 resolve,并证明 `Validity` ColorBinding 返回 `InvalidBinding`。
- [x] TurboModule primitive string ABI 与 Java `WeatherRuntime.setEncodedColorBinding(...)` 的参数数量、顺序及 primitive 形状均未修改。
## 修改范围
- `packages/maplibre-particle-rn/src/types.ts`
- `packages/maplibre-particle-rn/src/FieldRuntime.ts`
- `packages/maplibre-particle-rn/android/src/main/java/org/navsea/maplibre/particle/weather/WeatherRuntimeBridgeModule.kt`
- `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp`
- `test/particle/field_binding.test.cpp`
- 本记录、完成报告与 `粒子开发记录.md`。
未修改 Renderer/GPU/shader、QBIN container/decoder、Store、ForecastClock、Projection identity、ParticleBinding、Test Host V4 或 NavSea。
## 验证
所有 noisy command 均在 `particle-codex` tmux session 内经 `build-output-throttle` 执行,日志位于 `/tmp/step078v4-build-logs`:
- [x] package `tsc --noEmit`。
- [x] TurboModule schema combine/codegen。
- [x] Linux OpenGL `mbgl-test-runner` rebuild。
- [x] `ParticleFieldBinding.*`。
- [x] `Source.RasterArrayV4HostApi*`。
- [x] `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`。
- [x] `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`。
- [x] `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`。
- [x] Java `nativeSetRasterArraySourceEncodedColorBinding` 6-parameter declaration、C++ method signature 和 registration 静态一致性核对。
- [x] task-scope `git diff --check`。
## 未完成、阻塞与下一步
- Android Kotlin/Java/JNI 真实编译、Gradle、APK、install、ADB、设备/模拟器验证均按任务约束未执行;Windows 11 Android Test Host 编译仍待用户手工验收。Jest、iOS/Metal 亦未执行。
- 无实现阻塞。完成 scoped Git 收尾后立即停止;STEP-079 由 ChatGPT 独立复验本修订后重启。
## Git 收尾
- implementation commit:`e02353820aafa1834ce3a2ab3afe9261fadd83bc`(`fix: expose V4 velocity component color binding`)。
- implementation push:PASS,`dd1d70147..e02353820 particle/main -> particle/main`;remote branch 为 `origin/particle/main`。
- implementation Final HEAD:`e02353820aafa1834ce3a2ab3afe9261fadd83bc`。本记录、完成报告和开发记录将以独立、仅文档 scoped commit 推送;完成后立即停止。

View File

@@ -0,0 +1,46 @@
# STEP-079 — 真实 QBIN Golden Fixture 接入 RN Test Host v1
状态:阻塞
日期:2026-09-13
## 目标
把 WeatherV2 冻结的 QBIN V2 Golden Fixture 通过 STEP-078 的 V4 public API 接入 `MapLibreParticleRNTestHost`;本 Step 不执行 Android Gradle、APK、安装或 ADB。
## 起始基线
- `HEAD`:`2ea8f03064b2379d5220ce67806e3543e9cba0a7`
- `origin/particle/main`:`2ea8f03064b2379d5220ce67806e3543e9cba0a7`
- 开始前存在 STEP-062 dirty、历史 untracked、`.codex-build-logs/`、Test Host generated Android/Expo/modules 与 package tgz;均未清理、暂存或提交。
## 已完成的只读核对
- [x] 核对冻结来源 `/root/sourceserver/weatherV2/_artifacts/MapLibre_QBIN_V2_单元测试数据_v1/`、说明、原始 manifest、profile/semantic/unit registry。
- [x] 核对 8 个 raw `.qbin` 的目标(Wind/Wave/Current/Rain,各 T0/T1)及 descriptor:Wind/Current U/V 都是 `VELOCITY_COMPONENT`,semantic `1/2`、group `1`、unit `1`。
- [x] 核对 STEP-078 V4 TypeScript、TurboModule/Kotlin bridge 与 Core ColorBinding resolver。
- [x] 确认不存在可在不改变冻结 public API 语义的前提下为 Wind/Current 提交正确 ColorBinding 的路径。
## 阻塞
Wind/Current 的任务合同要求选择 U 或 V 做 ColorBinding;其正确 `expectedValueClass` 是 `VELOCITY_COMPONENT`。然而冻结的 RN public API 在 `FieldRuntime.setEncodedColorBinding` 只接受 `SCALAR` 或 `DIRECTION_ANGLE`,Kotlin bridge 同样只映射这两者。将 U/V 伪装为 `SCALAR` 虽能通过 JS/Kotlin 参数白名单,但 Core resolver 会以 `PlaneClassMismatch` 拒绝真实 velocity plane。
因此无法同时满足“所有 dataset 至少有 ColorBinding”“所有 binding 仅通过冻结 V4 public API”“不得改变 STEP-078 public API 语义”。按任务的接口阻塞条款,未创建 Test Host wrapper、未修改 package/Core/Renderer,也未保留半成品 fixture 复制。
完整证据与解阻条件见 `docs/报告/STEP-079_真实QBIN_GoldenFixture接入RNTestHost_接口阻塞报告_v1.md`。
## 构建与验证
- 未执行 TypeScript/codegen/C++ build 或测试:在实现开始前已得到不可绕过的 public-API 合同矛盾,继续执行不会验证一个可交付实现。
- 未执行 Android Gradle、APK、install、ADB、模拟器、实体机或 iOS/Metal 操作。
- `git diff --check` 与 staged `git diff --cached --check`:在 `particle-codex` tmux session 执行,PASS。
## Git 收尾
- 阻塞记录提交:`afcc74066f1395678621fa2b1a70d10bc722a623`,`docs: record STEP-079 V4 fixture API blocker`。
- 已推送至 `origin/particle/main`;该提交只含本 Step 的 Step 记录、接口阻塞报告和开发记录,不含 fixture、Test Host 或既有 dirty/untracked 文件。
## 允许/禁止范围结果
- 未修改 Renderer/GPU/shader、Core/QBIN/Binding/Host API、Android bridge、RN package public API、NavSea、网络、下载或 gzip。
- 未进入下一 Step;等待具备明确授权的 STEP-078 API 补充/修订后重新开始本 Step。

View File

@@ -0,0 +1,49 @@
# STEP-079 — 真实 QBIN Golden Fixture 接入 RN Test Host v2
状态:完成(IMPLEMENTED / WAITING FOR WINDOWS 11 ANDROID VALIDATION)
日期:2026-09-13
## 目标与基线
- 开始 HEAD / `origin/particle/main`:`a83ab9cc94a14426913f971786eae6b13a6f1702`。
- 将 WeatherV2 冻结来源的 Wind、Wave、Current、Rain QBIN V2 原始 T0/T1 fixture 接入现有 RN Test Host 的 V4 public API;保留全部 V3 测试区。
- 开始前的 STEP-062 dirty、历史 untracked、`.codex-build-logs/`、Test Host generated `android/.expo/modules`、tgz 和旧任务书均未清理、暂存或提交。
## 完成项
- [x] 从 `/root/sourceserver/weatherV2/_artifacts/MapLibre_QBIN_V2_单元测试数据_v1/` 逐字节复制 8 个 raw `.qbin` 到 Test Host 自有 `assets/qbin-golden/`;运行时不读取该来源、不使用 HTTP/CDN/NAS/download/gzip。
- [x] 新增 V4 fixture contract:稳定 `fieldKey`、`fieldID`、generation/dataVersion、真实 validTime/midpoint、profile contract、source 相对路径、bytes 与 SHA-256。
- [x] Wind/Current 使用真实 U ColorBinding:semantic 1 / group 1 / `VELOCITY_COMPONENT` / unit 1,并使用 U/V `VECTOR_COMPONENTS` ParticleBinding;未伪装为 `SCALAR`。
- [x] Wave 使用 height ColorBinding(20/0/`SCALAR`/2)及 direction ParticleBinding(10/0/unit 4);Rain 使用 precipitation ColorBinding(30/0/`SCALAR`/5),Particle 明确 N/A。
- [x] 沿用 `expo-asset` 将 bundled asset materialize 为 fail-closed 的本地 `file://` URI,再调用 `FieldRuntime.submitEncodedField(...)`;Metro 仅追加 `qbin` asset extension。
- [x] 保留 V3 区域;新增 V4 dataset 选择、load、分开的 T0/T1 submit 结果、binding 结果、T0/midpoint/T1 seek、PLAY/PAUSE、正数 speed、clear selected/clear all 控制。load 固定顺序为 clear → binding → T0 → T1 → midpoint,T0/T1 非 `ACCEPTED_INSERTED` 会显示失败;midpoint 仅显示 command accepted,不宣称观测到 mix=0.5。
- [x] 新增开发期 fail-closed fixture verifier,逐个同时核验冻结 source 与 bundled copy 的 raw bytes/SHA-256。
## 修改范围
- 仅修改 `integration/react-native/MapLibreParticleRNTestHost/` 下的 V4 fixture、assets、materialize/UI、Metro、README 与 verifier,以及本 Step、完成报告、`粒子开发记录.md`。
- 未修改 Core、QBIN decoder、JNI、Android bridge、RN package public API、Renderer/GPU/shader、NavSea 或网络层。
## 验证
以下均在 `particle-codex` tmux session 内执行;所有 build/typecheck/codegen/test 经 `build-output-throttle`,日志位于 `/tmp/step079-build-logs`:
- [x] Test Host `npm --prefix integration/react-native/MapLibreParticleRNTestHost run typecheck`:PASS。
- [x] package `npx tsc --noEmit -p packages/maplibre-particle-rn/tsconfig.json`:PASS。
- [x] TurboModule schema combine 与 Android modules codegen:PASS。
- [x] `scripts/verify-qbin-golden-fixtures.py`:PASS(8 个 raw source/copy 文件 bytes + SHA-256)。
- [x] Linux OpenGL `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:PASS(当前无 Core 变更,target 已最新)。
- [x] `ParticleFieldProfileContractJson.*`、`ParticleEncodedFieldTransport.*`、`Source.RasterArrayV4HostApi*`、`ParticleFieldBinding.*`、`ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`、`ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`、`Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*` 合并过滤:PASS。
- [x] `git diff --check` 与 task-scope cached `git diff --check`:PASS。
## 未完成、无法验证与下一步
- Android Gradle/APK/install/ADB/模拟器/真机均未在 Linux 执行;Jest、iOS/Metal 也未执行。
- 无法在 Linux 证明 Android Test Host 实际读取 Expo bundled `.qbin`、Kotlin/JNI/Renderer consumer 接受 fixture,或观察 Android 画面/播放效果;这些必须由 Windows 11 用户手工验收。
- 无 implementation blocker。下一步仅为 Windows 11 Android 手工验收;本 Step 不进入 Renderer consumer、NavSea 正式接入或下一 Step。
## Git 收尾
- implementation commit:`f1ea88b0ce9c6995f05a8d2ae2eb2696d72e5f28`,`feat: add V4 QBIN golden fixture test host`。
- implementation Final HEAD:`f1ea88b0ce9c6995f05a8d2ae2eb2696d72e5f28`。本 Step/报告/开发记录使用独立且仅文档的 scoped commit 收尾并 push 到 `origin/particle/main`;该文档收尾后立即停止。

View File

@@ -0,0 +1,69 @@
# STEP-080 — Android AAR 与 RN Test Host 标准构建流水线 v1
> **历史版本。** 当前正常公网发布/Win11 验证流程以同目录 `STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md` 为准;v1 的本地 bundle/MavenLocal 流程仅作历史/离线参考。
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD / `origin/particle/main`:`d490578b973508d267c590dd604b033507c9b859`。
- 固定交付版本:RN package / Native AAR 均为 `13.4.1-particle.3`,Maven coordinate 为 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.3`。
- 任务开始前已有 Native `VERSION` 升级、JNI 头文件修正、STEP-062 文档 dirty、历史 untracked task/build/cache/tgz 和 Test Host generated Android/Expo/modules;均未清理、暂存或提交。
## 必做项
- [x] 新增 Linux producer `tools/particle_android_pipeline/linux_build_publish.sh`:报告 HEAD/branch/dirty;版本完全相等校验;SDK 解析;OpenGL Release 四 ABI 完整构建与 Maven Local publish;Maven artifact、旧 `.2` 保护、AAR/tgz bytes/SHA-256 校验;版本隔离 bundle/manifest。
- [x] Linux 所有 noisy native build、Gradle publish、RN TypeScript typecheck 与 npm pack 均强制经 build-output-throttle;runner 不可达即 fail-closed。
- [x] 默认 Linux 流程会生成可传输 AAR/POM/module/tgz 与 `artifacts/maplibre-particle/<version>/build-manifest.json`;目录已有 manifest 或不同 partial file 时 fail-closed,不覆盖历史 handoff。
- [x] 明确提供 `--reuse-existing`:仅能消费已完整存在的 Maven Local 发布物并在 manifest 标识 `PASS_REUSED_EXISTING_MAVEN_LOCAL`;不会冒充重新 AAR 构建。`--preflight` 无写入。
- [x] 新增 Windows consumer `tools/particle_android_pipeline/windows_build_testhost.ps1`:manifest 是唯一 artifact/version 输入,校验 SHA、安装 AAR/POM/module、同步 clean Test Host tracked source、固定 tgz 与 Maven coordinate、显式 prebuild/Debug APK build、输出 APK bytes/SHA-256。
- [x] Windows 默认不做安装、启动或 ADB;只有 `-Install` 与 `-Install -Run` 才使用固定设备 `192.168.1.146`。支持 `-DryRun`,先打印版本、AAR、tgz、Host、Maven Local 与 Gradle command。
- [x] 新增中文 README;版本升级必须先同步 `platform/android/VERSION` 与 RN `package.json`,pipeline 只校验,绝不暗改版本。
- [x] `.gitignore` 忽略 `/artifacts/maplibre-particle/`,生成 AAR/tgz/manifest 不进入 scoped commit。
## 允许修改
- `tools/particle_android_pipeline/` 的 Linux/Windows 脚本及说明。
- `.gitignore`、本 Step、完成报告与 `粒子开发记录.md`。
- 运行时写入 Git ignore 的 `local.properties`、`.codex-build-logs/`、`artifacts/`。
## 禁止修改
- Core、Renderer、QBIN、Binding、JNI 业务语义。
- Native/RN 版本值、既有 dirty/untracked、历史 Maven Local `.2` 发布物。
- Windows APK/ADB/设备、Jest、iOS/Metal;本 Step 不重复完整 Linux AAR 编译。
## 构建与验证
以下 Linux 验证均在 `particle-codex` tmux session 内完成:
- [x] `bash -n tools/particle_android_pipeline/linux_build_publish.sh`:PASS。
- [x] `linux_build_publish.sh --preflight`:PASS;SDK 解析为 `/root/Android/Sdk`,无 build/package/artifact 写入。
- [x] `linux_build_publish.sh --reuse-existing`:PASS;不运行完整 AAR build/publish,验证现存 `.3` Maven Local 的 AAR/POM/module,保护 `.2` 字节不变;经限流完成 RN package typecheck 与 npm pack。
- [x] manifest JSON contract:AAR/POM/module/tgz 的存在、绝对路径、bytes、SHA-256、版本和 Maven coordinate 全部 PASS。
- [x] tarball `package/package.json` version 与 Native `13.4.1-particle.3` 一致:PASS。
- [x] `git diff --check -- .gitignore`:PASS。
- [ ] Windows PowerShell parser/static validation:本 Linux 环境无 `pwsh`,明确 `POWERSHELL_PARSE_UNAVAILABLE_NO_PWSH`;未伪称 PASS。
## 当前结果
本轮 bundle 已在 Git ignore 路径生成:
```text
artifacts/maplibre-particle/13.4.1-particle.3/build-manifest.json
```
manifest 记录构建 HEAD、分支、dirty 状态、版本、四 ABI、OpenGL、Maven coordinate、AAR/POM/module/tgz 的 path/bytes/SHA-256 和 Linux validation result。验证使用的 existing AAR SHA-256 为 `9ef03eb0d0c03457adc04847cfa69fb67d85191daecd64d3b76db2ad8cc688cd`,tgz SHA-256 为 `6c8af6ff19adb4e25785b54bea3edd1dc7d0502d9158cf1c519d84b69d08ee55`。
## 未完成与阻塞
- 未重复执行完整四 ABI AAR 构建与 Maven Local publish:任务明确禁止为验证重复完整 AAR 编译;默认脚本已固化该受限命令,验证仅复用现存完整 `.3` publication。
- Linux 无 `pwsh`,因此未能本机解析 Windows PowerShell。Windows `-DryRun`、npm/prebuild/Gradle APK、安装、启动和设备验收均未执行,须在 Windows 11 使用该 manifest 完成;这不是 Linux 流水线实现阻塞。
- Jest、Windows APK/ADB/设备、iOS/Metal 均未执行。
## 下一步
Windows 11 将完整 Linux bundle 复制到本机后,先运行 `windows_build_testhost.ps1 -ManifestPath <build-manifest.json> -DryRun`,核对路径与 SHA;再无设备开关地运行同一脚本完成 Debug APK。只有用户明确选择时才附加 `-Install` 或 `-Run`。

View File

@@ -0,0 +1,535 @@
# STEP-080 — Android AAR 与 RN Test Host 标准构建流水线 v2
状态:已按真实 Linux → pbf → Windows 11 → Android 模拟器链路验证通过
日期:2026-09-14
## 1. 目的
本文固定 MapLibreParticleLab Android Native AAR 完成后,到 Windows 11 构建 RN Test Host、启动 Metro、安装 App、执行 QBIN V4 验证的标准流程。
以后正常验证不得再临时复制 AAR、临时改 MavenLocal、在 Windows 重编 Native AAR,或凭经验猜 Metro 端口。
## 2. 最终权威链路
```text
Linux MapLibreParticleLab
→ 编译 Native OpenGL AAR
→ 发布到 /mnt/nas/pbf/maven
→ 公网 https://pbf.jpcn.ccwu.cc/maven
Linux RN package
→ npm pack
→ 发布到 /mnt/nas/pbf/npm
→ 公网 https://pbf.jpcn.ccwu.cc/npm
Windows 11 RN Test Host
→ npm install 公网 tgz
→ Gradle 从公网 Maven 自动取得 Native AAR
→ Expo prebuild
→ assembleDebug
→ Metro :8091
→ adb reverse 8081 → 8091
→ 安装并启动 App
→ QBIN V4 真机/模拟器验证
```
## 3. Linux 与 Windows 的职责边界
### Linux 负责
- MapLibre Native Android OpenGL AAR 编译。
- Native AAR 版本管理。
- RN package 打包。
- AAR/POM/module 与 RN tgz 发布到 pbf。
- 公网回读并校验 SHA-256。
### Windows 11 负责
- 从 pbf npm 安装 RN package。
- 通过 Gradle 从 pbf Maven 消费对应 Native AAR。
- Expo prebuild。
- Test Host Debug APK 编译。
- Metro、ADB reverse、安装、启动和人工验证。
### 禁止
- Windows 正常流程中重新编译 MapLibre Native AAR。
- Windows 正常流程中使用 Maven Local 替代 pbf Maven。
- Linux 与 Windows 之间通过局域网临时复制 AAR 作为正式验证路径。
- 覆盖旧 Maven 版本坐标。
## 4. 版本一致性硬门槛
以当前已验证版本为例:
```text
RN package = 13.4.1-particle.3
Native AAR = 13.4.1-particle.3
```
以下位置必须全部一致,否则停止构建:
1. `platform/android/VERSION`
2. `packages/maplibre-particle-rn/package.json`
3. `packages/maplibre-particle-rn/android/build.gradle`
4. `packages/maplibre-particle-rn/app.plugin.js`
5. `integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js`
## 5. Linux:AAR 构建与发布
推荐使用仓库内标准脚本:
```bash
cd /mnt/sda1/MapLibreParticleLab
tools/particle_android_pipeline/linux_build_publish.sh
```
如果只是检查版本、SDK、路径和发布条件:
```bash
tools/particle_android_pipeline/linux_build_publish.sh --preflight
```
Linux Android SDK 当前实际路径:
```text
/root/Android/Sdk
```
AAR 必须使用新的版本目录发布,不能覆盖历史 `.2`:
```text
/mnt/nas/pbf/maven/cc/navsea/maplibre/android-sdk-particle-opengl/<version>/
```
当前 `.3` 目录为:
```text
/mnt/nas/pbf/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.4.1-particle.3/
```
至少包含:
```text
android-sdk-particle-opengl-13.4.1-particle.3.aar
android-sdk-particle-opengl-13.4.1-particle.3.pom
android-sdk-particle-opengl-13.4.1-particle.3.module
```
RN package 使用 `npm pack` 生成,并发布到:
```text
/mnt/nas/pbf/npm/navsea-maplibre-particle-rn-<version>.tgz
```
## 6. Linux:把 AAR 正式发布到公网 pbf
`https://pbf.jpcn.ccwu.cc/` 的服务器文件根目录固定映射为:
```text
/mnt/nas/pbf
```
因此 Maven 公网根:
```text
https://pbf.jpcn.ccwu.cc/maven/
```
对应 Linux:
```text
/mnt/nas/pbf/maven/
```
npm/tgz 公网根:
```text
https://pbf.jpcn.ccwu.cc/npm/
```
对应 Linux:
```text
/mnt/nas/pbf/npm/
```
发布不需要 SSH 到其他服务器,也不需要从 Win11 上传;Linux 直接写 `/mnt/nas/pbf` 即完成公网发布。
### 6.1 发布 Native AAR
先定义版本:
```bash
VERSION=13.4.1-particle.3
GROUP_PATH=cc/navsea/maplibre/android-sdk-particle-opengl
SRC="$HOME/.m2/repository/$GROUP_PATH/$VERSION"
DST="/mnt/nas/pbf/maven/$GROUP_PATH/$VERSION"
```
正常发布必须使用一个从未发布过的新版本。先保护旧版本:
```bash
test ! -e "$DST" || {
echo "目标版本已经存在,禁止覆盖:$DST" >&2
exit 1
}
```
确认本地 Maven publication 完整:
```bash
ls -lh "$SRC"
sha256sum "$SRC/android-sdk-particle-opengl-$VERSION.aar"
```
创建公网 Maven 版本目录并复制:
```bash
mkdir -p "$DST"
cp -a "$SRC"/. "$DST"/
```
至少确认:
```bash
test -f "$DST/android-sdk-particle-opengl-$VERSION.aar"
test -f "$DST/android-sdk-particle-opengl-$VERSION.pom"
test -f "$DST/android-sdk-particle-opengl-$VERSION.module"
```
### 6.2 公网回读验证 AAR
公网 URL 规则:
```text
https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/<VERSION>/android-sdk-particle-opengl-<VERSION>.aar
```
发布后必须从公网重新下载,不允许只检查 `/mnt/nas/pbf` 本地文件:
```bash
LOCAL_AAR="$DST/android-sdk-particle-opengl-$VERSION.aar"
PUBLIC_AAR="https://pbf.jpcn.ccwu.cc/maven/$GROUP_PATH/$VERSION/android-sdk-particle-opengl-$VERSION.aar"
TMP_AAR="$(mktemp)"
curl -fL "${PUBLIC_AAR}?nocache=$(date +%s)" -o "$TMP_AAR"
sha256sum "$LOCAL_AAR" "$TMP_AAR"
cmp -s "$LOCAL_AAR" "$TMP_AAR"
```
`cmp` 必须返回 0。只有公网回读字节完全一致,才算 AAR 发布完成。
同时可验证 POM/module HTTP 200:
```bash
curl -fI "https://pbf.jpcn.ccwu.cc/maven/$GROUP_PATH/$VERSION/android-sdk-particle-opengl-$VERSION.pom"
curl -fI "https://pbf.jpcn.ccwu.cc/maven/$GROUP_PATH/$VERSION/android-sdk-particle-opengl-$VERSION.module"
```
### 6.3 发布 RN package 到公网 npm 目录
在仓库中生成 tgz:
```bash
cd /mnt/sda1/MapLibreParticleLab/packages/maplibre-particle-rn
npm pack
```
生成文件示例:
```text
navsea-maplibre-particle-rn-13.4.1-particle.3.tgz
```
正式发布路径:
```bash
TGZ="navsea-maplibre-particle-rn-$VERSION.tgz"
PUBLIC_TGZ_DIR="/mnt/nas/pbf/npm"
test ! -e "$PUBLIC_TGZ_DIR/$TGZ" || {
echo "公网同版本 tgz 已存在,禁止覆盖;请升级版本" >&2
exit 1
}
cp "$TGZ" "$PUBLIC_TGZ_DIR/$TGZ"
```
公网 URL:
```text
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-<VERSION>.tgz
```
发布后必须公网回读并比较 SHA:
```bash
TMP_TGZ="$(mktemp)"
curl -fL "https://pbf.jpcn.ccwu.cc/npm/$TGZ?nocache=$(date +%s)" -o "$TMP_TGZ"
sha256sum "$TGZ" "$TMP_TGZ"
cmp -s "$TGZ" "$TMP_TGZ"
```
正常发布不得重新覆盖同一版本的 tgz。今天 `.3` 的覆盖属于修复历史错误包的例外,不应成为日常流程。
## 7. Windows 11:从公网取得 RN package 与 AAR
Windows 正常流程不直接下载 AAR 文件。AAR 由 Gradle 根据 RN package 中的 Maven coordinate 自动从 pbf Maven 获取。
当前 Test Host 建议工作目录:
```text
D:\React\MapLibreParticleRNTestHost-V4
```
不要在 `V:\` 盘符根目录执行 Expo prebuild;本次实测 Expo autolinking 在盘符根目录会出现 `Couldn't find package.json up from path V:\`。
### 7.1 安装 RN package
```powershell
cd D:\React\MapLibreParticleRNTestHost-V4
npm install --force `
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.3.tgz
```
正常情况下新版本不需要清 npm cache;只有确认同版本曾被错误覆盖时才做:
```powershell
npm cache clean --force
```
安装后必须核对实际依赖:
```powershell
Get-Content node_modules\@navsea\maplibre-particle-rn\package.json
Select-String `
-Path node_modules\@navsea\maplibre-particle-rn\android\build.gradle `
-Pattern 'android-sdk-particle-opengl'
```
必须看到目标版本,例如:
```text
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.3
```
### 7.2 Expo prebuild 与 Android SDK
执行:
```powershell
cd D:\React\MapLibreParticleRNTestHost-V4
npx expo prebuild --platform android
```
Windows Android SDK 当前实际路径:
```text
C:\Users\taibe\AppData\Local\Android\Sdk
```
如果 Gradle 报 `SDK location not found`,优先在当前 PowerShell 显式设置:
```powershell
$env:ANDROID_HOME='C:\Users\taibe\AppData\Local\Android\Sdk'
$env:ANDROID_SDK_ROOT='C:\Users\taibe\AppData\Local\Android\Sdk'
```
并写入:
```text
D:\React\MapLibreParticleRNTestHost-V4\android\local.properties
```
内容:
```properties
sdk.dir=C\:\\Users\\taibe\\AppData\\Local\\Android\\Sdk
```
### 7.3 构建 Debug APK
```powershell
cd D:\React\MapLibreParticleRNTestHost-V4\android
$env:ANDROID_HOME='C:\Users\taibe\AppData\Local\Android\Sdk'
$env:ANDROID_SDK_ROOT=$env:ANDROID_HOME
.\gradlew.bat :app:assembleDebug
```
成功产物:
```text
D:\React\MapLibreParticleRNTestHost-V4\android\app\build\outputs\apk\debug\app-debug.apk
```
## 8. Metro、ADB reverse、安装与启动
Test Host 的固定规则是:
```text
Metro 实际监听端口:8091
App 默认访问端口:8081
ADB reverse:tcp:8081 → tcp:8091
```
### 8.1 启动 Metro
```powershell
cd D:\React\MapLibreParticleRNTestHost-V4
npx expo start --port 8091 --clear
```
### 8.2 配置 reverse
另开一个 PowerShell:
```powershell
adb -s 192.168.1.146 reverse --remove-all
adb -s 192.168.1.146 reverse tcp:8081 tcp:8091
adb -s 192.168.1.146 reverse --list
```
必须看到:
```text
tcp:8081 tcp:8091
```
### 8.3 安装并启动
```powershell
adb -s 192.168.1.146 install -r `
D:\React\MapLibreParticleRNTestHost-V4\android\app\build\outputs\apk\debug\app-debug.apk
adb -s 192.168.1.146 shell am force-stop org.maplibre.particle.rntesthost
adb -s 192.168.1.146 shell am start `
-n org.maplibre.particle.rntesthost/.MainActivity
```
## 9. V3 fixture 与 Metro watchFolders
当前 Test Host 同时保留旧 V3 数据集和 V4 QBIN 数据集。
独立 Windows Host 使用:
```text
D:\React\MapLibreParticleRNTestHost-V4
D:\React\particle-v3-fields
```
`src/fieldDatasets.ts` 使用 `../../particle-v3-fields/...`,因此这些 fixture 位于 Metro 项目根之外。
此时 `metro.config.js` 必须把外部 fixture 目录加入 `watchFolders`,例如独立 Windows Host:
```js
const path = require("path");
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname);
config.resolver.assetExts = [...new Set([...config.resolver.assetExts, "f32", "u8", "qbin"])];
config.watchFolders = [
path.resolve(__dirname, "..", "particle-v3-fields"),
];
module.exports = config;
```
修改 `metro.config.js` 后必须重启 Metro,普通 Reload 不足以保证配置重载。
如果看到:
```text
Unable to resolve ../../particle-v3-fields/...
```
先确认文件实际存在,再检查 `watchFolders`;不要重复复制同一批 fixture 或误判为 QBIN 问题。
## 10. QBIN V4 验收确认
V4 测试区必须走:
```text
assets/qbin-golden/*.qbin
→ FieldRuntime.submitEncodedField(...)
→ WeatherRuntimeBridgeModule.submitEncodedField(...)
→ WeatherRuntime.submitEncodedField(...)
→ JNI native_map_view.cpp
→ RasterArraySource::submitEncodedField(...)
→ QbinV2Decoder::decode(...)
```
这与旧 V3 `metadata.json + .f32 + .u8 → submitField(...)` 是两条不同链路。
至少验证:
- V4 Wind:T0/T1 load、Color Binding、Particle Binding、T0/midpoint/T1 seek。
- V4 Wave:同上。
- V4 Current:同上。
- V4 Rain:Color Binding、T0/midpoint/T1;Particle Binding 为 N/A。
- PLAY / PAUSE / playback speed。
- `clearEncodedField` / `clearAllEncodedFields`。
人工确认 V4 功能正常后,才可判定 Windows Android QBIN V4 验证通过。
## 11. 本次 `.3` 已验证发布基线
Native AAR:
```text
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.3
SHA-256: 9ef03eb0d0c03457adc04847cfa69fb67d85191daecd64d3b76db2ad8cc688cd
```
最终修正后的 RN tgz:
```text
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.3.tgz
SHA-256: abdd5b5f313b6dc2bc2f2b00848d1901be72fed6a6e1fa15899efb10df9f923f
```
Windows 11 Debug APK 已于 2026-09-14 成功构建、安装并由用户完成功能验证。
## 12. 本次实际踩过的弯路与固定处理
### A. Win11 不要重编 Native AAR
症状:为了验证 Test Host,又在 Windows 尝试重建 MapLibre Native。
固定规则:Native AAR 只在 Linux 生产;Windows 只通过 pbf Maven 消费。
### B. 不要用局域网复制 AAR 作为正常路径
固定规则:Linux 发布到 `/mnt/nas/pbf/maven`,Windows Gradle 从 `https://pbf.jpcn.ccwu.cc/maven` 获取。
### C. RN package 版本号相同但内部仍依赖旧 AAR
必须检查 `android/build.gradle`、`app.plugin.js`、Test Host plugin 的 Maven coordinate,不能只看 `package.json` 版本号。
### D. Android Kotlin 必须实编译
Linux TypeScript/Core 检查无法替代 Android Kotlin 编译。本次实际发现 `value >= if (...)` 在 Kotlin 编译器下失败,最终改为显式括号与 `Double` 常量。
### E. Expo 不要在 `V:\` 根目录 prebuild
本次实际出现 `Couldn't find package.json up from path V:\`。固定使用普通子目录,例如 `D:\React\MapLibreParticleRNTestHost-V4`。
### F. App 报 localhost:8081,而 Metro 在 8091
这是正常设计,不要把 Metro 改回 8081。正确映射是:
```text
adb reverse tcp:8081 tcp:8091
```
### G. fixture 文件存在但 Metro 仍 Unable to resolve
优先检查 `watchFolders`,不要重复复制 fixture。仓库 Test Host 的 `metro.config.js` 已加入仓库布局/独立 Windows 布局自动探测。
## 13. 文档优先级
本文件 `v2` 覆盖 STEP-080 v1 中“Linux bundle → Windows MavenLocal”的正常发布结论。
`tools/particle_android_pipeline/` 中旧脚本保留用于历史/离线用途;正常联网验证以本文的 pbf 公网链路为准。

View File

@@ -0,0 +1,64 @@
# STEP-081 iOS Metal Renderer 与真机 Smoke 入口 v1
状态:CLOSED / PASS
## 目标
为既有 C++ RasterArray/QBIN V4 → WeatherColor + RasterParticle path 增加 Metal shader registry 接线,并在 iOS Test App 中提供 Wind Golden QBIN 的真机 smoke 入口。
## 本轮结果
- [x] 新增 Metal `ParticleGpuValidationShader` source、属性/纹理表和 registry 注册。
- [x] 保持 `ParticleGpuValidationUBO` 为唯一 shared UBO,MSL 静态契约为 `31 * 16`。
- [x] 在 Test App Runtime Styling 菜单加入 T0/midpoint/T1/clear private smoke seam。
- [x] CMake 将既有 Wind Golden T0/T1 复制到 app bundle。
- [x] 仅做静态检查;未执行 Jest。
- [x] iPhoneOS arm64 Test App 无签名编译通过,`BUILD SUCCEEDED`。
- [x] Pleiades(iPad mini 5 / iPadOS 26.5.2)完成签名安装、真实 Metal runtime 与视觉验收。
- [x] Deterministic Uniform East 向右、Uniform North 向上、Vortex 可见并具有连续轨迹。
- [x] QBIN T0 / MID / T1 / Clear / Clear 后重新 T0 均通过。
## 允许范围与限制
修改仅涉及 Metal thin-hook、iOS Test App、CMake Metal/app 资源接线以及本 Step 文档。未修改 QBIN V4/Decoder/Profile/Binding、Android/RN API、版本坐标或项目总则。现有 gfx 代码使用 `gfx::RenderTarget`、`Texture2D`、`Lines` 与 shader registry;静态审查未见 layer 内的直接 OpenGL 调用。
## 真机验收入口
在 iOS Test App 中,`Particle Core Validation (Fixtures)` 用 Uniform East / Uniform North / Vortex 验证正式 Host ingress → RasterParticleLayer → Metal 路径;`Particle / QBIN V4 Smoke (T0)` 用 T0 / midpoint / T1 / Clear 验证真实 Wind Golden QBIN。QBIN T0/MID/T1 对应 1789239600000 / 1789241400000 / 1789243200000。
## 构建与阻塞
`Codex` 未执行任何编译;GPT 审查后由 RDC 完成 CMake/Xcode iPhoneOS arm64 build。最终签名 device build `BUILD SUCCEEDED`,Bundle ID `jp.eyaku.maplibreparticlelab.step081`,Wind T0/T1 QBIN 均进入 `ParticleSmokeFixtures/`。随后在 Pleiades 真机完成安装、启动、Metal shader runtime 与视觉 smoke。
## GPT / RDC 独立审查(2026-09-17)
Codex 结束后由 RDC 读取完整 diff、设计文档和完成报告,GPT 独立审查通过;编译仍未执行。审查中做了 3 个小范围、事实明确的修正:
- State A/B RenderTarget 明确为 `TextureChannelDataType::Float`,Metal fragment 输出由 `half4` 改为 `float4`,避免 Web Mercator 粒子状态在 shader 输出阶段降为半精度。
- Metal `resolveAdvection()` 的 VELOCITY 零向量行为与 OpenGL fragment fail-closed 对齐:长度 `<= 0.00001` 返回 false。
- Smoke 选择器由 iPad 需要 popover anchor 的 ActionSheet 改为 Alert,保证 iPhone/iPad 真机都可直接进入。
`git diff --check` 通过;未发现 QBIN V4/Core contract、Android/RN API 或版本坐标越界修改。下一步允许 RDC 执行 iPhoneOS device build;仍禁止 Simulator。
## RDC 编译验证(2026-09-17)
- `cmake --preset ios-metal` 配置成功,Xcode 26.3 / iPhoneOS 26.2 / AppleClang 17。
- Darwin style codegen 需要 Bazel;按仓库 `.bazelversion` 使用 Bazel 8.6.0 后通过。
- 首次签名构建被 `org.maplibre.app` provisioning 阻塞,因此改用 `CODE_SIGNING_ALLOWED=NO` 做 device-architecture 编译验证;未使用 Simulator。
- 编译中只对 Test App 做小范围修正:使用 `${CMAKE_SOURCE_DIR}/src` 暴露 private smoke 所需内部头;`weakSelf` 改为显式 `MBXViewController *`;fixture path 使用 `NSString *`。
- 最终 `app` target `BUILD SUCCEEDED`;`particle_gpu_validation.cpp` 和 `MBXViewController.mm` 均通过 arm64 编译。
- 当前登记 iPhone/iPad 均为 `unavailable`,因此状态进入 `WAITING_DEVICE_SMOKE`,不能声明真机/Metal runtime PASS。
## RDC 真机收口(2026-09-18)
- 设备:Pleiades,iPad mini (5th generation) / iPad11,2,iPadOS 26.5.2;使用 iPhoneOS device build,未使用 Simulator。
- 确认 Metal render-pipeline cache key 缺少 attachment pixel format:State RGBA32Float / Trail RGBA8Unorm / 主 renderable BGRA8Unorm 之间可能错误复用 pipeline。`src/mbgl/shaders/mtl/shader_program.cpp` 已把 color/depth/stencil format 合入 cache key。
- Trail target 强制写测试在修复前全零、修复后 RGBA8 有效写入,证明 cache-key 根因。
- State GPU 与 CPU/视觉验证确认 canonical U/V、T0/T1、State ping-pong、vertex-stage texture sampling 和 z0 matrix 投影正确。
- 实测正式 previous→current 线段平均约 0.065 px、最大约 0.24 px,全部 `<0.5 px`;Metal line primitive 无稳定覆盖。保留真实 line,同时增加 Metal-only 1 px current-endpoint quad coverage,不改变物理位移。
- Deterministic:Uniform East 向东/右,Uniform North 向北/上,Vortex 粒子可见、移动连续、有轨迹感。
- QBIN Golden fixture:grid 513×769,bounds=112.5E..157.5E / 0..55.776573N;Smoke 相机定位约 27.8883N / 135E、zoom 3 后,T0 / MID / T1 / Clear / Clear 后重进全部人工确认正常。
- 中间 `STEP081_*` GPU readback / fixed-line / point fallback 诊断均已删除;错误的 Point coverage 曾导致整屏绿色,最终未保留。
- `git diff --check` 在最终清理后通过;无关 `MBXAppDelegate.m` 与 vendor/submodule dirty 不纳入 STEP-081 commit。
- 功能与真机验收实现已提交并成功推送:`f1c0f92b2b`(`fix: close STEP-081 iOS Metal particle rendering`),`origin/particle/main` 已同步。

View File

@@ -0,0 +1,40 @@
# STEP-082 iOS Native FieldRuntime Facade 与 XCFramework 发行基线 v1
状态:CLOSED / PASS
## 目标与基线
起始分支 `particle/main`,起始 HEAD `3e03949d3411c830cfff4bf320cd350f5b6a7527`。本 Step 只新增 iOS public V4 facade 和 XCFramework distribution 基线;不重做 STEP-081,也不进入 STEP-083/RN。
## 执行清单
- [x] iOS public Native FieldRuntime facade。
- [x] MapView-owned replace/current/dispose 生命周期。
- [x] V4 submit、binding、forecast、clear 转发。
- [x] style reload/source missing fail-closed 与 public header 无 Core/STL 泄漏。
- [x] XCFramework public header 构建图接线。
- [x] distribution-ready 模板和中文设计文档。
- [x] macOS XCFramework 成功构建、Headers/symbol/zip SHA-256 验证。
- [x] `git diff --check` 已通过;本次文档收尾后的 scoped closeout commit/push 由宿主执行并回填最终 hash。
## 允许与禁止
允许修改 iOS public header/implementation、Bazel public export、release 模板以及指定中文文档。禁止改 QBIN V2、FieldProfile、BindingProjection、renderer/Metal physics、STEP-081 smoke、Android/RN/NavSea;本 Step 未修改这些范围。
## 最终构建记录与关闭依据
宿主已在真实实现提交 `136ed726cf749c24b3673c046581a38ef48be390`(`fix: close ios xcframework packaging blockers`)上完成阻塞修复和 macOS 验证。`bazel build //:generated_style_code` PASS;`bazel build //platform/ios:MapLibre.dynamic` PASS,最终日志 `/tmp/step082-host-maplibre-dynamic-final2.log` 含 `Build completed successfully`。
产物为 `bazel-bin/platform/ios/MapLibre.dynamic.xcframework.zip`,大小 `24639200` bytes,SHA-256 为 `64faa174c5c0dc0a2da3e289f0beaf2dedf7298665e3345f39836729b668e239`。`ios-arm64` 与 `ios-arm64_x86_64-simulator` slice 均导出 `Headers/MLNParticleFieldRuntime.h`,两份 `Mapbox.h` 均 import `MLNParticleFieldRuntime.h`;`MLNMapView_Private.h` 的 public export count 为 0。两 slice binary 均存在 `_OBJC_CLASS_$_MLNParticleFieldRuntime` 和 `_OBJC_CLASS_$_MLNParticleFieldRuntimeConfiguration`;device 架构为 arm64,simulator 架构为 x86_64+arm64。
本次仅完成工作树文档收尾;Git closeout commit/push 将由宿主在本文件和 v2 完成报告生成后执行,最终 hash 以宿主回填/最终回复为准。
## 下一步
STEP-082 已关闭。远端发布仍未执行,manifest 的 artifact URL 保持 `LOCAL_BUILD`;后续发布必须另行上传并以发布后的 URL/校验结果更新 release 元数据。STEP-083 不因本记录自动开始。
## STEP-083 post-close correction(2026-09-18)
本段追加纠正,不改写上方当时的历史记录。后续真实 RN device 验证确认,上方使用裸 `bazel build //platform/ios:MapLibre.dynamic` 取得的默认 OpenGL artifact(`24639200` bytes,SHA-256=`64faa174c5c0dc0a2da3e289f0beaf2dedf7298665e3345f39836729b668e239`,source commit `136ed726…`)只有色场、没有 Particle render,故作废且不再作为 iOS Particle 发行基线。
iOS Particle 发行固定改用 `bazel build --//:renderer=metal //platform/ios:MapLibre.dynamic`。Metal XCFramework 为 `24943196` bytes,SHA-256=`ae88975fc20f4d9961b95079638d0e9c0d8e61d4394d5c0a8ffcc48d173a827f`,source commit=`bdd7f40f9029031816c9bf4db6e1cd6c7c69b7bd`。其 device binary 含 `MLNMapView`、Metal/`MTLDevice`/`ParticleGPU` 标记且无 OpenGL marker;Pleiades iPad mini 5 RN Test Host signed Release 安装后,用户已确认粒子恢复。artifact 仍为本地构建,URL 保持 `LOCAL_BUILD`。

View File

@@ -0,0 +1,38 @@
# STEP-083 iOS RN 统一 FieldRuntime / TurboModule / Fabric 与 XCFramework 消费 v1
状态:`CLOSED / PASS`
## 基线与范围
- branch:`particle/main`;开始 HEAD:`bdd7f40f9029031816c9bf4db6e1cd6c7c69b7bd`。
- STEP-082 public facade 与 `//platform/ios:MapLibre.dynamic` Bazel build 已由 RDC 真实验证 PASS。
- 本批次仅增加 RN package iOS bridge/pod/codegen/autolinking、Test Host iOS identity 和记录;没有修改 Android、Shared Core、QBIN contract、renderer、NavSea 或 frozen TS public API。
## 已实现
- `WeatherRuntimeBridge` 是 RN 0.85.2 codegen TurboModule:`NativeWeatherRuntimeBridgeSpecJSI`、Promise `resolve/reject` ABI 及 package `ios.modulesProvider` 均使用当前本地 RN 0.85.2 source 的真实 pattern。
- 每个 Map command 先校验正整数 `reactTag`,再通过 RN 注入的当前 Fabric/UIManager `viewRegistry_DEPRECATED` 进入 UI block,重新确认 `MLRNMapViewComponentView` → `contentView` → `MLRNMapView` → public `MLNMapView`。不缓存 tag、map、Core pointer 或建立第二 registry。
- V3、V4、runtime state、weather/particle visibility、timeline、binding 与所有视觉命令均映射到现有 public `MLNParticleFieldRuntime`。V3 `clearWindFields` 的 Android 语义本身等价 `clearFields`。
- V3/QBIN file ingress 均只接收 absolute path 或 `file://`,要求 readable regular file、1..64 MiB,并比较读前后 length;metadata、contract、UTF-8 identifier、safe integer 与 inactive `(0,0)` selector 同 Android guard。
- pod 只 dependency `MapLibreReactNative 11.3.6`,不声明 MapLibre pod/framework/SPM product。MLRN 11.3.6 的真实 `$MLRN_SPM_SPEC` 将唯一 `MapLibre` product 注入 Pods 和 app target。
## RDC 实际 iOS 验证(2026-09-18)
- RDC 已实际执行 npm dependency install(`npm ci`)、typecheck、RN codegen、Expo prebuild、Pod integration、device arm64 build、signed Release build 与安装。MapLibre.framework 签名成功。
- Test Host 消费的是 single MapLibre SPM product;未发现第二份 MapLibre binary。新 XCFramework 必须以 Metal 命令 `bazel build --//:renderer=metal //platform/ios:MapLibre.dynamic` 生成。默认裸 Bazel 命令的 OpenGL artifact 只有色场、无粒子,已由 STEP-082 post-close correction 作废。
- Pleiades iPad mini 5 上 signed Release 安装运行。一次 V4 Wind load 实际观察到 `clearAllEncodedFields` → color binding → particle binding → T0 `ACCEPTED_INSERTED` → T1 `ACCEPTED_INSERTED` → midpoint seek;用户确认粒子恢复。
## 已执行检查
- `ruby -c packages/maplibre-particle-rn/MapLibreParticleRN.podspec`:成功。
- `npm ci`、typecheck、RN codegen、Expo prebuild、Pod integration、device arm64 build、signed Release build/installation:均由 RDC 实际执行;Release 日志为 `BUILD SUCCEEDED`。
- `npm pack --dry-run --json --cache /private/tmp/step083-npm-cache`:成功,tgz 清单包含 podspec、`ios/WeatherRuntimeBridge.h/.mm`、既有 Android 与 `src`。
- package `codegenConfig.ios.modulesProvider.WeatherRuntimeBridge` 静态检查和 Native Spec 34 个方法与 ObjC++ 34 个实现逐项计数完成。
## Final control regression 真机关闭
`integration/react-native/MapLibreParticleRNTestHost/App.tsx` 新增唯一按钮 `RUN V4 FINAL CONTROL REGRESSION`,仅在当前 `reactTag` 有效时执行,且运行中禁用以避免重复并发。它复用 V4 加载的 `prepareV4` 公共 API 路径:clearAll → color binding →(如有)particle binding → T0/T1 `ACCEPTED_INSERTED` → midpoint seek。
Pleiades iPad mini 5 真机已由用户用该单按钮完成最终控制序列:`prepare#1 → PLAY → 约500ms → PAUSE → clear selected → prepare#2 → clear all → detach → attach → prepare#3 → refresh`。用户明确反馈“FINAL CONTROL REGRESSION PASS”;最后一次 prepare/refresh 后粒子恢复并保持可见。此前同一真机的 V4 Wind 验收中,用户也已确认“粒子恢复了”。
上述结果关闭此前唯一未完成的逐控制面真机验收项;STEP-083 正式 `CLOSED / PASS`。回归按钮保留为后续入口,未改动其逻辑。

View File

@@ -0,0 +1,50 @@
# STEP-084 正式粒子速度着色默认关闭与红头修复 v1
状态:`完成(等待 STEP-085 后由 RDC 统一执行 Native/真机验收)`
## 1. 目标
修复 NavSea 天气正式粒子在 Android/iOS 均出现橙红色“头部”的问题。正式 `RasterParticleLayer` 默认应严格使用宿主显式提供的 `particleColor`;速度着色只作为显式 opt-in 能力保留,不再作为正式粒子的默认行为。
## 2. 基线
- 机器:Mac-mini-tei.lan。
- 工作仓库:`/Users/tei/dev/MapLibreParticleLab-step084`,独立 worktree,避免触碰主 Mac 工作树既有 dirty/untracked。
- branch:`step/084-particle-speed-color-default-off`。
- start commit:`91d4859121afef98e05efac923797b31835a49df`(tag `maplibre-particle/rn/13.4.1-particle.7`)。
- Integration repo 已同步并核对 `main`;本修正不改变 RN 公共 API、QBIN/Field contract 或 NavSea/Particle 责任边界。
## 3. 根因
正式 fragment/Metal shader 在 `speedColorEnabled=true` 时,会对当前粒子段覆盖 `particleColor`,使用内置速度色带;高速端为橙红色。`RasterParticleLayer::Impl` 当前默认值为 `true`,因此 Host 未显式设置该属性时,Android OpenGL 与 iOS Metal 都会出现同样的红头。
## 4. 必做项
- [x] 将正式 `RasterParticleLayer` 的 `speedColorEnabled` 默认值改为 `false`。
- [x] 将 `ParticleGpuResources` 初始默认值同步为 `false`,避免资源首次创建后、视觉属性同步前出现单帧速度着色。
- [x] 保留 shader 中速度着色逻辑和显式 setter,不删除 Validation/Test 能力。
- [x] 更新 `Layer.RasterParticleVisualProperties` 回归测试:默认必须为 OFF,显式打开后仍可为 ON,再可关闭。
- [x] 默认 OFF → 显式 ON → 再 OFF 的 Core Layer 回归语义已由定向测试源码断言覆盖;未运行测试二进制。
- [x] `ParticleGpuResources` 初始值、RenderLayer visual-property 同步和 shader UBO 开关的静态调用链已复核。
- [x] `git diff --check` 通过。
- [x] scoped commit 并 push 到 `origin/particle/main`(commit 与远端确认见本轮最终交付)。
- [ ] Mac Metal 精确编译、Android Native/AAR 编译与 Android/iOS 设备验收:`BUILD_NOT_RUN_BY_CODEX — WAITING_FOR_RDC`。任务书要求与 STEP-085 性能修复合并验收,故本 Step 不单独声称平台 PASS。
## 5. 禁止范围
- 不修改 QBIN V4、FieldProfile、Runtime Window、时间插值或天气数据。
- 不增加 iOS/Android 专用公共协议,不增加 RN 平台分叉。
- 不修改 NavSea。
- 不删除 speed-color 能力;只改变正式层默认值。
- 不执行 Android Gradle/AAR/APK 编译、安装或 ADB 操作;Android 构建仍由用户手工执行。
## 6. 验收语义
默认情况下 `current ? particleColor : trailColor` 选择完成后,不再进入 `particleSpeedColor(...)` 覆盖分支;只有调用方显式打开 speed-color 时才允许速度色覆盖当前粒子段。由于 OpenGL GLSL 与 Metal shader 共用同一 `speedColorEnabled`/UBO 语义,本修正应在重新构建各平台 Native 后同时生效。
## 7. Codex 静态收尾
- 远端 `origin/particle/main` 在本 Step 开始时与 start commit 一致;现有 dirty 仅含任务书列出的三处源码/测试修改及本 Step 记录,未混入 STEP-085 性能代码。
- `RasterParticleLayer::Impl` 默认值与 `ParticleGpuResources` 的资源首帧默认值均为 `false`;显式 setter 及 shader speed-color 分支保持不变。
- `Layer.RasterParticleVisualProperties` 明确覆盖 default OFF、set ON 与 set OFF,因此正式宿主未设属性时不再选择内置速度色带,诊断/显式 opt-in 仍然可用。
- 未执行任何 Native 编译、链接、artifact、Gradle、ADB、iOS 安装或真机工作;这些项目由 RDC 在 STEP-085 后统一验收。

View File

@@ -0,0 +1,35 @@
# STEP-085 V4 Renderer 性能修复 v1
状态:`完成(BUILD_NOT_RUN_BY_CODEX — WAITING_FOR_RDC)`
## 基线
- 机器:Mac-mini-tei.lan。
- worktree:`/Users/tei/dev/MapLibreParticleLab-step085`。
- branch:`step/085-v4-render-efficiency`。
- STEP-085 Start HEAD:`bc6b8dd84b4c10e5263d61832661508c02d84d88`。
- STEP-084 Final HEAD:`bc6b8dd84b4c10e5263d61832661508c02d84d88`,已进入 `origin/particle/main`。
## 本轮结果
- [x] V4 WeatherColor PAUSE/静态 window 不再因缺少 legacy timeline 永久 `needsRepaint()`。
- [x] V4 Particle 使用 Source-owned backend-neutral cadence;30/60 gate simulation,AUTO 保持 animation-frame 语义。
- [x] Forecast PAUSE 与 Particle visual simulation 解耦;camera redraw 未到 deadline 时只 composite。
- [x] V4 Renderer prepare 改持 shared immutable snapshot handle,稳定 window 不深复制大数组。
- [x] WeatherColor 稳定 wrap/resource 状态复用 drawable,mix/opacity/stops 由 tweaker UBO 更新。
- [x] 加入 DEBUG/Validation cumulative counters。
- [x] Android/iOS existing Runtime thin seam 为 V4 30/60 提供 deadline wake;无新 RN API 或平台专用 Core。
- [x] 自审将 platform wake 收紧为“已绑定的 fieldKey 已收到 accepted V4 submit”;clear 非粒子 field 不会错误停止仍可运行的粒子,disable 后 re-enable 可恢复既有 V4 field 的 wake。
- [x] 审查 `advanceForecastClock` production owner;当前未发现,已明确记录而未扩大重构。
- [x] `git diff --check` 通过。
- [ ] Native C++/Metal/Android 编译、链接、codegen、artifact、安装、设备和性能采样:`BUILD_NOT_RUN_BY_CODEX — WAITING_FOR_RDC`。
## 禁止范围核对
未修改 NavSea、provider/network、QBIN V4、FieldProfile/Binding 对外 contract、RN 公共 API、C-001、`detach()` 语义或项目总则。未新增第二套 runtime/timeline,也未引入平台专用 Particle Core。
## 已知风险与下一步
V4 ForecastClock PLAY 的自动生产推进仍无调用 owner,因此未声称 PLAY 自动推进已经修复。RDC 必须先按完成报告进行 Mac C++/Metal、Android Native/AAR、shader/build graph 编译;成功后用统一颜色+性能真机矩阵验证 30/60/AUTO、PAUSE、speed-color、camera 和 debug counters。
本 Step 只完成源码和静态自审;Android Java/Objective-C++ wake 状态调整尚未经过目标平台编译。

View File

@@ -0,0 +1,34 @@
# STEP-087 QBIN V3 解码与 13.4.1-particle.9 升级
## 状态
CLOSED / PASS
## 已完成
- 已将用户提供的《WeatherV2 QBIN V3 通用天气场二进制格式与 React 独立 Render Binding 设计 v2.2 最终冻结稿》保存到 `docs/30_设计/`,原文件内容与 SHA-256 保持一致。
- 新增独立 `QbinV3Decoder`,生产 Block Geometry 冻结为 16×16。
- `EncodedFieldIngress` 改为 QBV2 / QBV3 显式 magic 分流;QBV2 兼容链保留。
- `RasterArraySource::submitEncodedField()` 改为统一走 `EncodedFieldIngress::decodeAndValidate()`。
- CMake / Bazel Core 已注册 V3 decoder;CMake test 已注册 V3 decoder tests。
- 新增 8 个 Current / Rain / Wave / Wind QBIN V3 Golden Fixtures、manifest 与中文校验脚本。
- 新增 C++ 定向测试,覆盖 block boundary、Direction wrap、all-invalid、非最优 Rice-K、CRC/descriptor/validity corruption、Rice padding、残差越界、Node grid 与 ingress 分流。
- Mac clang 定向构建并运行 8 个 V3 tests:8/8 PASS。
- Native / RN 版本声明已统一升级到 `13.4.1-particle.9`。
## Block Geometry
真实 8 帧 benchmark:16×16 数值 payload 为 2,470,419 bytes;32×32 为 2,514,288 bytes,32×32 增加约 1.7758%。32×32 的 decode 总时间仅减少约 2.67 ms / 8 帧,因此最终冻结 16×16。详见 `docs/30_设计/MapLibreParticle_QBIN_V3_Decoder与BlockGeometry实施决策_v1.md`。
## 最终验证与发布
- `verify-qbin-v3-golden-fixtures.py`:PASS(8 files,block=16×16)。
- QBIN V3 C++ 定向 tests:PASS 8 / FAIL 0。
- Android Release 四 ABI:armeabi-v7a、arm64-v8a、x86、x86_64 全部构建通过。
- iOS Metal `MapLibre.dynamic`:PASS;device-arm64 XCFramework 已生成并发布。
- RN tgz:已生成并发布。
- Android Maven / RN tgz / iOS XCFramework 公网 readback:PASS。
- SwiftPM `6.28.0-particle.9`:已发布。
- Release tag:`maplibre-particle/rn/13.4.1-particle.9`。
- Release Final HEAD:`63047540cba703c8efeae0df71e759e165315f85`。
- Android APK / ADB 仍按项目规则由用户手工执行,不属于本次自动发布门槛。

View File

@@ -0,0 +1,125 @@
# STEP-072 V3-08 Android 单次综合验收
> **历史 V3 验收文档。** 本文的 Native `.2`、Maven Local、旧 reverse/临时路径仅描述 STEP-072 当时事实;当前 QBIN V4 `.3` 验收请改读 `docs/00_项目总则/当前基线与开发路线_v2.md` 与 `docs/50_Step记录/STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md`。
## 状态与边界
本文件只列出用户手工执行的单次 Android 验收流程。目标组合是 RN/TS `13.4.1-particle.3` 与 Native `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2`。`.2` 是 STEP-069 v2 multi-plane 所需的新坐标;不得复用、覆盖或误用旧 `.1`。
执行前记录当前 commit。所有 Gradle 调用均使用 `--console=plain`;所有设备命令均显式指定 `adb -s 192.168.1.146`。本流程完成前不得将 AAR、发布、Android 或 bridge 验收写为 PASS。
## 1. Native `.2` Release AAR 与 Maven Local
在 `/mnt/sda1/MapLibreParticleLab/platform/android` 手工确认 `VERSION` 为 `13.4.1-particle.2`,然后执行 OpenGL Release AAR 构建与 Maven Local 发布:
```bash
make RENDERER=opengl BUILDTYPE=Release JOBS=1 \
'MLN_ANDROID_GRADLE=./gradlew --console=plain --parallel --max-workers=$(JOBS) -Pmapbox.buildtype=$(buildtype) -Pmapbox.stl=$(MLN_ANDROID_STL) -Dorg.gradle.internal.http.socketTimeout=$(SOCKET_TIMEOUT) -Dorg.gradle.internal.http.connectionTimeout=$(CONNECTION_TIMEOUT)' \
apackage
./gradlew :MapLibreAndroid:publishOpenglreleasePublicationToMavenLocal --console=plain
```
上述 `make` 写法已用 `make -n` 验证,最终 Gradle 命令包含 `--console=plain`、`--max-workers=1`、`-Pmapbox.buildtype=release` 和 `-Pmaplibre.abis=all`;不要把 `MLN_ANDROID_GRADLE` 只作为环境变量放在 `make` 前面,因为 Makefile 的普通赋值会覆盖环境变量。
记录构建产物 `MapLibreAndroid/build/outputs/aar/MapLibreAndroid-opengl-release.aar` 的 SHA-256,并与 Maven Local `.2` AAR 的 SHA-256 比对;二者必须完全一致。
AAR 不是可直接传给 `javap` 的 JAR;先提取 `classes.jar`:
```bash
M2_AAR="$HOME/.m2/repository/cc/navsea/maplibre/android-sdk-particle-opengl/13.4.1-particle.2/android-sdk-particle-opengl-13.4.1-particle.2.aar"
unzip -p "$M2_AAR" classes.jar > /tmp/step072-particle2-classes.jar
javap -classpath /tmp/step072-particle2-classes.jar -public org.maplibre.android.maps.WeatherRuntime \
| grep -E 'submitField|clearFields|setFieldValueSource|setParticleAdvectionScale'
```
若任一方法缺失、AAR 坐标不是 `.2` 或 SHA 不一致,停止后续 Test Host 验收。
## 2. Linux → Win11 Native `.2` handoff
若 Test Host 继续按已验证的 CARBON7i/Win11 流程构建,则 Linux Maven Local 发布完成后,必须把同一 `.2` 的 AAR、POM、module 三个文件传到 Win11,并放入 Win11 当前用户的 Maven Local 对应目录;不能只复制 AAR,也不能让 Win11 fallback 到 `.1` 或官方 SDK。
目标目录结构:
```text
%USERPROFILE%/.m2/repository/cc/navsea/maplibre/android-sdk-particle-opengl/13.4.1-particle.2/
android-sdk-particle-opengl-13.4.1-particle.2.aar
android-sdk-particle-opengl-13.4.1-particle.2.pom
android-sdk-particle-opengl-13.4.1-particle.2.module
```
Win11 上再次计算 AAR SHA-256,必须与 Linux build output 及 Linux Maven Local AAR 三者一致。可用 `Get-FileHash <AAR路径> -Algorithm SHA256`。完成后再进入 Test Host clean prebuild。若直接在 Linux 同机编译 Test Host,则可使用该机 Maven Local,不需要此 handoff;但不得因此省略 artifact SHA/API 检查。
## 3. Test Host 单次 Android 周期
沿用已验证的 CARBON7i/Win11 流程时,在 **Win11 当前 Test Host checkout 根目录**执行下面的 PowerShell 命令。先 clean prebuild,再用 dependencyInsight 确认 app 最终只解析到 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2`,然后只编译一次 Debug APK:
```powershell
npm ci
npx expo prebuild --platform android --clean
Set-Location android
.\gradlew.bat :app:dependencyInsight --dependency android-sdk-particle-opengl --configuration debugRuntimeClasspath --console=plain
.\gradlew.bat :app:assembleDebug --console=plain
Set-Location ..
```
`dependencyInsight` 中若出现 `.1`、官方 `org.maplibre.gl:android-sdk-opengl` 最终胜出,或 `.2` 未解析,立即停止,不安装 APK。
在另一个 Win11 PowerShell 窗口启动 Metro,固定监听 8091:
```powershell
npm run start
```
Metro ready 后执行固定设备安装/启动命令;每条 ADB 都必须显式带设备地址:
```powershell
adb -s 192.168.1.146 reverse tcp:8091 tcp:8091
adb -s 192.168.1.146 reverse tcp:8081 tcp:8091
adb -s 192.168.1.146 install -r android/app/build/outputs/apk/debug/app-debug.apk
adb -s 192.168.1.146 shell am force-stop org.maplibre.particle.rntesthost
adb -s 192.168.1.146 shell am start -n org.maplibre.particle.rntesthost/.MainActivity
```
Metro binary asset extension 必须仍为 `.f32` 与 `.u8`。本轮 Test Host 只做这一轮 clean prebuild + Debug APK 编译/安装,不为单个数据组重复编译 APK。
## 4. 五组 field 数据逐组验收
对每一组分别先 clear,再按 T0、T1 顺序加载,且每次均记录页面结果和 logcat:
| 数据组 | plane/粒子期望 |
| --- | --- |
| Wind real | vector;Wind/GLOBAL 粒子显示 |
| Rain scalar-only | scalar;不得显示粒子 |
| Wave scalar+DIRECTION | scalar + direction vector;色场与粒子均可用 |
| Tide VELOCITY | vector velocity;粒子可用 |
| Wave synthetic independence | scalar + direction vector;专用于独立性检查 |
每一组都必须依序满足 T0=`ACCEPTED_PENDING_INITIAL_WINDOW`、T1=`INITIAL_WINDOW_READY`,并使最终 Runtime=`READY`。任一状态不同、出现拒绝或 Runtime 非 READY,记录失败组并停止将该组标记为通过。
对每个 Runtime READY 的数据组验证:PLAY/PAUSE、HALF/NORMAL/DOUBLE、seek T0/T1、AUTO/30/60 FPS,以及 Field/Particle 的独立显隐。额外检查 Wave synthetic independence:scalar 色场和 direction particle 的表现及开关互不绑定;Rain scalar-only 不得出现粒子。
## 5. Logcat 与结论
使用固定设备筛查 logcat,观察 v2 submit、initial window ready,以及 fatal bridge/JNI 错误:
```powershell
adb -s 192.168.1.146 logcat -c
adb -s 192.168.1.146 logcat -v time | Select-String -Pattern 'submitField|INITIAL_WINDOW_READY|initial window ready|WeatherRuntime|FATAL EXCEPTION|JNI|bridge'
```
记录每组的 T0/T1/READY 证据、控制项结果、AAR SHA 比对、`javap` 输出摘要及 logcat。仅当五组均符合要求且无 fatal bridge/JNI 错误时,才可由用户决定更新 Android 验收状态;本文件本身不代表已执行或通过。
## 2026-09-11 V3-08 最终关闭
状态:**CLOSED / PASS**。
Win11/CARBON7i 使用 Gradle 8.14.3、AGP 8.12.0、Kotlin 2.1.20、JDK 17.0.18、NDK 27.1.12297006、compileSdk/targetSdk 36、minSdk 24 完成正式 npm 消费链与 Debug APK 构建。dependencyInsight 最终解析到 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2`。
实体机 `192.168.1.146` 上五组数据均满足 T0=`ACCEPTED_PENDING_INITIAL_WINDOW`、T1=`INITIAL_WINDOW_READY`、最终 Runtime=`READY`:Wind real、Rain scalar-only、Wave real、Tide/Current、Wave synthetic independence。
控制面 PLAY/PAUSE、HALF/NORMAL/DOUBLE、FPS AUTO/30/60、seek T0/T1、Weather/Particle 独立显隐、Field stops、Particle color、Trail color、Apply visual 均返回 `ACCEPTED`;最终 FATAL/JNI/UnsatisfiedLinkError 扫描为空。
测试期间发现 fixture 位于 Metro project root 外,临时 Win11 `D:\V308` 验收 worktree 通过 `watchFolders` 纳入 `integration/react-native/particle-v3-fields` 后 bundle 成功。该临时 Metro 修改不进入正式仓库,不属于 Native/RN Runtime 缺陷。
最终产物:Native `.2` AAR SHA-256 `f890f834692d3cacb8a281b2d0f82cf4a7f8ba7756132092be800505b961e9a2`;RN `.3` tgz SHA-256 `16e5dab86aac9832f7a48d8c35dd9821f80f52e7e53ff4e0e6c98724781eda3a`;APK SHA-256 `49824062CAB72771282294114B5A818D2067F9B786B8697D2D2A983BF0B743E2`。

View File

@@ -0,0 +1,35 @@
# Android / RN QBIN V4 `13.4.1-particle.3` 发布基线 v1
日期:2026-09-14。状态:Android OpenGL / RN Test Host 人工验证通过。
## 版本
- Native:`cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.3`
- RN:`@navsea/maplibre-particle-rn@13.4.1-particle.3`
- MLRN:`11.3.6`
## 公网 Maven
Repository:`https://pbf.jpcn.ccwu.cc/maven`
AAR:`https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.4.1-particle.3/android-sdk-particle-opengl-13.4.1-particle.3.aar`
AAR SHA-256:`9ef03eb0d0c03457adc04847cfa69fb67d85191daecd64d3b76db2ad8cc688cd`。
## 公网 RN tgz
`https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.3.tgz`
2026-09-14 Kotlin guard 修正后的公网回读 SHA-256:`abdd5b5f313b6dc2bc2f2b00848d1901be72fed6a6e1fa15899efb10df9f923f`。
## 发布规则
`https://pbf.jpcn.ccwu.cc/` 对应 Linux `/mnt/nas/pbf`。Native AAR/POM/module 写入 `/mnt/nas/pbf/maven/.../<version>/`;RN tgz 写入 `/mnt/nas/pbf/npm/`。发布后必须从公网重新下载并比对 SHA,不能只验证 NAS 本地文件。
本次开发过程中曾为完成 Android 编译验证覆盖同一个 `.3` tgz;该做法从本基线起禁止。以后公开 artifact 内容变化必须递增版本,避免 npm/CDN/Gradle cache provenance 不清。
## 当前验证事实
Win11 Debug APK 已成功构建、安装并运行;Metro/App 端口固定为 Metro `8091`、App `8081`、ADB reverse `8081 → 8091`。Test Host V4 Wind/Wave/Current/Rain Golden QBIN 功能由用户人工确认正常。
完整发布/构建命令以 `docs/50_Step记录/STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md` 为准。

View File

@@ -1,3 +1,9 @@
# 发布目录
保存 Particle release、MapLibre upstream 升级、冻结 commit 和双平台回归结果。
## 当前 Android / RN 发布基线
当前有效基线:`Android_RN_QBINV4_13.4.1-particle.3_发布基线_v1.md`。
历史 release/Step 记录可用于追溯,但当前 Consumer 版本、URL、SHA 和发布规则以该文件、`docs/00_项目总则/当前基线与开发路线_v2.md`、`docs/50_Step记录/STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md` 为准。

View File

@@ -1,6 +1,6 @@
# STEP-064 RN 纯 Field Renderer 接口 V2 完成报告
状态:VALIDATED / READY_FOR_RELEASE_TAG
状态:RELEASED / CLOSED / PASS
## 完成内容
@@ -46,3 +46,13 @@ Consumer 尚未安装最终 `.2` tarball 并在既有 `.1` Native artifact 的 A
- 修正后 attach、T0–T3、Runtime READY、图场/粒子独立开关及恢复均通过。
- 未重新构建 Android APK/AAR;复用既有 Native `.1` APK + Metro 验证 RN `.2`。
- hard gate 无上述 Native/崩溃错误。当前允许进入 release tag 与固定 HTTPS tgz 发布。
## 正式发布结果
- Release source:`e43b58707cfd0f87965424477ed3f95625b3f724`。
- Tag:`maplibre-particle/rn/13.4.1-particle.2`,已推送远端。
- 正式 tgz URL:`https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.2.tgz`。
- size:10,660 bytes;SHA256:`ace9e0401824839d18a93cc4201c1f5d4cccbff0636570cfde82fd80b60779b5`。
- NAS 文件、tag pack 与公网重新下载内容一致;公网返回 HTTP 200。
- Win11 固定 Consumer 从正式 HTTPS URL 重装成功,`@navsea/maplibre-particle-rn=13.4.1-particle.2`、MLRN=11.3.6 且 dedupe,TypeScript PASS。
- Native AAR 仍为 `13.4.1-particle.1`,本次没有重新构建、修改或发布 AAR。

View File

@@ -0,0 +1,33 @@
# STEP-065 MapLibreParticleLab V3 通用 Scalar / Vector Field 架构冻结 Codex 完成报告
状态:CLOSED / PASS(设计冻结;非实现/构建验收)
## 已完成
已依据文件/符号级真实代码完成 Contract、GPU、粒子单位、binary ingress、timeline/reuse、RN ABI 与 WeatherV2 指定源文件的只读调查;正式冻结 `maplibre-particle-field/v2`、0/1 scalar + 0/1 vector + shared validity、`SCALAR|VECTOR_MAGNITUDE`、`VELOCITY|DIRECTION`、独立 plane file references、v1 thin normalize 和 V3-02 至 V3-08 实施/验收路线。四类 WeatherV2 映射、Wave synthetic independence fixture、风险和受影响文件矩阵均已记录。
## 逐项状态
|项目|结果|
|---|---|
|生产代码修改|未完成(按任务边界:否)|
|WeatherV2/ NavSea 修改|未完成(按任务边界:否)|
|Android Gradle / APK / ADB|未执行(按任务边界)|
|Jest|未加入、未执行(按任务边界)|
|只读架构调查与 V3 Contract|已完成|
|文档/记录|已完成|
|构建/运行验证|无法验证(任务禁止且本任务不实现)|
## 未完成项与风险
没有本 Step 的文档交付阻塞。后续实现未解决项:Kotlin/JNI 采用三 nullable `byte[]` 或带 offsets 的 v2 container 的 ABI 决策、64 MiB 单文件限制向总 plane 上限的调整、GPU texture format/内存实测、DIRECTION 的产品 advection scale 默认值、Android GL/Vulkan 与未来 Metal 验证。它们均不应由本设计任务臆断为已通过。
建议下一任务为 `STEP-066 V3-02 Core Contract + Scalar Snapshot/Adapter`:只改 Core Contract/adapter 和离线 fixture,首先锁定 v1 normalize、三组合 validation、timeline compatibility;不得提前提交 Android build。
## Git 收尾
- Scoped content commit:`44999edb256ae73c3abc94ef3f0d208730c47897`。
- Commit message:`docs: freeze V3 scalar vector field contract`。
- Push:成功,`particle/main -> origin/particle/main`(`2e3ab2627..44999edb2`)。
- 该内容提交 push 后复核:`HEAD == origin/particle/main == 44999edb256ae73c3abc94ef3f0d208730c47897`。
- 本报告的此 Git handoff 回填将以单独 docs-only scoped commit 推送;最终远端 HEAD 以任务最终回复所列值为准。除任务书与五个文档/记录外未暂存任何文件。

View File

@@ -0,0 +1,39 @@
# STEP-066 MapLibreParticleLab V3-02 Core Contract 与 Scalar Snapshot Adapter Codex 完成报告
状态:CLOSED / PASS
## 结果
V3 Core 已从 Wind-only U/V payload 统一为 generic snapshot:0/1 scalar、0/1 vector、1 shared validity。v1 仍可读取,并在 adapter ingress 转为 `VELOCITY` vector snapshot;v2 vector-only、scalar-only、scalar+vector 均由 Core 通过。无 channel、尺寸、validity、semantic、finite 或 valid-cell noData 错误均拒绝。
`fieldType` 与 `displayDomain` 保留为可选 legacy metadata,但不再决定 Core capability、validity 或 T0/T1 compatibility。content identity 直接比较 decoded scalar/vector/validity plane,任一 plane 改变不会误判 same-content。
## 逐项状态
|任务项|结果|
|---|---|
|规范、STEP-065、实际源码与工作区核对|已完成|
|V3 generic snapshot/descriptor/payload bundle|已完成|
|WIND/GLOBAL validity hardcode 移除|已完成|
|v2 vector-only/scalar-only/scalar+vector|已完成|
|shared validity、VectorSemantics、v1 thin normalize|已完成|
|interpolation compatibility/content identity|已完成|
|Timeline/Runtime Window ownership 保护|已完成|
|最小 Core fixtures/tests|已完成(内存 plane fixture)|
|精确 Linux/Core 编译和过滤测试|已完成|
|Android/JNI/RN/WeatherV2/NavSea 不修改|已确认|
|Step、报告、开发记录|已完成|
## 验证与未完成项
Linux OpenGL `mbgl-test`、受影响 `source.test.cpp.o`、`mbgl-test-runner` 均已通过;`Source.ParticleFieldV3*` 过滤测试退出码为 0;`git diff --check` 通过。首次受限沙箱编译因 ccache 临时目录只读失败,获准使用现有构建缓存后完成,非源码错误。
未完成且不属于本 Step:V3-03 scalar GPU texture/`FieldValueSource`、V3-04 DIRECTION normalizing/advection、V3-05 v2 JNI/RN 三 plane ingress、真实业务 fixture/Android 验收。既有 renderer 对无 vector snapshot 明确 fail-closed,未伪造 scalar 为 U/V。
## Git 收尾
- Source/function commits:`379c9ed0e`(`core: add scalar vector field snapshots`)与 `6d1f3e051`(`core: preserve fixture times in generic snapshots`)。
- Docs commits:`b366a2d16`(`docs: record STEP-066 core contract`)与 `9a09278a2`(`docs: record STEP-066 push handoff`)。
- 已完成 handoff commit:`9a09278a28170446fd3e180ae5f7c2d8d558972d`。
- 已完成 push:`particle/main -> origin/particle/main`,先后为 `d23ae2079..b366a2d16`、`b366a2d16..9a09278a2`;当时复核 `HEAD == origin/particle/main == 9a09278a28170446fd3e180ae5f7c2d8d558972d`。
- 本次仅补纳遗留任务书与本报告 Git 收尾回填;其 scoped commit/push 后最终 HEAD 以本次收尾回执为准。

View File

@@ -0,0 +1,22 @@
# STEP-067 MapLibreParticleLab V3-03 Scalar GPU 与 Generic Field Renderer Codex 完成报告
状态:CLOSED / PASS
## 生产代码修改
- `weather_color_layer.*` 与 `weather_color_stop.hpp`:增加明确 `FieldValueSource`(默认 `VectorMagnitude`),将 color stop 的 C++ 内部字段改为 generic `value`。
- `render_weather_color_layer.cpp`:同一 renderer 按 source 显式上传 scalar 或 vector bundle,A 写 shared validity;source 不匹配时 unavailable。切换 source 不复用旧的不同编码,正常同 source rollover 继续重用 immutable decoded bundle。
- `particle_gpu_validation.fragment.glsl` 与生成的 OpenGL header:Weather pass 用 UBO flag 选择 scalar 或 vector magnitude,分别对 T0/T1 做数值插值后进入通用 ramp;默认 ramp 保留旧 Wind 的 0..30 数值外观,但不再声明固定 m/s 单位。
- `render_particle_validation_layer.cpp`:Particle 仅接受 vector + `Velocity`;scalar 或 `Direction` 清理粒子资源、不可绘制。粒子 shader 的物理 U/V 计算未改变。
## 验证结果
shader generator、三个受影响 C++ 编译对象、`Source.ParticleFieldV3*` 与 WeatherColor layer 过滤测试、`git diff --check` 均 PASS,命令输出均经 build-output-throttle 限流。此前受限终端未完成的全量重链,已由用户在当前 `HEAD`(`06fcbef9ccb820e595f06acbd356950de7e3a73f`)手工执行 `ninja -C build-linux-opengl -j3 mbgl-test mbgl-test-runner` 收尾,输出为 `ninja: no work to do.`;因此确认 `mbgl-test`、`mbgl-test-runner` 的完整目标和依赖已最新,没有待处理的构建或重链接工作。
该手工 Ninja 结果不等于重新执行全量测试用例:本次未重跑完整测试套件,只确认构建/重链接目标无待处理工作。
未执行 Android Gradle、AAR、APK、ADB 或 Jest;未触碰 STEP-062 用户修改、历史 untracked、build/cache/tgz。
## Git 收尾
实现基线提交为 `06fcbef9ccb820e595f06acbd356950de7e3a73f`(`docs: record STEP-067 scalar GPU renderer`)。本次关闭仅提交 STEP-067 任务归档、Step 记录、完成报告和开发记录;不会暂存 STEP-062 用户修改、历史未跟踪文件、build/cache 或 tgz。关闭提交 hash、push 结果与最终 `HEAD == origin/particle/main` 复核见本次交付终端回执。

View File

@@ -0,0 +1,50 @@
# STEP-068 MapLibreParticleLab V3-04 DIRECTION 粒子推进 Codex 完成报告
结论:CLOSED / PASS。
## 已完成
生产代码限定在 RasterParticleLayer Core 属性、Particle renderer、Particle shader/UBO 和 generated OpenGL shader header。`DIRECTION` 采用 T0/T1 U/V 分量插值后再归一化;零或近零、NaN/Inf 混合向量 fail-closed。`advectionScale` 默认 1.0,必须 finite 且大于零,仅作用于 DIRECTION 的推进长度。VELOCITY 没有归一化,也不读取该 scale,保持既有 U east/V north 与 Web Mercator 位移数学。
State 与 line vertex 都使用相同的 resolved advection 语义。renderer 仍拒绝 scalar-only、缺 semantics 和 T0/T1 semantics mismatch;字段内容 reuse、T0/T1/MIX、rollover、lifecycle 与 trail 没有重写。变更 scale 不会重传 field texture 或重建 simulation/trail。
增加 C++ Layer 属性测试,以及 provider-neutral CPU oracle 测试:东、北、斜向、T0 east/T1 north/MIX 0.5 的 northeast、反向归零 invalid、scale 仅改 Direction magnitude,和 VELOCITY 保持未归一化。
## 验证结果
- shader generator:PASS。
- 最终受影响精确对象(Particle/WeatherColor renderer、RasterParticle style、source/style tests):PASS。
- `ninja -C build-linux-opengl -j3 mbgl-test mbgl-test-runner`:PASS,完整目标重编译/重链完成。
- STEP-068 相关过滤测试 `Source.ParticleFieldV3*:Layer.RasterParticle*`:8/8 PASS。
- `git diff --check`:PASS。
- Android Gradle/AAR/APK/ADB、Jest:未执行,任务明确禁止。
## 2026-09-11 全量 Linux 重编译遗漏修复
外部全量 Linux 重编译报告 `render_weather_color_layer.cpp:108` 的
`-Wmissing-field-initializers`:共享 `ParticleGpuValidationUBO` 增加 `advection` 后,
WeatherColor 的聚合初始化未同步。本次只显式初始化该字段为 `{0, 1, 1e-5, 0}`:
Direction flag 关闭、scale 为单位值,且 WeatherColor 不消费 advection,因此不影响
色场。审计所有 C++ 聚合初始化器后,Particle renderer 已完整初始化;WeatherColor
是唯一遗漏。
本次未改 shader 输入,不需为该 C++ 初始化修复重新运行 shader generator。修复后的
WeatherColor 精确对象编译和 `git diff --check` 均 PASS(编译输出已限流保存);随后
外部终端完成 `mbgl-test` / `mbgl-test-runner` 完整目标重编译与重链并 PASS。
## 独立既有测试问题、风险与下一步
扩大过滤范围时,当前基线自带的 `Layer.WeatherColorUsesIndependentFixedOpacity` 暴露一个与 STEP-068 无关的测试断言错误:测试已成功把 stops 改为 `{-2,3}`,最后却断言仍等于旧的 `{0,10,20}`。通过 `git show HEAD:test/style/style_layer.test.cpp` 已确认该逻辑在 STEP-068 前就存在;本 Step 不修改该旧测试,避免把无关修复混入 DIRECTION 实现。
STEP-068 自身相关 8 个测试全部 PASS,完整 Linux 目标也已 PASS,因此可以关闭。剩余平台风险保留到后续综合阶段:Android/Vulkan 与最终设备 shader driver 仍未在本 Step 验证。下一步为 V3-05 Android/JNI/RN generic v2 multi-plane ingress;仍不在此处引入 WeatherV2/NavSea 业务逻辑。
## Git 交付
- 主实现 commit:`f3c33c67275b24f61b80924abbb9d10d68b2cb7c`
- commit message:`feat: add direction particle advection`
- push result:成功
- remote branch:`origin/particle/main`
- 主实现提交推送后确认:`HEAD == origin/particle/main == f3c33c67275b24f61b80924abbb9d10d68b2cb7c`
- 本报告随后仅追加 Git 交付记录并进行独立文档收尾提交;主实现 commit 保持不变。
- 未纳入 STEP-062 用户修改及历史无关 untracked/build/cache/tgz。

View File

@@ -0,0 +1,27 @@
# STEP-069 MapLibreParticleLab V3-05 Android/JNI/RN v2 多 Plane 入口 Codex 完成报告
结论:IMPLEMENTED / WAITING_ANDROID_VALIDATION。
## 已完成
`@navsea/maplibre-particle-rn` 已升至 `13.4.1-particle.3`。v2 `FieldRuntime.submitField` 仍接收必填 validity、可选 scalar/vector 的 `FieldPayloadRefs`;仅 Native TurboModule ABI 改为 `validityFileUri, scalarFileUri, vectorFileUri` 三个明确 string 参数,空 scalar/vector string 只表示 ABI optional marker。Kotlin 在读入前检查 local-file、非空及 aggregate 64 MiB。Java 的 explicit packed v2 container 只存在于 Java/JNI,使用 plane presence flags 和 lengths;JNI 恢复 `ParticleFieldPayloadBundle`,JS 不构造大容器。Java 对 nullable scalar/vector 有 `@Nullable` 注释,并在 Java facade 额外拒绝 total 大于 64 MiB,防止绕过 Kotlin 的 oversized packed allocation。v1 ABI 保留,`.3` 用 `submitLegacyVectorField` 明确标识。
Core v1/v2 ingress decode 后共用 Timeline 提交 helper,未改 duplicate、T0/T1、MIX、rollover 或生命周期。`setFieldValueSource` 仅接受 0/1 对应 `SCALAR|VECTOR_MAGNITUDE`,`setParticleAdvectionScale` 仅接受 finite 且大于零,均只改 renderer property。
## 验证与未完成
- `git diff --check`:PASS。
- 外部复核指定的实际 codegen 命令 `node integration/react-native/MapLibreParticleRNTestHost/node_modules/@react-native/codegen/lib/cli/combine/combine-js-to-schema-cli.js /tmp/step069-schema.json packages/maplibre-particle-rn/src/NativeWeatherRuntimeBridge.ts`:PASS。
- `./node_modules/.bin/tsc --noEmit -p packages/maplibre-particle-rn/tsconfig.json`:PASS。
- `source.test.cpp.o` 精确编译与 `ninja -C build-linux-opengl -j3 mbgl-test` 重链:PASS;`Source.HostFieldV2BundleUsesTheSameTimelineIngress:Source.HostFieldLegacyAndCompatibleV2VectorOnlyShareOneTimeline`:2/2 PASS,明确覆盖 legacy v1 与 compatible v2 vector-only 的同一 Timeline ingress。
- 上述 build/codegen/test 命令均经 build-output-throttle;Android Kotlin/Java/JNI 签名只做源码一致性核对,未运行 Android 编译。
- Android Gradle、AAR、APK、ADB 与 Jest 均按任务禁止未执行。Android Kotlin/Java/JNI compile 和设备验证必须在 V3-08 完成,故不标记 Android CLOSED/PASS。
## Git 收尾
- implementation commit:`148bf80fe41ba566de6c1d4b439cfe372a8a453c`,message:`feat: add v2 multi-plane field ingress`。
- handoff commit:`12827489f4c377175554562632129df125c0a556`,message:`docs: record STEP-069 v2 field ingress`。
- 首轮 push:成功,`9e9ea07bc..12827489f particle/main -> particle/main`。
- remote branch:`origin/particle/main`;首轮 push 后 `HEAD == origin/particle/main == 12827489f4c377175554562632129df125c0a556`。
- 本报告的 Git 结果回填会形成一个最小 metadata 收尾提交;该提交完成后再次 push,并在任务最终验收中以实际 `git rev-parse HEAD` / `origin/particle/main` 核对最终 HEAD。Git commit 无法在自身正文中稳定自包含自身最终 hash,因此不伪造自引用 hash。
- 未纳入:STEP-062 两处用户 dirty、历史 untracked、`.codex-build-logs/`、旧 tgz/build/cache。

View File

@@ -0,0 +1,11 @@
# STEP-070 MapLibreParticleLab V3-06 四类场 Fixture 与 RN 通用回归 Codex 完成报告
结论:IMPLEMENTED / WAITING_ANDROID_VALIDATION。
Wind、Wave、Tide/Current 已形成真实本地离线 canonical fixture。Wind 从既有 v1 field 转换时,shared validity 现在严格要求 U/V 都 finite 且都不等于 `-9999`。Wave 直接读取 WeatherV2 `2026090912` run 的正式 `waves_particles.bin` 与 sidecar:height 进入 scalar,规范化后的 `unit_to_u/v` 进入 `DIRECTION` vector,period 被明确排除。Tide 从 CMEMS `2026090900` run 的正式 `current_vectors.bin` 读取 U/V 形成 `VELOCITY` vector。每帧摘要记录源 BIN 与 sidecar SHA-256、target_time、bounds、grid、projection 与 provenance。
Rain 仍是明确的 `rain-decoder-fixture`:本任务未实现 PMTiles reader,其数值不是真实场值。所有 fallback/synthetic generation 均为小且严格递增的 `1/2`,不再使用 epoch 毫秒。
RN Test Host 的 Wave/Tide 已改用新 real 目录;所有提交继续只使用 `@navsea/maplibre-particle-rn` 的 `FieldRuntime` 公共 API 和本地 file URI。fixture validator、package TypeScript、Host typecheck、TurboModule schema 与 `/tmp/step070-ccache` 下的 `source.test.cpp.o` 精确重编译均 PASS。随后恢复 Linux OpenGL 完整依赖闭包构建,459/459 完成并成功链接 `mbgl-test-runner`;`Source.ParticleFieldV3*` 过滤测试 6/6 PASS,整条验证链退出码 0,`git diff --check` PASS。
未运行 Android Gradle/AAR/APK/ADB/Jest,故没有 Android PASS 声明。implementation commit 为 `8588e6fcb4f3f5d874159c2e42619c5227cb7921`(`feat: add V3 field fixtures and RN regression host`),已成功 push 到 `origin/particle/main`。最终 handoff 只提交 STEP-070 的 Metro `.u8` asset 配置与验收文档,不纳入 STEP-062 两份用户 dirty 文档或历史 untracked/build/cache/tgz。未修改 WeatherV2、NavSea 或 Core Runtime 算法;Rain 真实 reader 与 Android/设备验收仍是后续工作,Linux 完整重链/过滤测试已经完成。

View File

@@ -0,0 +1,37 @@
# STEP-071 — MapLibreParticleLab V3-07 Linux 综合冻结完成报告
结论:CLOSED / PASS(Linux)。
日期:2026-09-11
## 实际完成
V3-07 在 `particle/main` 上完成 Linux 综合冻结。最终验证链从 canonical fixture、RN/TS/codegen 到 Linux OpenGL test target 和 V3 相关 Source/Layer 回归全部通过。
最终结果:
- fixture validator:PASS;
- package TypeScript:PASS;
- RN Test Host typecheck:PASS;
- TurboModule codegen:PASS;
- `mbgl-test` / `mbgl-test-runner`:PASS / up-to-date;
- V3 综合过滤:21/21 PASS;
- `git diff --check`:PASS;
- 最终验证链退出码:0。
## 冻结中修正
综合回归暴露两个旧测试 fixture 问题:Host T0/T1 使用重复 payload,以及 Timeline preload 三帧 `timeIndex` 全为 0。两处都通过测试侧局部修正解决;生产 Runtime 行为未改变。
## 未执行与边界
- 未运行 Android Gradle/AAR/APK/ADB/Jest,因此没有 Android PASS 声明。
- 未修改 `src/`、`include/`、shader、JNI、Android、RN runtime、WeatherV2 或 NavSea。
- 已知 `Layer.WeatherColorUsesIndependentFixedOpacity` 既有断言债未纳入本 Step gate,也未修复。
- STEP-062 两份用户 dirty 与历史无关 untracked/build/cache/tgz 未纳入。
## 下一步
进入 V3-08:按既定策略仅安排一次 Android 编译、安装和实体机综合验收,覆盖 V3-05/V3-06 尚未完成的 Android/JNI/RN v2 multi-plane 与四类场路径。
## Git handoff
- 冻结提交:`a4bf6b7aea447254e2b01a95215dc67b2ba8c9a4`
- 提交信息:`test: close V3 Linux freeze`
- Push:成功
- Remote branch:`origin/particle/main`
- Push 后已验证:`HEAD == origin/particle/main == a4bf6b7aea447254e2b01a95215dc67b2ba8c9a4`
- 本报告的 Git 元数据回填将作为单独 docs-only closeout 提交;不改变上述冻结代码基线。

View File

@@ -0,0 +1,17 @@
# STEP-072B V3-08 RN Kotlin ReadableMap 编译修复 Codex 完成报告 v1
## 结论
**PASS / CLOSED**。
Codex 严格按任务边界,仅在 `WeatherRuntimeBridgeModule.kt` 增加 `com.facebook.react.bridge.ReadableMap` import,未修改 API、ABI、TurboModule spec、Maven 坐标或业务逻辑。
## 验证结果
Linux hard gate 全部通过:fixture validator、package TypeScript、RN Test Host typecheck、TurboModule codegen、`git diff --check`。
重新发布的 `@navsea/maplibre-particle-rn@13.4.1-particle.3` 公网 tarball SHA-256 为 `16e5dab86aac9832f7a48d8c35dd9821f80f52e7e53ff4e0e6c98724781eda3a`。
Win11 正式 npm 消费链编译通过:`:navsea_maplibre-particle-rn:compileDebugKotlin` PASS,`:app:assembleDebug` PASS,`BUILD SUCCESSFUL in 6m 20s`。APK SHA-256 为 `49824062CAB72771282294114B5A818D2067F9B786B8697D2D2A983BF0B743E2`。
实体机五组 V3 数据与 Runtime/控制面最终验收全部通过,STEP-072/V3-08 可关闭。

View File

@@ -0,0 +1,55 @@
# STEP-072 — V3-08 Android Native v2 与单次综合验收准备完成报告
结论:IMPLEMENTED / WAITING_ANDROID_VALIDATION
日期:2026-09-11
## 实际完成
V3-08 前置准备已完成:Native Android 版本目标升为 `13.4.1-particle.2`,RN package `13.4.1-particle.3`、Expo plugin、RN Test Host 与 release manifest 均统一指向该新 Native 坐标;旧 `.1` 不再作为当前 active dependency。
Test Host 同时存在两套 `WeatherRuntimeBridge` 的问题已消除。Expo autolinking 复核后仅保留正式 `@navsea/maplibre-particle-rn` bridge,legacy local bridge 不再参与 autolink/codegen。
## 静态验证结果
- Expo autolinking:PASS;legacy bridge 不在 dependency graph。
- RN Test Host TypeScript:PASS。
- package TypeScript:PASS。
- RN TurboModule codegen:PASS;仅 `NativeWeatherRuntimeBridge`。
- V3 fixture validator:PASS。
- Expo plugin JavaScript syntax:PASS。
- release manifest JSON:PASS。
- `git diff --check`:PASS。
## 验收流程冻结
新增 `docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md`。流程按既有成功路径明确分为:Linux 构建/发布 Native `.2` → SHA/API 检查 → Linux→Win11 Maven Local handoff → Win11 clean prebuild/dependencyInsight/单次 APK build → Metro 8091 → 固定设备 `192.168.1.146` 安装/启动 → 五组 field 数据综合验收。
文档已修正两个容易导致误操作的问题:AAR 需先提取 `classes.jar` 再 `javap`;Makefile 的 `MLN_ANDROID_GRADLE` 必须作为 make 命令行变量传入,不能仅作为环境变量。
## 未执行与边界
- 未执行 Android Gradle、AAR build、Maven publish、APK build、ADB 或实体机测试。
- 未运行或加入 Jest。
- 未修改 MapLibre Core/JNI/WeatherRuntime/renderer、WeatherV2、NavSea 或 V3 field runtime 语义。
- STEP-062 两份用户 dirty 文档、STEP-062 Android 最终报告及历史 untracked/build/cache/tgz 均未纳入。
- legacy bridge 的 untracked `android/build/` 按任务要求原样保留,未清理。
## Git handoff
- 实现/准备提交:`11cf58d344e64e6aff77deceb2c34c8eda045f1b`。
- 提交信息:`chore: prepare V3-08 android validation`。
- Push:成功,`particle/main -> particle/main`。
- Remote branch:`origin/particle/main`。
- 实现提交 push 后验证:`HEAD == origin/particle/main == 11cf58d344e64e6aff77deceb2c34c8eda045f1b`。
- 本报告 Git 元数据回填将作为 docs-only closeout 提交;该提交后的仓库 Final HEAD 由最终验证结果记录,不伪造自引用 hash。
## 下一步
仅由用户执行 V3-08 单次 Android 综合验收。验收通过前 release manifest 保持 `awaiting_android_manual_build_publish_acceptance` / Android `not_run`,不得把 `.3` 标记 released 或创建 release tag。
## 2026-09-11 V3-08 最终关闭
状态:**CLOSED / PASS**。
Win11/CARBON7i 使用 Gradle 8.14.3、AGP 8.12.0、Kotlin 2.1.20、JDK 17.0.18、NDK 27.1.12297006、compileSdk/targetSdk 36、minSdk 24 完成正式 npm 消费链与 Debug APK 构建。dependencyInsight 最终解析到 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2`。
实体机 `192.168.1.146` 上五组数据均满足 T0=`ACCEPTED_PENDING_INITIAL_WINDOW`、T1=`INITIAL_WINDOW_READY`、最终 Runtime=`READY`:Wind real、Rain scalar-only、Wave real、Tide/Current、Wave synthetic independence。
控制面 PLAY/PAUSE、HALF/NORMAL/DOUBLE、FPS AUTO/30/60、seek T0/T1、Weather/Particle 独立显隐、Field stops、Particle color、Trail color、Apply visual 均返回 `ACCEPTED`;最终 FATAL/JNI/UnsatisfiedLinkError 扫描为空。
测试期间发现 fixture 位于 Metro project root 外,临时 Win11 `D:\V308` 验收 worktree 通过 `watchFolders` 纳入 `integration/react-native/particle-v3-fields` 后 bundle 成功。该临时 Metro 修改不进入正式仓库,不属于 Native/RN Runtime 缺陷。
最终产物:Native `.2` AAR SHA-256 `f890f834692d3cacb8a281b2d0f82cf4a7f8ba7756132092be800505b961e9a2`;RN `.3` tgz SHA-256 `16e5dab86aac9832f7a48d8c35dd9821f80f52e7e53ff4e0e6c98724781eda3a`;APK SHA-256 `49824062CAB72771282294114B5A818D2067F9B786B8697D2D2A983BF0B743E2`。

View File

@@ -0,0 +1,80 @@
# STEP-073 QBIN2 V4 Core 与通用 Field Profile Validator 完成报告
## 结论
**已完成 / PASS。** 本 STEP 只建立 QBIN2 V4.4 的 provider-neutral C++ Core primitive 与 Contract Schema v1 的通用 Validator。没有实现 QBIN 二进制 Decoder,未接入 Android/JNI/RN,未修改 Renderer,也未切换现有 V3 `submitField(...)` 生产路径。
## Git 记录
- 任务开始 HEAD:`a0aa367f302ad0e3518931c311ae8764efd2d566`。
- 初始实现提交:`0a190adff3a24af59647af4977f1c1e7e107ec28`,`feat: add QBIN2 V4 generic field profile validation`。
- 最终实现 HEAD:`3526e324ce6c623cfacd4a658605549ccd32d4da`。
- Final follow-up:`3526e324ce6c623cfacd4a658605549ccd32d4da`,`fix: enforce validity descriptor class`;强制独立 validity descriptor 自身为 `Validity`,避免被伪装为 Scalar。
- 远端分支:`origin/particle/main`。
- push 结果:已成功从 `a0aa367f3` 推送至 `0a190adff`,随后已成功推送 follow-up 至 `3526e324c`。
- 任务开始前的既有 modified/untracked 文件未暂存、未提交、未删除或格式化。
## 实际修改文件
- `CMakeLists.txt`、`test/CMakeLists.txt`:注册 V4 Core、Validator 与测试。
- `include/mbgl/particle/field/field_profile_contract.hpp`:Contract Schema v1、value class/component/time relation 与 Core 单位常量。
- `include/mbgl/particle/field/field_runtime.hpp`、`src/mbgl/particle/field/field_runtime.cpp`:provider-neutral frame/grid/plane、Series/store、ForecastClock 与 revision 处理。
- `include/mbgl/particle/field/field_profile_validator.hpp`、`src/mbgl/particle/field/field_profile_validator.cpp`:独立通用 Validator。
- `test/particle/field_runtime.test.cpp`:Validator、revision、独立 cadence 与 clock 自动化测试。
- `docs/50_Step记录/STEP-073_QBIN2_V4_Core与通用FieldProfileValidator_v1.md`、`粒子开发记录.md`:正式阶段与开发记录。
## V4 Core 草稿审查与修正
已保留草稿的 `FrameContentIdentity`、`FieldSeries`、每 fieldKey 独立 store/window 与 `ForecastClock` 边界。修正点是增加 `DecodedFieldPlane::requiredFlag`,并在 `DecodedFieldFrame` 中加入独立 `validityPlane` descriptor。Runtime 仍没有 QBIN header、byte offset、CRC、网络或 provider 字段。
`ForecastClock` 仅拥有 requested time、播放状态和速度;未接管 `RasterArrayTimelineRuntime`。旧 `ParticleFieldDescriptor`、`ParticleFieldSnapshot`、`ParticleFieldDataAdapter`、`LocalParticleFieldFeed` 和 `RasterArrayTimelineRuntime` 均未删除或迁移。
## Validator API 与结果映射
最终入口为 `FieldProfileValidator::validate(const ResolvedFieldProfileContract&, const DecodedFieldFrame&)`,返回 `FieldProfileValidationResult { code, detail }`。
| 结果枚举 | 含义 |
| --- | --- |
| `Accepted` | 已接受 |
| `UnsupportedContractSchema` | 不支持的 schema version |
| `InvalidContract` | Contract 结构非法 |
| `InvalidFrame` | Frame/dtype/结构非法 |
| `MissingRequiredPlane` | 缺少 Contract required plane |
| `PlaneDescriptorMismatch` | required descriptor 或 REQUIRED flag 不一致 |
| `UndeclaredRequiredPlane` | 实际 REQUIRED plane 未被 Contract 声明 |
| `AdditionalPlaneNotAllowed` | 禁止额外的非 REQUIRED plane |
| `InvalidVelocityGroup` | 速度 group 缺分量、重复分量或 group=0 |
| `InvalidCoreUnit` | Velocity/Direction/Validity 的 Core 单位或 dtype 违规 |
| `UnsupportedSharedValidityMode` | Schema v1 当前 frame 能力无法表达 `sharedValidityRequired=false` |
| `TimeRelationMismatch` | Contract 要求的 valid/reference 时间关系不成立 |
验证仅使用 Contract descriptor,不基于 `profileID` 做产品分支。普通 Scalar semanticID 与 unitID 保持 opaque;测试中的未知 semantic `777` 和 unit `65432` 在 Contract/Frame 一致时已接受。
## Validity、REQUIRED 与 Core 规则
Validity 采用“独立 descriptor + `validityBits` payload”:`validityPlane.storage` 与 `validityBits` 是同一共享只读引用,因此不复制 payload,且后续 projection 可复用同一 validity domain。Validator 强制该 descriptor 自身为 `Validity`,并检查 semantic/valueClass/unit/group/component/dtype/REQUIRED/count;要求 `BITSET`、`NONE`、group `0`、component `None`。
REQUIRED 为双向 fail-closed:Contract 每项必须在 Frame 中存在、完整 descriptor 匹配且实际 REQUIRED=true;任一实际 REQUIRED=true plane 必须被 Contract 完整声明。`allowAdditionalPlanes=true` 只允许额外的非 REQUIRED plane。
Velocity 的通用检查要求非零 group、唯一 U_EAST/V_NORTH、两者 count/unit 一致且单位为 MPS;Direction Angle 要求 DEGREE。上述规则均没有 profile 名称逻辑。
## Frame identity、cadence 与时钟
自动化测试确认:同 fieldKey + validTime + identity + content 返回 `Duplicate`;同 identity 不同 content 返回 `RejectedIdentityConflict`;generation/dataVersion 更新返回 `ReplacedRevision`,且 replacement 继续检查相邻 frame interpolation compatibility。不同 fieldKey 在同一 requested time 各自计算不同 cadence 的 T0/T1/mix。ForecastClock 的 pause/play/速度与 Runtime window 独立。
## 实际 Linux 验证
所有构建/测试通过输出节流脚本执行:
- `cmake --build build-linux-opengl --target mbgl-test -j3`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:PASS。
- `git diff --check`:PASS。
未执行 Android Gradle、APK 编译、ADB 或真机操作;这符合本 STEP 禁止范围。未执行 iOS/Metal 验证,也不声明其通过。
## 未完成 / 无法验证
- 未完成:QBIN2 Encoded Ingress / `QbinV2Decoder`,按任务边界留给后续审计批准后的 STEP。
- 未完成:Android/JNI/RN、Renderer、GPU 与 iOS/Metal 验证;本 STEP 明确禁止或不要求这些工作。
- 无法验证项:无阻塞的本 STEP 必做 C++ 项。

View File

@@ -0,0 +1,34 @@
# STEP-073 QBIN2 V4 Core 与通用 Field Profile Validator 审计修正完成报告 v2
## 结论
**已完成 / PASS。** 本次仅修正 STEP-073 v1 的三项确定审计问题,并补充 C++ 覆盖;没有实现 QBIN Decoder,未接入 Renderer、JNI、Android 或 RN,现有 V3 `submitField(...)` 与 timeline 生产路径保持不变。
## 审计修复结果
1. **DEGREE Core Unit ID**:`FieldUnitDegrees` 已由错误的 `2` 改为 QBIN V2 registry 冻结值 `4`。测试同时确认 `NONE=0`、`MPS=1` 保持不变;Direction Angle 的 `unitID=4` 被接受,`unitID=2` 得到 `InvalidCoreUnit`。
2. **Plane 唯一键**:Frame plane 与 Contract required plane 均按 `(semanticID, groupID)` 唯一。component 不再参与定位键,作为 descriptor 的 valueClass/unitID/component/requiredFlag 比较项;同 semantic/group、不同 component 的 Frame 或 Contract 均结构非法;已定位 plane 的 component 错误返回 `PlaneDescriptorMismatch`。既有 U/V 速度组仍以各自 semanticID 配合 U_EAST/V_NORTH 规则通过。
3. **ForecastClock HALF 漂移**:时钟在内部保存 half-millisecond remainder,避免每次 HALF tick 独立四舍五入。测试确认连续两次 `advance(1)` 只前进 1ms、多 tick 不发生 2x 漂移、seek 清除旧 remainder、NORMAL/DOUBLE 行为不变、pause 不前进并在 overflow 时 fail-closed。
## Linux 验证
构建和测试全部经 `build-output-throttle` 节流执行:
- `cmake --build build-linux-opengl --target mbgl-test -j3`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:PASS。
- `git diff --check`:PASS。
首次 build 因沙箱禁止 ccache 写入 `/root/.cache` 而未开始编译;经授权以相同节流命令重试后通过,不影响上述结论。
## Git 收尾
- 任务开始 HEAD:`c3033eb5a4560e5778dcae7f341965caca4f570c`。
- 本次实现提交 / Final 功能 HEAD:`d6a784a791601a0829529b1a5d3b184bcd23dd3b`,`fix: correct STEP-073 QBIN2 V4 core contract`。
- push:成功,`origin/particle/main` 从 `c3033eb5a` 前进至 `d6a784a79`。
- 任务前已有 modified/untracked 文件(包括 STEP-062 文档、历史任务文档、构建日志与 RN 目录)未暂存、未删除、未提交。
## 未执行 / 未完成
- 未执行 Android Gradle、APK、ADB、真机操作或 Jest;未执行 iOS/Metal 验证。
- 未完成 QBIN2 Encoded Ingress/Decoder、Binding/Projection、JNI、Renderer 或 RN 接入,均在本任务禁止范围内。

View File

@@ -0,0 +1,46 @@
# STEP-073 QBIN2 V4 Core 与通用 Field Profile Validator 审计修正完成报告 v3
## 结论
**已完成 / PASS。** 本轮只修复 QBIN2 V4 provider-neutral Core contract 与 Generic Field Profile Validator 的协议一致性遗漏;未进入 Decoder、Binding/Projection、Renderer、JNI、Android、RN 或 V3 生产入口。
## 逐项结果
| 项目 | 结果 | 说明 |
| --- | --- | --- |
| Known Core semantic 固定物理含义 | 已完成 | 静态协议表覆盖 semantic `1`、`2`、`3`、`10`、`11`,同时校验 Contract 和 Frame,错误为 `SemanticContractMismatch`。 |
| Unknown SCALAR 可扩展性 | 已完成 | 未增加 Scalar unit 白名单;opaque semantic/unit 的 Contract/Frame 一致组合仍通过。 |
| Direction Circular Quantization | 已完成 | 通用 value-class 校验固定 DEGREE=4、NONE、group0、UINT16、offset=0、scale=360/65536,并容忍极小 float32 表示误差。 |
| v2 plane identity / DEGREE / HALF | 已完成 | 原有 `(semanticID, groupID)` 唯一性、DEGREE=4 及 ForecastClock HALF 测试继续通过。 |
| V3 定向回归 | 已完成 | FieldDataAdapter、RasterArrayTimeline、HostField 过滤回归通过。 |
| QBIN2 Decoder、Binding/Projection、Renderer、JNI、Android、RN | 未完成(禁止范围) | 本任务未实现或改动。 |
| Android/ADB/Jest/iOS-Metal 验证 | 无法验证(未执行) | 任务明确禁止 Android Gradle、APK、ADB、真机与 Jest;未主张 iOS/Metal 结果。 |
## 自动化测试
新增/修正的 C++ 覆盖确认:
- semantic `1` 的 Contract+Frame 同时伪装为 Scalar 被拒绝;semantic `2` 的错误 component、semantic `3` 的错误 validity descriptor 被拒绝;规则来自 protocol table 而非 profile 名称。
- semantic `10` 的合法 Direction 接受;MPS/unit 错误、U_EAST component、非零 group、非 UINT16 dtype、非 canonical scale 及非零 offset 都拒绝。
- Unknown Scalar 的 opaque unit 继续接受;既有 semantic/group 唯一性、DEGREE=4、ForecastClock HALF 和 V3 回归持续通过。
全部 noisy 命令均通过 `build-output-throttle` 执行:
- `cmake --build build-linux-opengl --target mbgl-test -j3`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:PASS。
- `git diff --check`:PASS。
首次受沙箱构建因既有 ccache 需要写入 `/root/.cache/ccache` 而失败;经授权用完全相同的节流命令重试后 PASS。
## Git 收尾
- 开始 HEAD:`1a453a6623b4fae63b67b604d2bd690b1ebc77ef`。
- Final 实现 HEAD:`4b8d200831ce464b6fbb141785ecf0245e8c26ca`。
- 实现提交:`4b8d200831ce464b6fbb141785ecf0245e8c26ca`,`fix: close STEP-073 QBIN2 V4 validator audit`。本报告、v3 Step 与开发记录以独立 closing docs commit 收尾;两笔提交都只包含本任务允许的文件。
- push:在 closing docs commit 后尝试推送 `origin/particle/main`;最终结果以该次 Git 命令输出为准。
- 任务开始前已有 STEP-062 文档 modified 与历史 untracked 文件保持原样,未暂存、未删除、未提交。
## 停止条件
范围内修正和 Linux 验证已完成。本报告后立即停止,等待 ChatGPT 独立审计,不进入 STEP-074。

View File

@@ -0,0 +1,28 @@
# STEP-073 QBIN2 V4 Core 与通用 Field Profile Validator 完成报告 v4
日期:2026-09-13
## 结果
STEP-073 最终关闭修正完成。Frame Core 现在明确维护 single shared validity invariant;空 numeric plane list 不再被结构层误判。Validator 在 required descriptor 匹配前拒绝实际保留 Core semantic 的物理含义冲突,unknown Scalar opaque 行为与 Direction v3 量化规则保持。
## 实际修改
- `DecodedFieldFrame::isStructurallyValid()` 对 designated validity 强制 `Validity`、`Bitset`、`NONE`、group 0、`None` component、`REQUIRED`、正确 cells/bit payload,并要求 descriptor storage 与 `validityBits` 是同一共享引用;常规 `planes[]` 不允许任何 Validity plane。
- 删除空 `planes[]` 的结构拒绝。于是完整 validity-only Frame 保持结构有效,Generic Validator 对缺失 Profile numeric required plane 返回 `MissingRequiredPlane`。
- 以既有静态协议表先核验 actual planes,故 semantic 10 的 component/group 错误,以及 semantic 1 伪装成 Scalar,都优先返回 `SemanticContractMismatch`,而不是 `MissingRequiredPlane` 或 `PlaneDescriptorMismatch`。
- 测试新增 second validity、designated validity descriptor 各字段、`requiredFlag=false` 与 storage identity 的 fail-closed 覆盖;修正 ForecastClock pause 后的测试恢复播放。
## 独立 runner 验证
`mbgl-test-runner` 本体已重新构建和链接,并通过 `--gtest_list_tests` 确认加载 `ParticleFieldProfileValidator.`、`ParticleFieldRuntime.`。
- V4 filter:实际 `Running 11 tests`,`PASSED 11 tests`,failed=0。
- V3 定向回归:实际 `Running 11 tests`,`PASSED 11 tests`,failed=0。
- `git diff --check`:PASS。
构建/测试输出按节流要求写入 `/tmp` 日志;仓库当前不存在 skill 所指的 `scripts/run_limited.py` helper。首次 sandbox 构建因只读 ccache 失败,获准后以同一受限日志模式重跑并成功,不涉及源代码失败。
## 范围与未完成项
未触及 QBIN2 Decoder、Binding/Projection、Renderer、JNI、Android、RN、Style schema 或 V3 `submitField(...)`。未执行 Android Gradle、APK、ADB、真机、Jest、iOS/Metal。没有范围内未完成项;按任务要求停止,等待独立复验,不进入 STEP-074。

View File

@@ -0,0 +1,13 @@
# STEP-074 QBIN2 V4 Encoded Ingress 与 Decoder Codex 完成报告 v1
## 已完成
- 开始 HEAD:`b8a629b7b65067f7f561db473312484bd8f0451d`;最终 HEAD:待提交记录。任务开始前 dirty/untracked 文件未纳入本任务。
- 独立 ingress decoder、CRC、canonical packing、shared validity、invalid raw-zero 和 decode→contract facade。
- 新增 CMake production/test sources;新增 suites `ParticleQbinV2Decoder` 与 `ParticleEncodedFieldIngress`,共 6 tests。
- 实际重链 `mbgl-test-runner` PASS;新增 filter 实际 Running 6,failed=0;STEP-073 filter 实际 Running 11,failed=0;V3 filter 实际 Running 11,failed=0;`git diff --check` PASS。
## 未完成 / 无法验证
- 受限暂存提交与 push 尚待执行;最终 HEAD/push 结果届时记录。
- 未执行 Android/ADB/Jest/iOS/Metal;范围明确排除 URL/HTTP/gzip/Host/JNI/RN/Renderer/Binding。

View File

@@ -0,0 +1,27 @@
# STEP-074 QBIN2 V4 Encoded Ingress 与 Decoder Codex 完成报告 v2
## 已完成
- 开始 HEAD:`83a84452f23d019cbb143630eac04631552fef89`;任务前已有 dirty/untracked 未纳入。
- production 问题 A:identity 按 raw payload 与 descriptor 的固定 Little Endian canonical bytes 作 FNV;float 以 IEEE754 bits 编码,不依赖 host endian/object representation,也不含 packing offset。
- production 问题 B:限制语义改正为 QBIN 整体 `<= 64 MiB`、Plane raw bytes 总和 `<= 32 MiB`;后者返回 `LimitExceeded`,文件长度错误返回 `LengthMismatch`。
- production 问题 C:BITSET 仅可作为 VALIDITY,VALIDITY 必为 BITSET;accepted 前执行 frame structural self-check。
- fixture builder 明确写 uint16/32/64、float32、float64 的 Little Endian,未成为 production writer。
- corruption matrix、CRC、padding、枚举、时间/边界、capacity、validity 与五种 numeric dtype invalid-cell 场景均进入实际 decoder runner;identity 有固定 golden `75930fed16c969a7`,四 Plane 独立性已验证。
- 513×769 fixture 校验 U/V/validity raw byte-for-byte、canonical payload、共享 validity storage。
- Ingress 覆盖 valid、missing/invalid Contract、missing required plane、known Core conflict、unknown opaque Scalar Contract;未写 `EncodedFieldStore`。
## 验证结果
- Linux OpenGL `mbgl-test-runner` 重新构建及重链:PASS。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:实际 Running 18 tests,18/18 PASS(v1 为 6)。
- STEP-073:11/11 PASS。
- 既有 V3:11/11 PASS。
- `git diff --check`:PASS。
- 禁止范围:未执行 Android Gradle、APK、ADB、真机、Jest、iOS/Metal;未修改 Renderer、Binding、Projection、Store submission、Host、网络或平台目录。
## 未完成 / 无法验证
- 无协议 Core 范围内未完成项。
- Android/iOS 与网络链路明确不在本 Step 范围,未验证且不据此作任何声明。
- 最终 commit HEAD 与 push 结果在 Git 收尾后补充。

View File

@@ -0,0 +1,22 @@
# STEP-074 QBIN2 V4 Encoded Ingress 与 Decoder Codex 完成报告 v3
## 已完成
- 基线为 `56b8dab90f914438e6fcaec6278146c96c8df638`;任务开始前已有 dirty/untracked 内容原样保留,未纳入本任务。
- 新增 `RejectsTooSmallInputBeforeMagic`、`RejectsBadMagicAfterCompleteHeader` 与 `RejectsInvalidRuntimeMetadataButAllowsZeroDataVersion`,所有断言直接调用 production `QbinV2Decoder::decode` 并验证具体 error code;`dataVersion=0` 被明确验证为 Accepted。
- 新增 `RejectsBigEndianHeaderWidthWithValidCrc`:将 width=2 的四字节改为 `00 00 00 02`、重算 CRC 后返回 `LimitExceeded`,没有把它当作 Little Endian width=2。
- 新增 `RejectsMixedEndianDescriptorCountWithValidCrc`:将 descriptor count 的四字节改为 `00 00 00 04`、重算 CRC 后返回 `InvalidDescriptor`。
- 新增 `AcceptsRawLimitWithCanonicalPaddingExcluded`:1447×1449、7 Plane,raw sum=31,712,633,canonical payloadLength=31,712,648;测试显式断言 raw sum `<= 32 MiB`、payload 大于 raw sum 且 decoder Accepted。所有上限仍满足:cells=2,096,703、planes=7、QBIN 小于 64 MiB。
## 验证结果
- Linux OpenGL `mbgl-test-runner` 已实际重建及重链:PASS。
- `mbgl-test-runner --gtest_list_tests`:PASS。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:实际 Running 24 tests,24/24 PASS。
- STEP-073 `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:11/11 PASS。
- 既有 V3 `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:11/11 PASS。
- `git diff --check`:PASS。
## 范围与未完成项
只修改 C++ Decoder tests 与本任务文档;未发现生产缺陷,因此 production Decoder 未改。未触及 Host、网络、JNI、Android/RN、Binding/Projection、Renderer、Store submission、iOS/Metal 或 Jest。上述平台与链路不在本 Step 范围,未验证且不作 PASS 声明。

View File

@@ -0,0 +1,34 @@
# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder Codex 完成报告 v4
日期:2026-09-13
## 结果
已完成:只更新 `test/particle/qbin_v2_decoder.test.cpp` 中的 `AcceptsRawLimitWithCanonicalPaddingExcluded` fixture。新的 1101×1890、17-plane canonical QBIN 的数值为:
- rawSum:33,554,352 bytes。
- 32 MiB limit:33,554,432 bytes。
- payloadLength:33,554,448 bytes。
- totalQbinBytes:33,555,104 bytes。
测试直接断言 `rawSum <= limit`、`payloadLength > limit`、`payloadLength > rawSum` 与 `totalQbinBytes <= 64 MiB`,并调用 production `QbinV2Decoder::decode(...)`,结果 Accepted。
## 逐项状态
- 已完成:边界构造覆盖 canonical padding 将 payload 推过 32 MiB,而 decoded raw sum 未越界。
- 已完成:cells=2,080,890(不超过 2,097,152),planes=17(不超过 32),single shared validity、descriptor 合法与 canonical packing 均成立。
- 已完成:真实重建 `mbgl-test-runner`;Decoder/Ingress Running 24、Runtime/Profile Running 11、Source 回归 Running 11,均 PASS、零失败。
- 已完成:`git diff --check` PASS。
- 未完成:无。
- 无法验证:Android Gradle/APK/ADB/Jest、iOS/Metal;本任务明确禁止执行,且本轮未涉及这些路径。
## 范围审计
未修改 production Decoder,未触及 Host、网络、JNI、RN、Binding/Projection、Renderer 或 Store submission。已有 STEP-062 修改及历史 untracked/build 文件原样保留。
## Git
- 首个 scoped commit:`3274c11a6618a071792fd056680276b9f9868622`(`test: finalize STEP-074 QBIN2 padding boundary`)。
- 首次 push:成功,`origin/particle/main` 已从 `f4a495ff3` 前进至 `3274c11a6`。
- Final HEAD(完成报告记录提交):`bba99f8acfecdfc44e8484785c7a543da173e5b1`(`docs: record STEP-074 delivery`)。
- 最终 push:成功,`origin/particle/main` 与上述 HEAD 一致;本报告之外未暂存既有 STEP-062 修改、任务书或其他 untracked 文件。

View File

@@ -0,0 +1,27 @@
# STEP-075 QBIN2 V4 Binding 与 Projection Identity Codex 完成报告 v1
日期:2026-09-13
## 已完成
- 完成纯 Core 的 Color/Particle binding 与 projection resolver。Host 仅表达 binding intent;Core 仍拥有 requested forecast time 和 per-field window。
- Identity 由 binding selector、实际 T0/T1 window frame coordinate、所选 plane 与 shared validity 的 content identity 组成;SampleState 单独承载 requested time/mix。U/V/validity 未变化时,Scalar-only revision 不改变 Particle identity。
- U/V 强制 VELOCITY_COMPONENT、MPS equality、U_EAST/V_NORTH 与同组;Direction 强制 DIRECTION_ANGLE/NONE/unit equality。所有 lookup/assertion 都 fail-closed,绝不 fallback 到其他 fieldKey/plane。
- 增加 4 个独立 Binding tests:不同 cadence、MIX identity/storage、Scalar-only revision isolation、错误矩阵和 DirectionAngle。
## 验证结果
- `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:PASS(真实重建,输出节流)。
- 规定 GTest filter:50 tests / 0 failures / 0 errors;Binding 4、Runtime/Profile 11、QBIN Decoder/Ingress 24、V3 Source 11。
- `git diff --check`:PASS。
## 未完成或无法验证
- Android Gradle/APK/ADB/Jest、iOS/Metal、Renderer/GPU、Host/JNI/RN 均按任务禁止或范围限制未执行,不能标记 PASS。
- 不可插值相邻帧由既有 FieldSeries submit 拒绝,因此合法 Store 不会产生该 resolve window;Resolver 仍保留 `InterpolationIncompatible` 防御返回。
## Git
- 首个 scoped commit:`c465b804c`,`feat: add V4 field binding projection identities`。
- 首次 push:成功,`31783f25a..c465b804c particle/main -> particle/main`。
- 实现提交后的 `origin/particle/main` HEAD:`c465b804c`。本报告 Git 字段回填将作为独立 docs-only commit 再次推送。

View File

@@ -0,0 +1,26 @@
# STEP-075 QBIN2 V4 Binding 与 Projection Identity Codex 完成报告 v2
日期:2026-09-13
## 已完成
- Projection content identity 改为显式 `ProjectionFrameCoordinate(fieldID, validTimeMs)` 加上 binding 实际消费的 T0/T1 selected plane 和 shared validity content identity。完整 generation/dataVersion 不再污染任一 projection identity。
- 对称 revision 隔离已由 Store 的 `ReplacedRevision` 公共路径验证:Scalar-only 仅改变 Color,U/V-only 仅改变 Particle,validity-only 同时改变 Color 与 Particle。
- `InterpolationIncompatible` 已从 Resolver 公共错误语义移除;FieldSeries submit 已 fail-closed 拒绝不兼容相邻 frame,合法公开 resolve 路径不可形成该状态。
- 独立 fieldKey/cadence、MIX identity/storage stability、DirectionAngle success/failure 与既有 V4/V3 回归均保持。
## 验证结果
- Linux OpenGL `mbgl-test-runner` 真实重建 PASS(输出节流)。
- 规定 GTest XML:50 tests、0 failures、0 errors;Binding 4、Runtime/Profile 11、QBIN Decoder/Ingress 24、V3 Source 11。
- `git diff --check` PASS。
## 未完成或无法验证
- Android Gradle/APK/ADB/Jest、iOS/Metal、Renderer/GPU、Host/JNI/RN、网络和真实 QBIN Golden 数据均按任务禁止或不在范围内,未执行且不能标记 PASS。
- 无已知实现阻塞;本任务不进入 STEP-076。
## Git
- scoped commit:`90d05f596a42c157502f9a2a5f20425c999cebab`,`fix: close STEP-075 projection identity audit`。
- push 成功:`3616daec3..90d05f596 particle/main -> particle/main`。

View File

@@ -0,0 +1,29 @@
# STEP-075 QBIN2 V4 Binding 与 Projection Identity Codex 完成报告 v3
日期:2026-09-13
## 已完成
- `ParticleBinding` equality 现严格按 mode 比较实际消费的 selector,inactive selector 不再污染 Projection content identity。
- VectorComponents 的 inactive `directionPlane` 与 DirectionAngle 的 inactive U/V selector 都已由离线单元测试证明不会改变 binding equality 或 resolved `ParticleProjectionContentIdentity`。
- 两种 mode 的 active selector 变更仍使 binding 不同,并经缺失 selector 的 `PlaneMissing` 路径 fail-closed。
- selector identity 仍限定为 `(semanticID, groupID)`,component 仍只参与 selected-plane descriptor assertion。
## 验证结果
- Linux OpenGL `mbgl-test-runner` 真实重建 PASS,且所有命令经全局 build-output-throttle 执行。
- Binding 6/6、Runtime/Profile 11/11、QBIN/Ingress 24/24、Source 11/11;每组均为 0 failure。
- `git diff --check` PASS。
## 未完成或未验证
- Android Gradle/APK/ADB/Jest、iOS/Metal、Renderer/GPU、Host/JNI/RN、网络及真实 QBIN 均按任务禁止或不在范围内,未执行,不能标记 PASS。
- 无实现阻塞;本任务不进入 STEP-076。
## Git
- scoped commit:`2645bd725f63c4da12ab48a0cc11ff034c9d9878`。
- message:`fix: close STEP-075 binding selector identity audit`。
- push result:`653e6f2f1..2645bd725 particle/main -> particle/main`(成功)。
- remote branch:`origin/particle/main`。
- Final HEAD(scoped implementation delivery):`2645bd725f63c4da12ab48a0cc11ff034c9d9878`。

View File

@@ -0,0 +1,28 @@
# STEP-076 QBIN2 V4 Core Host API 与 RasterArraySource 接线 Codex 完成报告 v1
## 结果
已完成 V4 QBIN Host API 到 `RasterArraySource` 的隔离接线。raw QBIN bytes 经 metadata/key 检查、decode、contract validate 后才进入按 `fieldKey` 独立维护的 `EncodedFieldStore`。Color 与 Particle binding 保持独立 intent,并通过同一 `ForecastClock` 在 resolve 时产生各自 window/mix;本 Step 没有让 renderer 消费 V4 projection,也没有改变 V3 host/timeline 行为。
## 开始基线与修改文件
- 开始 HEAD:`b148fb6a0c6711182194f6778d80c8f8a60eb037`。
- 修改:`include/mbgl/style/sources/raster_array_source.hpp`、`src/mbgl/style/sources/raster_array_source.cpp`、`test/CMakeLists.txt`、`test/source/raster_array_v4_host_api.test.cpp`、本 Step、本文报告和 `粒子开发记录.md`。
- 历史 STEP-062 dirty/untracked/build 输出未暂存或提交。
## 验证结果
| Filter | Running | Failure |
| --- | ---: | ---: |
| `Source.RasterArrayV4HostApi*` | 3 | 0 |
| `ParticleFieldBinding.*` | 6 | 0 |
| `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*` | 11 | 0 |
| `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*` | 24 | 0 |
| `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*` | 11 | 0 |
真实 Linux OpenGL `mbgl-test-runner` 已重建;所有命令经全局 build-output-throttle,`git diff --check` PASS。未执行 Android Gradle/APK/ADB/Jest、iOS/Metal。
## Git 收尾
- scoped commit、push result、`origin/particle/main` 远端和 Final HEAD 以本次提交/推送的实际输出为准,未混入任务前内容。
- 未完成/阻塞:无;按任务边界停止,不进入 Android/JNI/RN bridge。

View File

@@ -0,0 +1,35 @@
# STEP-076 QBIN2 V4 Core Host API 与 RasterArraySource 接线 Codex 完成报告 v2
## 结果
已完成 v1 独立审计指出的测试证明缺口,未改变任何 production API 或架构。四种指定 V4 reject 都在公共 `RasterArraySource` resolve 路径上证明无 partial mutation;V4 clear 已在真实可观察的 V3 初始窗口存在时,证明不影响 V3 `field/nextField`,且 V4 binding intent 可在重新提交后继续 resolve。
## 基线与修改
- 开始 HEAD:`ae03d8bfd2813eb5e65074db16018e37c7cff1c4`。
- 实际 production 修改:无。
- 实际修改文件:`test/source/raster_array_v4_host_api.test.cpp`、`docs/50_Step记录/STEP-076_QBIN2_V4_CoreHostAPI与RasterArraySource接线_v2.md`、`docs/报告/STEP-076_QBIN2_V4_CoreHostAPI与RasterArraySource接线_Codex完成报告_v2.md`、`粒子开发记录.md`。
- 开始时已有 STEP-062 dirty、历史 untracked 与 build 输出均保持原样;本次 scoped commit 不包含它们。
## 测试证明
- `RejectedDecode`、`RejectedContract`、`RejectedInvalidFieldKey`、`RejectedStaleRevision`:每次 reject 前后 public Particle projection 均可 resolve;比较了 projection content identity、T0/T1 coordinate、两条 selected plane storage 和 validity storage identity。
- `clearEncodedField(...)` 与 `clearAllEncodedFields()`:先用公开 V3 `submitInitialRuntimeFieldWindow(...)` 建立窗口,clear 后 V3 field/nextField 均存在,identity/generation/validTime 不变;V4 resolve 均为 fail-closed;重新提交 V4 后 Color/Particle binding 都可 resolve。
- V4 suite 保留原 3 个并新增第 4 个测试;未使用旧 `clearHostFields()`,未读取 V4 Store 私有成员。
| Filter | 实际 Running | Failure |
| --- | ---: | ---: |
| `Source.RasterArrayV4HostApi*` | 4 | 0 |
| `ParticleFieldBinding.*` | 6 | 0 |
| `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*` | 11 | 0 |
| `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*` | 24 | 0 |
| `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*` | 11 | 0 |
真实执行 `cmake --build build-linux-opengl --target mbgl-test-runner -j3` 并 PASS;所有 noisy build/test 均经全局 `build-output-throttle`。`git diff --check` PASS。
## 范围与 Git 收尾
- 未执行 Android 编译/Gradle/APK/ADB/实体机,也未执行 Jest 或 iOS/Metal。
- 未修改 Renderer、shader、JNI、Android、RN、Test Host、NavSea、QBIN decoder 或 production Field Runtime/Binding。
- scoped commit message:`test: close STEP-076 V4 host API audit`;push target:`origin/particle/main`。实际 commit hash、push result、remote branch 和 Final HEAD 以紧随本报告的 scoped Git 收尾输出为准。
- 未完成/阻塞:无;完成后停止,不进入下一 Step。

View File

@@ -0,0 +1,35 @@
# STEP-077 QBIN2 V4 Android / JNI / RN 桥接调查 Codex 完成报告 v1
## 结果
已完成只读架构调查并形成 V4 桥接实施边界。推荐 V4 新建与 V3 平行的 encoded-field bridge:RN 传本地 `qbinFileUri`,Kotlin 有界读取为 `byte[]`,JNI copy 后调用 `RasterArraySource` V4 Host API;小型 contract 用受 schema 限制的 control-plane JSON,其余 binding/time/playback 使用 typed primitive。Host 不控制 T0/T1/MIX。
现有 V3 `submitField` 保留。V3 的 `clearFields`、`setForecastTime`、`setPlaybackState`、`setPlaybackSpeed` 直接走旧 Host-field/timeline,不能承载 V4;V4 必须有 `clearEncoded*` 与独立 forecast 命名空间,避免误操作 V3 timeline。
## 实际调查范围
- 逐层审计 `FieldRuntime.ts`、`NativeWeatherRuntimeBridge.ts`、`types.ts`、`WeatherRuntimeBridgeModule.kt`、`WeatherRuntime.java`、`MapLibreMap.java`、`NativeMapView.java`、`native_map_view.hpp/.cpp` 与 `RasterArraySource` V4 API。
- 确认 Fabric tag 每次在 UI queue 解析到当前 `MLRNMapView -> MapLibreMap`;不新增 registry,不缓存 map/source pointer。style reload、detach 或 dispose 后应以明确 lifecycle error 拒绝,Host 重 attach 后重新提交。
- 列出十项 `RasterArrayEncodedFieldSubmitResult` 到 RN resolved result 的一一映射,并与文件、参数、runtime、style/source 错误分层。
- 调查 Linux 静态入口:package/Host TypeScript、TurboModule codegen config、C++/JNI 精确编译、以及现有 Mockito `WeatherRuntimeStateTest` 的 JVM facade 边界;未执行 Android Gradle/APK/ADB/实体机。
完整结论、API 表、参数 ABI、风险与后续三段实施建议见 [调查报告](../20_调查/STEP-077_QBIN2_V4_Android_JNI_RN桥接调查报告_v1.md)。
## 修改与验证
- production code、package version、Test Host、Renderer/GPU:均未修改。
- 新增调查报告与 STEP 记录;将任务状态更新为完成;追加 `粒子开发记录.md`。
- `git diff --check`:PASS(本任务文档)。
- 没有执行 noisy build/test,因此没有 Android/Gradle、APK、ADB、真机、真实 QBIN、Jest 或 iOS/Metal 结果可报告。
## Git 收尾
- 文档调查提交:`e0088a5f0873ed95a902a9402683ffdd0dcc155c`
- 提交信息:`docs: complete STEP-077 V4 bridge investigation`
- 首次 push 结果:`0759a0610..e0088a5f0 particle/main -> particle/main`
- remote branch:`origin/particle/main`
- 本完成报告作为 Git 元数据收尾另行 scoped commit 并紧随推送;其最终 HEAD 以交付时 `git rev-parse HEAD` 为准。
## 未完成/阻塞
无调查阻塞。桥接实现、Android 编译与实体机验证都刻意不属于本 Step;按任务书在本报告提交推送后立即停止,不进入 STEP-077 实现。

View File

@@ -0,0 +1,35 @@
# STEP-078 QBIN2 V4 Android / JNI / RN 桥接实现 Codex 完成报告 v2
## 结果
V4 bridge 已完整闭环并与 V3 隔离。v1 保留了 encoded QBIN submit、local-file guard、V4 clear/time 骨架及 Java/JNI transport;本次修正了 contract 字段、Color unit assertion、mode-aware Particle selector,并拆分 forecast state/speed。binding 不再是占位 rejection。
## 设计与错误边界
- `contractJson` 有唯一的 provider-neutral C++ parser;其只把 frozen control-plane JSON 转为 `ResolvedFieldProfileContract`,随后仍由 Core structural validation 和 `FieldProfileValidator` 裁决。
- Color 固定传递 `fieldKey/(semanticID,groupID)/expectedValueClass/expectedUnitID`。
- Particle 固定 primitive ABI slots 为 U、V、direction;active selector 要求 uint16 范围与非零 semantic,inactive selector 只能为零对。
- V4 Core result 保持十种稳定字符串。JNI 的 `0` 仅为 source missing/type mismatch 私有 sentinel,最终成为 `SOURCE_MISSING` rejection;不占用 Core enum。
- state 只调 Core playback state,speed 只调 Core playback speed。
## 实际修改文件
`CMakeLists.txt`、`test/CMakeLists.txt`、新增 parser header/source/test;`packages/maplibre-particle-rn/src/{FieldRuntime.ts,NativeWeatherRuntimeBridge.ts,types.ts}`;Kotlin `WeatherRuntimeBridgeModule.kt`;Android Java `WeatherRuntime.java`、`MapLibreMap.java`、`NativeMap.java`、`NativeMapView.java`;以及 `native_map_view.hpp/.cpp`;本 Step 记录、本报告和`粒子开发记录.md`。
本次没有修改 Renderer/GPU/shader、Test Host、NavSea 或 STEP-073~076 已冻结 Core 业务语义。任务开始前 STEP-062 dirty、历史 untracked、`.codex-build-logs/`、generated/android/.expo 和 tgz 均没有纳入暂存或提交。
## Linux 验证
- `particle-codex` 中限流构建 `mbgl-test-runner`:PASS。
- `ParticleFieldProfileContractJson.*`、Source V4、Binding、QBIN/Ingress、Runtime/Profile、V3 Source 定向过滤:PASS。
- package TypeScript:PASS;TurboModule schema combine/codegen:PASS。
- JNI Java declaration ↔ C++ registration 静态 name-set:PASS。
- `git diff --check`:PASS。
所有 noisy build/test 都使用全局 `build-output-throttle`,日志放在 `/tmp/step078-build-logs`,没有触碰受保护的既有 build 输出。
## Android 未验证与 Git 收尾
Android Kotlin/Java/JNI 的真实编译、Gradle、APK、ADB 和实体机均按任务要求未执行,须在后续一次性 Android 验收中完成;iOS/Metal 与 Jest 亦未执行。无实现阻塞。
scoped commit 的 hash、message、push result、remote branch 与 Final HEAD 在本 Step Git 收尾命令完成后以该命令输出为准;目标为 `origin/particle/main`。完成后立即停止,不进入 STEP-079。

View File

@@ -0,0 +1,30 @@
# STEP-078 QBIN2 V4 Android / JNI / RN 桥接实现 Codex 完成报告 v3
## 结果
已按 `73779b89bcce1094eb344f4727d6d23904f667f3` 基线关闭审计发现的两个 production guard 缺口,未重构已通过的 V4 bridge,未进入 STEP-079。
QBIN transport 现在在 Kotlin local-file、Java facade、JNI copy 三处均为 1..64 MiB;JNI 的检查在 `std::vector` 构造之前,直接 JNI caller 的空/超限数组 fail-closed。该 transport guard 是独立 helper,未混入 QBIN decoder:decoder 自有的 64 MiB 文件与 32 MiB decoded raw limits 不变。
RN/TS 以 `TextEncoder`、Kotlin 以 `toByteArray(Charsets.UTF_8)`、Java 以 `StandardCharsets.UTF_8` 执行相同的 1..64 UTF-8 bytes guard,覆盖 fieldKey、runtime fieldID、profileID。profileID 的 lowercase ASCII structural contract 仍由 Core parser/validator 最终裁决;未统一或放宽其它 ID 字符集。contractJson 仍为既有 256 KiB UTF-8 上限。
Java facade 亦在解析 profileID 前保留 `contractJson` 的 256 KiB UTF-8 transport/control-plane 上限,避免直接 Java caller 绕开 Kotlin seam 扩大既有边界。
## 验证结果
- `particle-codex` tmux session 内所有 noisy build/test 均经 `build-output-throttle`:package TypeScript、TS 多字节 fail-fast directed test、TurboModule combine/codegen、Linux `mbgl-test-runner` rebuild 与规定 V4/V3 filter 均 PASS。
- 新 C++ `ParticleEncodedFieldTransport` test 证明 0/64 MiB+1 拒绝、64 MiB 接受,且 helper 在 JNI vector copy 之前调用。
- 新 JDK-only Java guard test 证明 Java facade 采用的 helper 接受 64 MiB、拒绝 0 和 64 MiB+1,并拒绝 33 CJK 字符(99 UTF-8 bytes)。Kotlin 与 Java source 的 UTF-8 guard 已定向静态核对。
- Java JNI declaration 与 C++ registration 的 V4 name-set 静态核对 PASS;`git diff --check` PASS。
## 未完成与边界
Android Kotlin/Java/JNI 的真实编译、Gradle、APK、安装、ADB 和实体机验证均按任务要求未执行,留待后续一次性 Android 验收;iOS/Metal 与 Jest 也未执行。没有读取真实服务器 QBIN,未改 Renderer/GPU/shader、Test Host、NavSea 或 STEP-073~076 冻结 Core 语义。
任务开始前的 STEP-062 dirty、历史 untracked、`.codex-build-logs/`、Test Host generated/android/.expo 和 tgz 未纳入本次提交。scoped commit、push result、remote branch 与 Final HEAD 记录于本报告的 Git 收尾更新及 Step 记录;完成后停止,不进入 STEP-079。
## Git 收尾
- 实现 commit:`75a974e77503c56ec5bd2823ac1b689225ed80a7`,message:`fix: close STEP-078 V4 bridge guard audit`。
- push result:PASS,`73779b89b..75a974e77 particle/main -> particle/main`;remote branch:`origin/particle/main`。
- 本报告与 Step 的 Git 收尾补充会以独立、仅文档的 STEP-078 scoped commit 推送;该 commit 的 Final HEAD 在提交完成后报告,并立即停止。

View File

@@ -0,0 +1,34 @@
# STEP-078 QBIN2 V4 ColorBinding VELOCITY_COMPONENT 接口修订 Codex 完成报告 v4
## 结果
已完成最小、向后兼容的 public API 修订:RN `FieldRuntime.setEncodedColorBinding(...)` 现在可传递 Core 已支持的 `VELOCITY_COMPONENT`。传输路径为 TS string → Kotlin `1` → JNI/Core `FieldValueClass::VelocityComponent`,不增加 TurboModule 参数,也不改变 Java 透传方法形状。
`VALIDITY` 没有被开放为 ColorBinding:TS/Kotlin 均拒绝该字符串,JNI 仅接受 `0..2`,Core 回归继续证明它返回 `InvalidBinding`。未实现 vector magnitude、单位换算或任何 provider/weather 特例。
## 修改与证明
- TS 类型与 public validation 加入 `VELOCITY_COMPONENT`。
- Kotlin bridge 映射 `SCALAR=0`、`VELOCITY_COMPONENT=1`、`DIRECTION_ANGLE=2`。
- JNI guard 从仅 `0/2` 收敛为合法连续值域 `0..2`,故 `3` 及其它非法值仍拒绝。
- `ParticleFieldBinding.FailsClosedAndSupportsDirectionAngle` 增加真实 velocity component plane 的 accepted projection/storage identity 断言,另加 Validity fail-closed 断言。
## 验证结果
在 `particle-codex` tmux session 内,所有 typecheck、codegen、build 与 test 命令均经 `build-output-throttle`,8 个限流日志完整产生于 `/tmp/step078v4-build-logs`。package TypeScript typecheck、TurboModule schema combine/codegen、Linux OpenGL `mbgl-test-runner` rebuild,及 `ParticleFieldBinding.*`、Source V4 Host API、QBIN/Ingress、Runtime/Profile、V3 Source 指定回归均 PASS。
此外,Java `NativeMapView` 的 6 参数 native declaration、C++ `NativeMapView` member signature 与 `METHOD` registration 已静态核对一致;task-scope `git diff --check` PASS。
## 未完成与边界
按任务要求未运行 Android Gradle、APK、install、ADB、实体机或模拟器;Android Kotlin/Java/JNI 的真实编译和 Windows 11 Test Host 验收仍待用户执行。Jest、iOS/Metal 也未验证。既有 STEP-062 dirty、历史 untracked、workspace `.codex-build-logs/` 与其它无关内容均未纳入本任务。
未进入 STEP-079;它必须由 ChatGPT 独立复验本修订后再启动。
## Git 收尾
- implementation commit hash:`e02353820aafa1834ce3a2ab3afe9261fadd83bc`。
- commit message:`fix: expose V4 velocity component color binding`。
- push result:PASS,`dd1d70147..e02353820 particle/main -> particle/main`。
- remote branch:`origin/particle/main`。
- Final HEAD(implementation scoped commit):`e02353820aafa1834ce3a2ab3afe9261fadd83bc`。本报告、Step 记录和开发记录随后使用独立、仅文档 scoped commit 收尾并 push;该提交完成即停止。

View File

@@ -0,0 +1,50 @@
# STEP-079 真实 QBIN Golden Fixture 接入 RN Test Host Codex 完成报告 v2
## 结果
状态:**IMPLEMENTED / WAITING FOR WINDOWS 11 ANDROID VALIDATION**。
RN Test Host 现保留 V3 功能并新增独立 V4 QBIN Golden 区。8 个冻结 raw QBIN 已随 Test Host 打包,JS 不接收 payload bytes、数组、Base64 或 URL;只由 `expo-asset` materialize 为本地 `file://` URI 并调用冻结的 `FieldRuntime.submitEncodedField(...)`。
Wind/Current 的真实 U/V descriptor 以 `VELOCITY_COMPONENT` 传入 ColorBinding,Wave/Rain 以各自真实 scalar semantic 传入;Wave direction 才作为 ParticleBinding,Rain 没有虚构粒子数据。load 按 clear → binding → T0 → T1 → midpoint 运行,T0/T1 必须分别返回 `ACCEPTED_INSERTED`,否则界面显示实际结果并失败。midpoint 只证明 V4 seek command accepted,不能证明或显示 mix=0.5。
## 已完成
- 冻结来源 `/root/sourceserver/weatherV2/_artifacts/MapLibre_QBIN_V2_单元测试数据_v1/` 的 8 个 raw fixture 已复制至 Test Host 自有 assets;开发 verifier 对 source 与 copy 的 size/SHA-256 全部 PASS。
- V4 manifest 保留 profile contract、runtime identity、validTime、midpoint、source path、size/SHA-256 和真实 binding;运行时没有 source-server 依赖。
- Metro 最小追加 `.qbin`,V3 UI/transport 没有移除或改成 V4-only。
- Test Host typecheck、package typecheck、TurboModule schema combine/codegen、Linux OpenGL runner target、Contract JSON / Transport / Source V4 / Binding / QBIN-Ingress / Runtime-Profile / V3 Source 回归和 `git diff --check` 均 PASS。
## 未完成与无法验证
- 未执行 Android Gradle、APK build、install、ADB、模拟器、实体机或 Jest;也未验证 iOS/Metal。
- 因任务明确禁止 Linux Android 操作,无法声明 Android PASS,也无法在此环境观察 V4 画面、实际 decode/submit、binding、midpoint、clear 或 PLAY/PAUSE 的设备结果。
- 任务开始前既有 STEP-062 dirty、历史 untracked、`.codex-build-logs/`、generated Android/Expo/modules、tgz 与旧任务书均未纳入提交。
## Windows 11 Android 手工验收
在 Windows checkout 的 `particle/main`,由用户执行(路径与 APK 产物名如与本地不同,以实际生成工程为准):
```powershell
git fetch origin
git checkout particle/main
git pull --ff-only origin particle/main
cd integration/react-native/MapLibreParticleRNTestHost
npm ci
npx expo prebuild --platform android --clean
npx expo run:android
adb -s 192.168.1.146 reverse tcp:8091 tcp:8091
adb -s 192.168.1.146 reverse tcp:8081 tcp:8091
adb -s 192.168.1.146 shell am start -n org.maplibre.particle.rntesthost/.MainActivity
```
如选择手工 APK 安装,须使用实际 APK 路径且显式指定同一设备:`adb -s 192.168.1.146 install -r <实际-debug-apk路径>`。
观察项:依次选择 Wind/Wave/Current/Rain 并 load;每类均确认 T0/T1 为 `ACCEPTED_INSERTED`、Color/Particle binding 为 `ACCEPTED`(Rain Particle=N/A)、midpoint 仅显示 command accepted、T0/T1 seek、V4 PLAY/PAUSE/speed 和 clear selected/all 均返回 `ACCEPTED`。检查 Metro/Android logcat 无 QBIN 读取、decode、contract 或 binding rejection;不得以画面存在替代以上文字结果。Android build/install/ADB 结果由用户记录后才可更新此状态。
## Git 收尾
- implementation commit hash:`f1ea88b0ce9c6995f05a8d2ae2eb2696d72e5f28`。
- commit message:`feat: add V4 QBIN golden fixture test host`。
- remote branch:`origin/particle/main`。
- implementation Final HEAD:`f1ea88b0ce9c6995f05a8d2ae2eb2696d72e5f28`。本报告、Step 记录和开发记录随后使用仅文档 scoped commit 推送;push result 和收尾 Final HEAD 以该次 Git 输出为准,完成后立即停止。

View File

@@ -0,0 +1,35 @@
# STEP-079 真实 QBIN Golden Fixture 接入 RN Test Host 接口阻塞报告 v1
状态:阻塞(未实现,未执行 Android 验收)
## 结论
STEP-078 的冻结 V4 RN public API 无法表达真实 Wind/Current U 或 V plane 的正确 ColorBinding。因此本 Step 依照任务的停止条款退出,没有以 Test Host wrapper、错误 enum 或 Core/API 修改绕过。
## 可复核证据
冻结 WeatherV2 fixture manifest/profile registry 规定:
| 数据集 | 要提交的 Color plane | fixture descriptor |
| --- | --- | --- |
| Wind `wind/1` | U 或 V | semantic `1`/`2`,group `1`,unit `1`,`VELOCITY_COMPONENT` |
| Current `current/1` | U 或 V | semantic `1`/`2`,group `1`,unit `1`,`VELOCITY_COMPONENT` |
冻结 RN API `packages/maplibre-particle-rn/src/FieldRuntime.ts` 的 `setEncodedColorBinding` 只接受 `SCALAR`、`DIRECTION_ANGLE`;Android `WeatherRuntimeBridgeModule.kt` 也只将这两个字符串映射为 native value class。它没有 `VELOCITY_COMPONENT` 的 public enum 通路。
Core 并非可以安全忽略该差异:`src/mbgl/particle/field/field_binding.cpp` 会对 ColorBinding 的 selected T0/T1 plane 验证 value class。以 `SCALAR` 提交真实 U/V 必返回 `PlaneClassMismatch`。把 value class 伪造为 scalar 既不是 fixture-derived binding,也不会形成有效 V4 bridge 验证。
Wave(scalar Height)与 Rain(scalar precipitation)没有此问题;Wave 的 DirectionAngle particle binding 与 Wind/Current 的 VectorComponents particle binding 也均有冻结 public API 表达。但任务要求同一 Test Host V4 dataset contract 至少包含 ColorBinding,且固定规则特别允许 Wind/Current U/V 的 ColorBinding;缺少该 enum 使四组完整合同无法成立。
## 未执行的内容
- 未保留或提交 8 个 copied raw QBIN fixture,避免仓库出现无法被完整 Test Host contract 使用的半成品。
- 未创建 V4 dataset/materialize/UI;未修改 V3 UI 或 Test Host Metro。
- 未运行 TypeScript、TurboModule codegen、C++ rebuild/回归、Android Gradle/APK/ADB,因没有可验证的合规实现。
- 未创建实现提交;没有 fixture、Test Host 或 package/Core 改动。阻塞记录已以 `afcc74066f1395678621fa2b1a70d10bc722a623`(`docs: record STEP-079 V4 fixture API blocker`)推送到 `origin/particle/main`。
## 最小解阻条件
需要新的、显式授权的 API 修订 Step:令 `EncodedColorBindingInput.expectedValueClass`、RN validation、TurboModule spec、Kotlin enum mapping 以同一冻结 ABI 支持 `VELOCITY_COMPONENT`,并增加相应的 Core/bridge contract tests。该 Step 必须单独决定 Renderer 是否仅 bridge-only;本报告不建议把 vector magnitude 伪造为 color input,也不建议在 Test Host 绕过 public API。
在该修订完成且稳定后,应重新从本任务指定 fixture source materialize 8 个 `.qbin`,进行完整 source/size/SHA-256 审计,并执行本任务要求的 Linux static validation。Windows 11 Android 验收目前没有可交付的 V4 Test Host,因此不能提供安装/ADB 验收步骤或宣称 Android PASS。

Some files were not shown because too many files have changed in this diff Show More