From 11cf58d344e64e6aff77deceb2c34c8eda045f1b Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Fri, 11 Sep 2026 12:42:49 +0800 Subject: [PATCH] chore: prepare V3-08 android validation --- ...RNTestHost移除Legacy双Bridge_Codex任务_v1.md | 50 ++++ ..._Native_v2与单次综合验收准备_Codex任务_v1.md | 81 +++++++ ...08_Android_Native_v2与单次综合验收准备_v1.md | 40 ++++ .../STEP-072_V3-08_Android单次综合验收_v1.md | 108 +++++++++ ...ive_v2与单次综合验收准备_Codex完成报告_v1.md | 40 ++++ .../MapLibreParticleRNTestHost/README.md | 12 +- .../android/build.gradle | 25 -- .../android/src/main/AndroidManifest.xml | 1 - .../weather/WeatherRuntimeBridgeModule.kt | 225 ------------------ .../weather/WeatherRuntimeBridgePackage.kt | 20 -- .../weather-runtime-bridge/package.json | 14 -- .../react-native.config.js | 1 - .../src/NativeWeatherRuntimeBridge.ts | 37 --- .../weather-runtime-bridge/src/index.ts | 10 - .../plugins/withParticleNativeAndroid.js | 2 +- .../react-native.config.js | 7 - .../MapLibreParticleRNTestHost/tsconfig.json | 2 +- packages/maplibre-particle-rn/CHANGELOG.md | 3 +- packages/maplibre-particle-rn/README.md | 6 +- .../maplibre-particle-rn/android/build.gradle | 2 +- packages/maplibre-particle-rn/app.plugin.js | 2 +- platform/android/VERSION | 2 +- .../maplibre-particle/release-manifest.json | 85 ++++--- 粒子开发记录.md | 6 + 24 files changed, 383 insertions(+), 398 deletions(-) create mode 100644 docs/40_任务/STEP-072A_V3-08_RNTestHost移除Legacy双Bridge_Codex任务_v1.md create mode 100644 docs/40_任务/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_Codex任务_v1.md create mode 100644 docs/50_Step记录/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_v1.md create mode 100644 docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md create mode 100644 docs/报告/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_Codex完成报告_v1.md delete mode 100644 integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle delete mode 100644 integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/AndroidManifest.xml delete mode 100644 integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt delete mode 100644 integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgePackage.kt delete mode 100644 integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/package.json delete mode 100644 integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/react-native.config.js delete mode 100644 integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/src/NativeWeatherRuntimeBridge.ts delete mode 100644 integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/src/index.ts delete mode 100644 integration/react-native/MapLibreParticleRNTestHost/react-native.config.js diff --git a/docs/40_任务/STEP-072A_V3-08_RNTestHost移除Legacy双Bridge_Codex任务_v1.md b/docs/40_任务/STEP-072A_V3-08_RNTestHost移除Legacy双Bridge_Codex任务_v1.md new file mode 100644 index 000000000..e07e6a322 --- /dev/null +++ b/docs/40_任务/STEP-072A_V3-08_RNTestHost移除Legacy双Bridge_Codex任务_v1.md @@ -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 验证;完成文件修改后停止。 \ No newline at end of file diff --git a/docs/40_任务/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_Codex任务_v1.md b/docs/40_任务/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_Codex任务_v1.md new file mode 100644 index 000000000..328dfdb7e --- /dev/null +++ b/docs/40_任务/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_Codex任务_v1.md @@ -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 未执行”。 diff --git a/docs/50_Step记录/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_v1.md b/docs/50_Step记录/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_v1.md new file mode 100644 index 000000000..e01ba6eb1 --- /dev/null +++ b/docs/50_Step记录/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_v1.md @@ -0,0 +1,40 @@ +# 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 五组场数据。 \ No newline at end of file diff --git a/docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md b/docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md new file mode 100644 index 000000000..9c0a19e12 --- /dev/null +++ b/docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md @@ -0,0 +1,108 @@ +# STEP-072 V3-08 Android 单次综合验收 + +## 状态与边界 + +本文件只列出用户手工执行的单次 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 -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 验收状态;本文件本身不代表已执行或通过。 diff --git a/docs/报告/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_Codex完成报告_v1.md b/docs/报告/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_Codex完成报告_v1.md new file mode 100644 index 000000000..9623b8b48 --- /dev/null +++ b/docs/报告/STEP-072_V3-08_Android_Native_v2与单次综合验收准备_Codex完成报告_v1.md @@ -0,0 +1,40 @@ +# 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 +- 实现/准备提交:待本报告后的 scoped commit 回填。 +- 提交信息:待回填。 +- Push:待回填。 +- Remote branch:`origin/particle/main`。 +- Final HEAD:待回填。 + +## 下一步 +仅由用户执行 V3-08 单次 Android 综合验收。验收通过前 release manifest 保持 `awaiting_android_manual_build_publish_acceptance` / Android `not_run`,不得把 `.3` 标记 released 或创建 release tag。 \ No newline at end of file diff --git a/integration/react-native/MapLibreParticleRNTestHost/README.md b/integration/react-native/MapLibreParticleRNTestHost/README.md index b3085ceac..63a9021b8 100644 --- a/integration/react-native/MapLibreParticleRNTestHost/README.md +++ b/integration/react-native/MapLibreParticleRNTestHost/README.md @@ -2,14 +2,18 @@ 这是 MapLibreParticleLab 内的独立 React Native 开发宿主,不是 NavSea 业务应用。它只有一个 `MLRNMapView` 与 WeatherRuntime 控制页,用于证明低频控制面:React 的 `reactTag` 经薄 Android 模块定位该 View,再定位该 View 的 `MapLibreMap` 与 map-owned `WeatherRuntime`。 -## 版本与首次 prebuild 边界 +## 当前 V3-08 版本与首次 prebuild 边界 + +- 当前验收目标是 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 新 artifact;不得复用或覆盖 `.1`。 +- 本地 legacy `@maplibre-particle/weather-runtime-bridge` 已移除;`@navsea/maplibre-particle-rn@13.4.1-particle.3` 是唯一的 `WeatherRuntimeBridge` 提供者。 +- 必须先由用户手工构建并 Maven Local publish Native `.2`,再对 Test Host 执行 clean prebuild、build、install 与启动。当前仅完成坐标准备,AAR 尚未构建、发布或验收。 +- Metro 当前 binary asset extension 为 `.f32` 与 `.u8`。 - package manager 为 npm(`packageManager: npm@10.9.4`),锁文件为 `package-lock.json`;当前环境的 `npm ci --dry-run --ignore-scripts` 无须 `--legacy-peer-deps` 即可解析。 - package 明确固定 React Native `0.85.2`、React `19.2.3`、Expo `56.0.4`、`expo-file-system` `56.0.11`、TypeScript `5.9.3`、MLRN `11.3.6`;`app.json` 明确请求 New Architecture。Hermes 的最终启用状态必须在首次 Android prebuild 后检查生成工程。 - AGP `8.5.0`、Gradle `8.14.3`、Kotlin `2.1.20`、NDK `27.1.12297006`、JDK 17、minSdk 24、compile/targetSdk 36 是本 Test Host 的**人工验收配置目标**,不是尚未生成 Android 工程的已验证结果。首次 prebuild 后必须以实际 Gradle wrapper、AGP/Kotlin plugin、SDK/NDK 与 Hermes/New Architecture 配置为准核对。 -- MLRN 的 root Gradle extras 固定 `org.maplibre.reactnative.nativeVersion=13.4.1-step061` 与 `nativeVariant=opengl`。STEP-061 必须构建并 Maven Local publish 这个唯一的新坐标,禁止覆盖已经冻结的 `13.4.1-step060f`;本轮 RDC 修复仅推进坐标和文档,未构建或发布。 -## Custom SDK 来源(手工执行) +## 历史:STEP-061/062 Custom SDK 来源(手工执行) Gate C 封存环境负责生成 custom AAR;Test Host 只负责 Maven Local handoff。Expo plugin 对唯一 custom 坐标 `org.maplibre.gl:android-sdk-opengl` 排除 Maven Central,而将 Maven Local 排在首位。因此未先发布本仓库 custom SDK 时 dependency resolution 必须失败,不能悄悄使用官方 SDK;MLRN 的独立 turf/annotation/scalebar 依赖不被这个 SDK-only 排除误伤。 @@ -51,7 +55,7 @@ Style JSON 明确创建 background、host-field `RasterArraySource`、`WeatherCo Test Host 固定消费已验收的 `org.maplibre.gl:android-sdk-opengl:13.4.1-step061`,本阶段不构建或发布 AAR。页面 style 顺序是 `background → GSI raster → WeatherColorLayer → RasterParticleLayer`;GSI 使用国土地理院标准地图 `https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png`(256、maxzoom 18、含 attribution),相机初始聚焦日本及周边 WeatherV2 bounds。 -受控 assets `assets/weather/ecmwf-hres-wind-20260807/` 原样复制 Android Validation 的 `dataset.json` 与四个 `metadata.json`/`field.f32`。`expo-asset` 是明确直接依赖,`metro.config.js` 仅增加 `.f32` asset extension。点击 `LOAD REAL WEATHER T0-T3` 会先 clear,然后通过 `Asset.fromModule(...).downloadAsync()` 取得四个 Expo cache `file://` URI,按 producer validTime 提交;JS 只传小型 metadata JSON 和文件引用,不 materialize 3.01 MiB payload 为数组、JSON、base64 或 hex。 +受控 assets `assets/weather/ecmwf-hres-wind-20260807/` 原样复制 Android Validation 的 `dataset.json` 与四个 `metadata.json`/`field.f32`。`expo-asset` 是明确直接依赖,`metro.config.js` 当前增加 `.f32` 与 `.u8` binary asset extension。点击 `LOAD REAL WEATHER T0-T3` 会先 clear,然后通过 `Asset.fromModule(...).downloadAsync()` 取得四个 Expo cache `file://` URI,按 producer validTime 提交;JS 只传小型 metadata JSON 和文件引用,不 materialize 3.01 MiB payload 为数组、JSON、base64 或 hex。 硬断言依次为 T0=`ACCEPTED_PENDING_INITIAL_WINDOW`、T1=`INITIAL_WINDOW_READY`、T2/T3=`ACCEPTED_NEXT_SNAPSHOT`,并在最后要求 Native state=`READY`;任何偏差均显示 `ERROR`。界面提供 PLAY/PAUSE、HALF/NORMAL/DOUBLE、四个 epoch-ms forecast seek、AUTO/30/60 FPS、两层可见性、冻结 WeatherColor stops、独立 particle/trail 色彩,以及粒子/轨迹视觉参数。Expo/系统管理 asset cache;本页不自建 `.f32` 临时文件。 diff --git a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle deleted file mode 100644 index 0bc87ed77..000000000 --- a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - id "com.android.library" - id "org.jetbrains.kotlin.android" -} - -apply plugin: "com.facebook.react" - -android { - namespace "org.maplibre.particle.rntesthost.weather" - compileSdk 36 - defaultConfig { minSdk 24 } -} - -dependencies { - implementation "com.facebook.react:react-android" - implementation "org.jetbrains.kotlin:kotlin-stdlib:2.1.20" - implementation "cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1" - compileOnly project(":maplibre_maplibre-react-native") -} - -react { - jsRootDir = file("../src/") - libraryName = "WeatherRuntimeBridge" - codegenJavaPackageName = "org.maplibre.particle.rntesthost.weather" -} diff --git a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/AndroidManifest.xml b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/AndroidManifest.xml deleted file mode 100644 index 94cbbcfc3..000000000 --- a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt deleted file mode 100644 index dafbebd2c..000000000 --- a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgeModule.kt +++ /dev/null @@ -1,225 +0,0 @@ -package org.maplibre.particle.rntesthost.weather - -import android.net.Uri -import android.view.View -import com.facebook.react.bridge.Arguments -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.turbomodule.core.interfaces.TurboModule -import com.facebook.react.bridge.ReactMethod -import com.facebook.react.bridge.ReadableArray -import com.facebook.react.bridge.ReadableMap -import com.facebook.react.uimanager.IllegalViewOperationException -import com.facebook.react.uimanager.UIManagerHelper -import com.facebook.react.uimanager.common.UIManagerType -import org.maplibre.android.maps.MapLibreMap -import org.maplibre.android.maps.Style -import org.maplibre.android.maps.WeatherRuntime -import org.maplibre.reactnative.components.mapview.MLRNMapView -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.IOException -import java.io.InputStream - -/** - * A low-frequency, per-view bridge. It resolves the Fabric view for each call and never stores a - * reactTag, current map, timeline, playback state, or a second Map registry. - */ -class WeatherRuntimeBridgeModule(private val context: ReactApplicationContext) : NativeWeatherRuntimeBridgeSpec(context), TurboModule { - - @ReactMethod - override fun attach(reactTag: Double, promise: Promise) = withMapView(reactTag, promise) { view, map -> - val style = map.style ?: return@withMapView reject(promise, "STYLE_NOT_READY", "The MapLibre style is not loaded") - val sourceExists = map.hasStyleSource(WeatherIds.SOURCE) - val weatherColorLayerExists = map.hasStyleLayer(WeatherIds.COLOR_LAYER) - val particleLayerExists = map.hasStyleLayer(WeatherIds.PARTICLE_LAYER) - if (!sourceExists || !weatherColorLayerExists || !particleLayerExists) { - return@withMapView reject( - promise, - "WEATHER_OBJECTS_NOT_READY", - weatherObjectsNotReadyDiagnostic( - reactTag.toInt(), - style, - sourceExists, - weatherColorLayerExists, - particleLayerExists, - ), - ) - } - val runtime = map.replaceWeatherRuntime(WeatherRuntime.Config(WeatherIds.SOURCE, WeatherIds.COLOR_LAYER, WeatherIds.PARTICLE_LAYER)) - // MLRN drops its MapView by detaching it. This listener captures only that concrete map, - // disposes its map-owned facade, and cannot target a replacement view. - view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) = Unit - override fun onViewDetachedFromWindow(v: View) { map.disposeWeatherRuntime() } - }) - promise.resolve(snapshot(runtime)) - } - - @ReactMethod - override fun detach(reactTag: Double, promise: Promise) = withMap(reactTag, promise) { map -> - if (map.weatherRuntime == null) reject(promise, "RUNTIME_DISPOSED", "No attached WeatherRuntime exists for this map") - else { map.disposeWeatherRuntime(); promise.resolve(null) } - } - - @ReactMethod override fun getState(reactTag: Double, promise: Promise) = withRuntime(reactTag, promise) { runtime -> promise.resolve(snapshot(runtime)) } - @ReactMethod override fun clearWindFields(reactTag: Double, promise: Promise) = command(reactTag, promise) { it.clearWindFields() } - - /** Binary ingress is a local file reference, never a JS number array or encoded string. */ - @ReactMethod - override fun submitWindField(reactTag: Double, metadataJson: String, payloadFileUri: String, promise: Promise) { - if (metadataJson.isEmpty() || metadataJson.toByteArray(Charsets.UTF_8).size > MAX_METADATA_UTF8_BYTES) return reject(promise, "INVALID_ARGUMENT", "metadataJson UTF-8 payload must be non-empty and at most $MAX_METADATA_UTF8_BYTES bytes") - val payload = try { readLocalPayload(payloadFileUri) } - catch (error: IllegalArgumentException) { return reject(promise, "PAYLOAD_FILE_UNREADABLE", error.message ?: "payload file is unreadable") } - catch (error: IOException) { return reject(promise, "PAYLOAD_FILE_UNREADABLE", "payload file could not be read: ${error.message ?: "I/O error"}") } - catch (error: RuntimeException) { return reject(promise, "PAYLOAD_FILE_UNREADABLE", "payload file could not be read") } - if (payload.isEmpty()) return reject(promise, "PAYLOAD_FILE_UNREADABLE", "payload file is empty") - withRuntime(reactTag, promise) { runtime -> - try { promise.resolve(runtime.submitWindField(metadataJson, payload).name) } - catch (error: IllegalStateException) { reject(promise, "NATIVE_REJECTED", error.message ?: "WeatherRuntime rejected the field") } - } - } - @ReactMethod override fun setWeatherEnabled(reactTag: Double, enabled: Boolean, promise: Promise) = command(reactTag, promise) { it.setWeatherEnabled(enabled) } - @ReactMethod override fun setWeatherColorVisible(reactTag: Double, visible: Boolean, promise: Promise) = command(reactTag, promise) { it.setWeatherColorVisible(visible) } - @ReactMethod override fun setParticleVisible(reactTag: Double, visible: Boolean, promise: Promise) = command(reactTag, promise) { it.setParticleVisible(visible) } - @ReactMethod override fun setTargetFps(reactTag: Double, target: String, promise: Promise) = enumCommand(reactTag, promise, target, WeatherRuntime.TargetFps.values()) { runtime, value -> runtime.setTargetFps(value) } - @ReactMethod override fun setPlaybackState(reactTag: Double, playback: String, promise: Promise) = enumCommand(reactTag, promise, playback, WeatherRuntime.PlaybackState.values()) { runtime, value -> runtime.setPlaybackState(value) } - @ReactMethod override fun setPlaybackSpeed(reactTag: Double, speed: String, promise: Promise) = enumCommand(reactTag, promise, speed, WeatherRuntime.PlaybackSpeed.values()) { runtime, value -> runtime.setPlaybackSpeed(value) } - - @ReactMethod - override fun setForecastTime(reactTag: Double, timestampMs: Double, promise: Promise) { - if (!timestampMs.isFinite() || timestampMs < 0 || timestampMs > Long.MAX_VALUE.toDouble() || timestampMs != kotlin.math.floor(timestampMs)) { - return reject(promise, "INVALID_ARGUMENT", "forecast timestamp must be a non-negative epoch millisecond integer") - } - command(reactTag, promise) { it.setForecastTime(timestampMs.toLong()) } - } - - @ReactMethod override fun setParticleCount(reactTag: Double, count: Double, promise: Promise) { - if (!count.isFinite() || count < 0 || count > Int.MAX_VALUE || count != kotlin.math.floor(count)) return reject(promise, "INVALID_ARGUMENT", "particle count must be a non-negative integer") - command(reactTag, promise) { it.setParticleCount(count.toInt()) } - } - @ReactMethod override fun setParticleLineWidth(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setParticleLineWidth(valid) } - @ReactMethod override fun setParticleLength(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setParticleLength(valid) } - @ReactMethod override fun setParticleOpacity(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setParticleOpacity(valid) } - @ReactMethod override fun setTrailOpacity(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setTrailOpacity(valid) } - @ReactMethod override fun setTrailFade(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setTrailFade(valid) } - @ReactMethod override fun setWeatherColorOpacity(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setWeatherColorOpacity(valid) } - @ReactMethod override fun setParticleColor(reactTag: Double, red: Double, green: Double, blue: Double, alpha: Double, promise: Promise) = colorCommand(reactTag, red, green, blue, alpha, promise) { runtime, r, g, b, a -> runtime.setParticleColor(r, g, b, a) } - @ReactMethod override fun setTrailColor(reactTag: Double, red: Double, green: Double, blue: Double, alpha: Double, promise: Promise) = colorCommand(reactTag, red, green, blue, alpha, promise) { runtime, r, g, b, a -> runtime.setTrailColor(r, g, b, a) } - - @ReactMethod - override fun setWeatherColorStops(reactTag: Double, stops: ReadableArray, promise: Promise) { - val parsed = ArrayList() - for (index in 0 until stops.size()) { - val stop: ReadableMap = try { stops.getMap(index) ?: throw IllegalArgumentException() } catch (_: RuntimeException) { return reject(promise, "INVALID_ARGUMENT", "color stop $index must be an object") } - try { - val speed = stop.getDouble("speedMps"); val red = stop.getDouble("red"); val green = stop.getDouble("green"); val blue = stop.getDouble("blue"); val alpha = stop.getDouble("alpha") - if (!speed.isFinite() || !red.isFinite() || !green.isFinite() || !blue.isFinite() || !alpha.isFinite() || red !in 0.0..1.0 || green !in 0.0..1.0 || blue !in 0.0..1.0 || alpha !in 0.0..1.0 || (parsed.isNotEmpty() && speed <= parsed.last().speedMps)) { - return reject(promise, "INVALID_ARGUMENT", "stops require finite, strictly increasing speedMps and finite RGBA in [0,1]") - } - parsed.add(WeatherRuntime.ColorStop(speed.toFloat(), red.toFloat(), green.toFloat(), blue.toFloat(), alpha.toFloat())) - } catch (_: RuntimeException) { return reject(promise, "INVALID_ARGUMENT", "color stop $index is incomplete") } - } - command(reactTag, promise) { it.setWeatherColorStops(parsed) } - } - - private fun command(reactTag: Double, promise: Promise, fn: (WeatherRuntime) -> WeatherRuntime.CommandResult) = withRuntime(reactTag, promise) { runtime -> - try { - promise.resolve(fn(runtime).name) - } catch (error: IllegalStateException) { - // WeatherRuntime rejects disabled/disposed commands by contract. Do not let an - // exception escape the UI queue or turn a stale command into an implicit success. - reject(promise, "NATIVE_REJECTED", error.message ?: "WeatherRuntime rejected the command") - } - } - private fun floatCommand(reactTag: Double, value: Double, promise: Promise, fn: (WeatherRuntime, Float) -> WeatherRuntime.CommandResult) { - if (!value.isFinite()) return reject(promise, "INVALID_ARGUMENT", "value must be finite") - command(reactTag, promise) { fn(it, value.toFloat()) } - } - private fun colorCommand(reactTag: Double, red: Double, green: Double, blue: Double, alpha: Double, promise: Promise, fn: (WeatherRuntime, Float, Float, Float, Float) -> WeatherRuntime.CommandResult) { - if (listOf(red, green, blue, alpha).any { !it.isFinite() || it !in 0.0..1.0 }) return reject(promise, "INVALID_ARGUMENT", "RGBA must be finite values in [0,1]") - command(reactTag, promise) { fn(it, red.toFloat(), green.toFloat(), blue.toFloat(), alpha.toFloat()) } - } - private fun > enumCommand(reactTag: Double, promise: Promise, raw: String, values: Array, fn: (WeatherRuntime, T) -> WeatherRuntime.CommandResult) { - val value = values.firstOrNull { it.name == raw } ?: return reject(promise, "INVALID_ARGUMENT", "unsupported enum value: $raw") - command(reactTag, promise) { fn(it, value) } - } - private fun readLocalPayload(reference: String): ByteArray { - if (reference.isBlank()) throw IllegalArgumentException("payload file reference is empty") - val uri = Uri.parse(reference) - if (uri.scheme != null && uri.scheme != "file") throw IllegalArgumentException("payload file reference must use file:// or an absolute local path") - val file = if (uri.scheme == "file") File(uri.path ?: throw IllegalArgumentException("payload file URI has no path")) else File(reference).also { - if (!it.isAbsolute) throw IllegalArgumentException("payload path without a URI scheme must be absolute") - } - if (!file.isFile || !file.canRead()) throw IllegalArgumentException("payload file is not a readable local file") - if (file.length() <= 0L || file.length() > MAX_PAYLOAD_BYTES) throw IllegalArgumentException("payload file size must be in 1..$MAX_PAYLOAD_BYTES bytes") - return file.inputStream().use(::readBounded) - } - private fun readBounded(input: InputStream): ByteArray { - val output = ByteArrayOutputStream(); val buffer = ByteArray(DEFAULT_READ_BUFFER_BYTES) - while (true) { - val count = input.read(buffer) - if (count < 0) break - if (output.size().toLong() + count > MAX_PAYLOAD_BYTES) throw IllegalArgumentException("payload file exceeds $MAX_PAYLOAD_BYTES bytes") - output.write(buffer, 0, count) - } - return output.toByteArray() - } - private fun withRuntime(reactTag: Double, promise: Promise, fn: (WeatherRuntime) -> Unit) = withMap(reactTag, promise) { map -> - val runtime = map.weatherRuntime - if (runtime == null || runtime.isDisposed) reject(promise, "RUNTIME_DISPOSED", "WeatherRuntime is not attached to this map") else fn(runtime) - } - private fun withMap(reactTag: Double, promise: Promise, fn: (MapLibreMap) -> Unit) = withMapView(reactTag, promise) { _, map -> fn(map) } - private fun withMapView(reactTag: Double, promise: Promise, fn: (MLRNMapView, MapLibreMap) -> Unit) { - if (!reactTag.isFinite() || reactTag <= 0 || reactTag != kotlin.math.floor(reactTag)) return reject(promise, "INVALID_ARGUMENT", "reactTag must be a positive integer") - context.runOnUiQueueThread { - val view = try { UIManagerHelper.getUIManager(context, UIManagerType.FABRIC)?.resolveView(reactTag.toInt()) } catch (_: IllegalViewOperationException) { null } - if (view !is MLRNMapView) return@runOnUiQueueThread reject(promise, "MAP_VIEW_NOT_FOUND", "reactTag does not resolve to a current MLRNMapView") - val map = view.mapLibreMap ?: return@runOnUiQueueThread reject(promise, "MAP_NOT_READY", "MLRNMapView has no MapLibreMap yet") - fn(view, map) - } - } - private fun snapshot(runtime: WeatherRuntime) = Arguments.createMap().apply { - putString("state", runtime.state.name); putString("playbackState", runtime.playbackState.name); putString("targetFps", runtime.targetFps.name); putBoolean("disposed", runtime.isDisposed) - } - - /** - * Core-only layer types do not have a Java Layer peer. Presence is checked directly in Core - * before this diagnostic is built; the style URI distinguishes a JSON style (empty URI) from - * a URI-backed style without materialising all sources or layers. - */ - private fun weatherObjectsNotReadyDiagnostic( - reactTag: Int, - style: Style, - sourceExists: Boolean, - weatherColorLayerExists: Boolean, - particleLayerExists: Boolean, - ): String { - return buildString { - append("WEATHER_OBJECTS_NOT_READY reactTag=").append(reactTag) - append(" mapView=present mapLibreMap=present style=present") - append(" styleUri=").append(style.getUri().ifEmpty { "" }) - append(" source[").append(WeatherIds.SOURCE).append("]=") - .append(presence(sourceExists)) - append(" layer[").append(WeatherIds.COLOR_LAYER).append("]=") - .append(presence(weatherColorLayerExists)) - append(" layer[").append(WeatherIds.PARTICLE_LAYER).append("]=") - .append(presence(particleLayerExists)) - } - } - - private fun presence(exists: Boolean) = if (exists) "present" else "missing" - - private fun reject(promise: Promise, code: String, message: String) = promise.reject(code, message) - private object WeatherIds { - const val SOURCE = "rn-weather-fields" - const val COLOR_LAYER = "rn-weather-color" - const val PARTICLE_LAYER = "rn-weather-particles" - } - companion object { - const val NAME = "WeatherRuntimeBridge" - private const val MAX_METADATA_UTF8_BYTES = 1_048_576 - private const val MAX_PAYLOAD_BYTES = 64L * 1024L * 1024L - private const val DEFAULT_READ_BUFFER_BYTES = 16 * 1024 - } -} diff --git a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgePackage.kt b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgePackage.kt deleted file mode 100644 index c0db6ecdb..000000000 --- a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/src/main/java/org/maplibre/particle/rntesthost/weather/WeatherRuntimeBridgePackage.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.maplibre.particle.rntesthost.weather - -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.module.model.ReactModuleInfo -import com.facebook.react.module.model.ReactModuleInfoProvider -import com.facebook.react.TurboReactPackage - -class WeatherRuntimeBridgePackage : TurboReactPackage() { - override fun getModule(name: String, context: ReactApplicationContext): NativeModule? = - if (name == WeatherRuntimeBridgeModule.NAME) WeatherRuntimeBridgeModule(context) else null - - override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { - mapOf(WeatherRuntimeBridgeModule.NAME to ReactModuleInfo( - WeatherRuntimeBridgeModule.NAME, - WeatherRuntimeBridgeModule::class.java.name, - false, false, false, false, true - )) - } -} diff --git a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/package.json b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/package.json deleted file mode 100644 index f128dd201..000000000 --- a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@maplibre-particle/weather-runtime-bridge", - "version": "0.1.0", - "private": true, - "main": "src/index.ts", - "react-native": "src/index.ts", - "peerDependencies": { "react-native": "0.85.2" }, - "codegenConfig": { - "name": "WeatherRuntimeBridgeSpec", - "type": "modules", - "jsSrcsDir": "src", - "android": { "javaPackageName": "org.maplibre.particle.rntesthost.weather" } - } -} diff --git a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/react-native.config.js b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/react-native.config.js deleted file mode 100644 index 56932d374..000000000 --- a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/react-native.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = { dependency: { platforms: { android: { sourceDir: "./android" } } } }; diff --git a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/src/NativeWeatherRuntimeBridge.ts b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/src/NativeWeatherRuntimeBridge.ts deleted file mode 100644 index 9cba04075..000000000 --- a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/src/NativeWeatherRuntimeBridge.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { TurboModule } from "react-native"; -import { TurboModuleRegistry } from "react-native"; - -type RuntimeState = "DISABLED" | "WAITING_FOR_FIELDS" | "READY" | "ERROR"; -type PlaybackState = "PLAY" | "PAUSE"; -type TargetFps = "AUTO" | "FPS_30" | "FPS_60"; -type CommandResult = "ACCEPTED" | "REJECTED"; -type FieldSubmitResult = "REJECTED_SOURCE_MODE" | "REJECTED_ADAPTER" | "REJECTED_INCOMPATIBLE" | "REJECTED_NON_MONOTONIC" | "REJECTED_CAPACITY" | "ACCEPTED_PENDING_INITIAL_WINDOW" | "INITIAL_WINDOW_READY" | "ACCEPTED_NEXT_SNAPSHOT" | "DUPLICATE" | "UNKNOWN"; -type ColorStop = { speedMps: number; red: number; green: number; blue: number; alpha: number }; -type RuntimeSnapshot = { state: RuntimeState; playbackState: PlaybackState; targetFps: TargetFps; disposed: boolean }; - -export interface Spec extends TurboModule { - attach(reactTag: number): Promise; - detach(reactTag: number): Promise; - getState(reactTag: number): Promise; - clearWindFields(reactTag: number): Promise; - submitWindField(reactTag: number, metadataJson: string, payloadFileUri: string): Promise; - setWeatherEnabled(reactTag: number, enabled: boolean): Promise; - setWeatherColorVisible(reactTag: number, visible: boolean): Promise; - setParticleVisible(reactTag: number, visible: boolean): Promise; - setTargetFps(reactTag: number, target: TargetFps): Promise; - setForecastTime(reactTag: number, timestampMs: number): Promise; - setPlaybackState(reactTag: number, playback: PlaybackState): Promise; - setPlaybackSpeed(reactTag: number, speed: "HALF" | "NORMAL" | "DOUBLE"): Promise; - setWeatherColorStops(reactTag: number, stops: ColorStop[]): Promise; - setParticleColor(reactTag: number, red: number, green: number, blue: number, alpha: number): Promise; - setTrailColor(reactTag: number, red: number, green: number, blue: number, alpha: number): Promise; - setParticleCount(reactTag: number, count: number): Promise; - setParticleLineWidth(reactTag: number, value: number): Promise; - setParticleLength(reactTag: number, value: number): Promise; - setParticleOpacity(reactTag: number, value: number): Promise; - setTrailOpacity(reactTag: number, value: number): Promise; - setTrailFade(reactTag: number, value: number): Promise; - setWeatherColorOpacity(reactTag: number, value: number): Promise; -} - -export default TurboModuleRegistry.getEnforcing("WeatherRuntimeBridge"); diff --git a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/src/index.ts b/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/src/index.ts deleted file mode 100644 index f673e0582..000000000 --- a/integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ - -export type RuntimeState = "DISABLED" | "WAITING_FOR_FIELDS" | "READY" | "ERROR"; -export type PlaybackState = "PLAY" | "PAUSE"; -export type TargetFps = "AUTO" | "FPS_30" | "FPS_60"; -export type CommandResult = "ACCEPTED" | "REJECTED"; -export type FieldSubmitResult = "REJECTED_SOURCE_MODE" | "REJECTED_ADAPTER" | "REJECTED_INCOMPATIBLE" | "REJECTED_NON_MONOTONIC" | "REJECTED_CAPACITY" | "ACCEPTED_PENDING_INITIAL_WINDOW" | "INITIAL_WINDOW_READY" | "ACCEPTED_NEXT_SNAPSHOT" | "DUPLICATE" | "UNKNOWN"; -export type ColorStop = Readonly<{ speedMps: number; red: number; green: number; blue: number; alpha: number }>; -export type RuntimeSnapshot = Readonly<{ state: RuntimeState; playbackState: PlaybackState; targetFps: TargetFps; disposed: boolean }>; - -export { default as WeatherRuntimeBridge } from "./NativeWeatherRuntimeBridge"; diff --git a/integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js b/integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js index 0e1f4ec9d..5def3358a 100644 --- a/integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js +++ b/integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js @@ -6,7 +6,7 @@ const marker = "// MapLibreParticleRNTestHost NavSea Particle release SDK"; module.exports = function withParticleNativeAndroid(config) { return withProjectBuildGradle(config, (project) => { if (project.modResults.contents.includes(marker)) return project; - project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.mavenLocal()\n p.configurations.configureEach { configuration ->\n configuration.resolutionStrategy.dependencySubstitution {\n substitute module("org.maplibre.gl:android-sdk-opengl") using module("cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.4.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`; + project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.mavenLocal()\n p.configurations.configureEach { configuration ->\n configuration.resolutionStrategy.dependencySubstitution {\n substitute module("org.maplibre.gl:android-sdk-opengl") using module("cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.4.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`; return project; }); }; diff --git a/integration/react-native/MapLibreParticleRNTestHost/react-native.config.js b/integration/react-native/MapLibreParticleRNTestHost/react-native.config.js deleted file mode 100644 index e59edec7c..000000000 --- a/integration/react-native/MapLibreParticleRNTestHost/react-native.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - dependencies: { - "@maplibre-particle/weather-runtime-bridge": { - root: "./modules/weather-runtime-bridge" - } - } -}; diff --git a/integration/react-native/MapLibreParticleRNTestHost/tsconfig.json b/integration/react-native/MapLibreParticleRNTestHost/tsconfig.json index 9a195195d..26c48778d 100644 --- a/integration/react-native/MapLibreParticleRNTestHost/tsconfig.json +++ b/integration/react-native/MapLibreParticleRNTestHost/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "expo/tsconfig.base", "compilerOptions": { "strict": true, "noEmit": true, "preserveSymlinks": true }, - "include": ["App.tsx", "src", "modules/weather-runtime-bridge/src"] + "include": ["App.tsx", "src"] } diff --git a/packages/maplibre-particle-rn/CHANGELOG.md b/packages/maplibre-particle-rn/CHANGELOG.md index b75b6b6e4..6ca68949f 100644 --- a/packages/maplibre-particle-rn/CHANGELOG.md +++ b/packages/maplibre-particle-rn/CHANGELOG.md @@ -2,11 +2,12 @@ ## 13.4.1-particle.3 +- V3-08 将 RN package、Expo plugin 与 Test Host 的目标 Native 坐标同步为 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2`;`.2` 是 STEP-069 v2 multi-plane 所需的新 artifact,绝不复用或覆盖 `.1`。 +- 此项仅为 Android 手工构建、Maven Local 发布与单次综合验收做准备;AAR 尚未发布或验收,未执行 Android Gradle/AAR/APK/ADB 或 Jest。 - `FieldRuntime.submitField` 升级为 `maplibre-particle-field/v2` 的明确 `FieldPayloadRefs`:shared validity 加可选 scalar/vector 本地文件,不再接受模糊单 payload。 - 新增 `setFieldValueSource` 与 `setParticleAdvectionScale`,仅桥接既有 renderer 配置,不重传 field 或改变 Timeline。 - 保留 `submitWindField` native/internal v1 ABI;公共兼容入口改名为 `submitLegacyVectorField`,避免与 v2 混淆。 - Android bridge 在读取前校验全部 local file、非空 plane 与合计 64 MiB;Java/JNI 用有 presence flags 和长度的 v2 packed container 恢复 Core `ParticleFieldPayloadBundle`。 -- Android artifact 未构建、未发布;Android Gradle/AAR/APK/ADB 与 Jest 均未执行。 ## 13.4.1-particle.2 diff --git a/packages/maplibre-particle-rn/README.md b/packages/maplibre-particle-rn/README.md index 19b2ed499..5b08bacf0 100644 --- a/packages/maplibre-particle-rn/README.md +++ b/packages/maplibre-particle-rn/README.md @@ -7,9 +7,9 @@ Map 与 Camera 必须由 Consumer 直接从 `@maplibre/maplibre-react-native@11. ## 版本关系 - RN/TS 包:`13.4.1-particle.3` -- Android Native artifact:`cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1` +- Android Native artifact(V3-08 目标):`cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2` -本次仅整理 RN/TS 公共接口。Native artifact 未构建、未修改、未发布;Expo plugin 继续解析上述冻结 artifact。 +`.2` 是 STEP-069 v2 multi-plane(shared validity 加可选 scalar/vector)的新 Native artifact 坐标,不能复用或覆盖旧 `.1`。本次只完成坐标准备;用户尚未手工构建、发布或验收 Android AAR,Expo plugin 现指向目标 `.2`,不得将其表述为已发布或已通过。 ## 最小接入 @@ -61,7 +61,7 @@ await FieldRuntime.setFieldColorStops(reactTag, [ `submitField` 的 v2 `payloadRefs` 必须有 `validityFileUri`,并且必须有 `scalarFileUri` 或 `vectorFileUri`。每个引用只能是 `file://` URI 或绝对本地路径;桥接层会在读入前检查全部文件、非空性和合计 64 MiB 上限。它不接受 JS number array、Base64 或大 JSON payload。Core 继续按 metadata 精确校验各 plane 的长度、encoding、layout 和 validity。 -`.2` 的 `submitField(reactTag, metadataJson, payloadFileUri)` 是单一 v1 vector payload。`.3` 不再以该模糊形状作为主入口;确有旧 native seam 的 Consumer 可显式调用 `submitLegacyVectorField`,它仅转发冻结的 v1 `submitWindField` ABI。新代码应使用 v2 `submitField`。 +旧 Native `.1` 的 `submitWindField(metadataJson, byte[])` 是单一 v1 vector ABI。`.3` 不再以该模糊形状作为主入口;确有旧 native seam 的 Consumer 可显式调用 `submitLegacyVectorField`,它仅转发冻结的 v1 ABI。新代码应使用目标 Native `.2` 提供的 v2 `submitField`。 ## 安装与发布 diff --git a/packages/maplibre-particle-rn/android/build.gradle b/packages/maplibre-particle-rn/android/build.gradle index f36606f68..42ed785f0 100644 --- a/packages/maplibre-particle-rn/android/build.gradle +++ b/packages/maplibre-particle-rn/android/build.gradle @@ -14,7 +14,7 @@ android { dependencies { implementation "com.facebook.react:react-android" implementation "org.jetbrains.kotlin:kotlin-stdlib:2.1.20" - implementation "cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1" + implementation "cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2" compileOnly project(":maplibre_maplibre-react-native") } diff --git a/packages/maplibre-particle-rn/app.plugin.js b/packages/maplibre-particle-rn/app.plugin.js index 50e3a9bfc..b497407bb 100644 --- a/packages/maplibre-particle-rn/app.plugin.js +++ b/packages/maplibre-particle-rn/app.plugin.js @@ -6,7 +6,7 @@ const marker = "// @navsea/maplibre-particle-rn Android OpenGL release"; module.exports = function withNavSeaMapLibreParticle(config) { return withProjectBuildGradle(config, (project) => { if (project.modResults.contents.includes(marker)) return project; - project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.maven { repository -> repository.url = p.uri("https://pbf.jpcn.ccwu.cc/maven") }\n p.configurations.configureEach { configuration ->\n configuration.resolutionStrategy.dependencySubstitution {\n substitute module("org.maplibre.gl:android-sdk-opengl") using module("cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.4.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`; + project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.maven { repository -> repository.url = p.uri("https://pbf.jpcn.ccwu.cc/maven") }\n p.configurations.configureEach { configuration ->\n configuration.resolutionStrategy.dependencySubstitution {\n substitute module("org.maplibre.gl:android-sdk-opengl") using module("cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.4.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`; return project; }); }; diff --git a/platform/android/VERSION b/platform/android/VERSION index 262843ce2..5b0bea1dd 100644 --- a/platform/android/VERSION +++ b/platform/android/VERSION @@ -1 +1 @@ -13.4.1-particle.1 +13.4.1-particle.2 diff --git a/release/maplibre-particle/release-manifest.json b/release/maplibre-particle/release-manifest.json index 5ae621212..93a5ffea7 100644 --- a/release/maplibre-particle/release-manifest.json +++ b/release/maplibre-particle/release-manifest.json @@ -1,10 +1,10 @@ { - "release": "13.4.1-particle.2", - "releaseState": "released", + "release": "13.4.1-particle.3", + "releaseState": "awaiting_android_manual_build_publish_acceptance", "upstreamMapLibreNative": "13.4.1", "upstreamCoreCommit": "18545ca10d296704b2cba675b182ff2c8da24588", "extensionLine": "particle", - "extensionRevision": 2, + "extensionRevision": 3, "platform": { "android": { "renderer": "opengl" @@ -12,68 +12,63 @@ }, "reactNativePackage": { "name": "@navsea/maplibre-particle-rn", - "version": "13.4.1-particle.2", + "version": "13.4.1-particle.3", "mapLibreReactNative": "11.3.6" }, "nativeArtifact": { "group": "cc.navsea.maplibre", "artifact": "android-sdk-particle-opengl", - "version": "13.4.1-particle.1" + "version": "13.4.1-particle.2", + "state": "awaiting_manual_build_publish_acceptance" }, - "releaseRelationship": "13.4.1-particle.2 仅变更 RN/TS Field Renderer 公共接口;Native artifact 保持 13.4.1-particle.1,未构建、修改或发布 AAR。", + "releaseRelationship": "13.4.1-particle.3 的 v2 multi-plane RN/TS API 目标为新的 Native artifact 13.4.1-particle.2;不得复用、覆盖或宣称发布旧 13.4.1-particle.1。", "canonicalField": { - "schema": "maplibre-particle-field/v1", - "transport": "metadataJson + local file:// payload", + "schema": "maplibre-particle-field/v2", + "transport": "metadataJson + validity local ref + optional scalar/vector local refs", "encoding": "float32-le", - "layout": "interleaved-uv" + "layout": "plane-specific; vector is interleaved-uv" }, "validation": { - "androidPackageConsumer": { + "android": { + "status": "not_run", + "required": [ + "Native .2 Release OpenGL AAR build and Maven Local publish", + "AAR SHA consistency check", + "javap WeatherRuntime v2 API check", + "Test Host clean prebuild/build/install/start", + "five field datasets and Runtime READY acceptance" + ] + }, + "previousBaseline": { + "reactNativePackage": "13.4.1-particle.2", + "nativeArtifact": "13.4.1-particle.1", "status": "pass", - "targetReactNativePackage": "13.4.1-particle.2", "validatedAt": "2026-09-10", "consumer": "integration/react-native/MapLibreParticlePackageConsumerTestHost", "dataset": "ECMWF HRES 20260807 T0-T3", "transport": "FILE_REF / FLOAT32_LE / INTERLEAVED_UV", - "nativeArtifact": "13.4.1-particle.1", "checks": [ "FieldRuntime attach", "T0-T3 ordered submit", "runtime READY", - "field OFF + particle ON", - "field ON + particle OFF", - "restore both ON", - "hard-gate no fatal/native bridge errors" - ] - }, - "previousBaseline": { - "reactNativePackage": "13.4.1-particle.1", - "status": "pass", - "validatedAt": "2026-09-09", - "consumer": "integration/react-native/MapLibreParticlePackageConsumerTestHost", - "dataset": "ECMWF HRES 20260807 T0-T3", - "transport": "FILE_REF / FLOAT32_LE / INTERLEAVED_UV" - }, - "publicHttpsConsumerInstall": { - "status": "pass", - "validatedAt": "2026-09-10", - "url": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.2.tgz", - "reactNativePackage": "13.4.1-particle.2", - "mapLibreReactNative": "11.3.6", - "typecheck": "pass", - "dependencyTree": "single MLRN 11.3.6 deduped" + "field and particle independent visibility", + "no fatal native bridge errors" + ], + "releaseTag": "maplibre-particle/rn/13.4.1-particle.2", + "releaseSourceCommit": "e43b58707cfd0f87965424477ed3f95625b3f724", + "npmTarball": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.2.tgz", + "npmTarballSha256": "ace9e0401824839d18a93cc4201c1f5d4cccbff0636570cfde82fd80b60779b5", + "npmTarballSizeBytes": 10660, + "publicHttpStatus": 200, + "publicBytesVerified": true } }, "publication": { - "npmRegistry": null, - "mavenRepository": "https://pbf.jpcn.ccwu.cc/maven", - "npmTarball": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.2.tgz", - "npmTarballSha256": "ace9e0401824839d18a93cc4201c1f5d4cccbff0636570cfde82fd80b60779b5", - "npmTarballSizeBytes": 10660, - "publishedAt": "2026-09-10", - "publicHttpStatus": 200, - "publicBytesVerified": true - }, - "releaseTag": "maplibre-particle/rn/13.4.1-particle.2", - "releaseSourceCommit": "e43b58707cfd0f87965424477ed3f95625b3f724" + "nativeArtifact": { + "status": "not_published", + "aarSha256": null, + "publishedAt": null + }, + "releaseTag": null + } } diff --git a/粒子开发记录.md b/粒子开发记录.md index ed4b59d88..635ccf876 100644 --- a/粒子开发记录.md +++ b/粒子开发记录.md @@ -673,3 +673,9 @@ WeatherColorLayer 的 `ParticleGpuValidationUBO` designated initializer 补齐 ` - 最终冻结链:fixture validator、package tsc、RN Test Host typecheck、RN codegen、Linux OpenGL `mbgl-test`/`mbgl-test-runner` target 均 PASS;V3 综合过滤 21/21 PASS,`git diff --check` PASS,验证链退出码 0。 - 综合回归发现并修正两个测试 fixture 陈旧问题:Host T0/T1 重复 GPU content 导致 Duplicate,以及旧 east/north/vortex `timeIndex=0` 导致 Timeline fail-closed。只改测试数据组合,不改 Runtime/Timeline/Renderer 生产逻辑。 - Android Gradle/AAR/APK/ADB/Jest 未运行;V3-07 Linux 冻结关闭,下一步统一进入 V3-08 Android 综合验收。STEP-062 用户 dirty 与历史 untracked/build/cache/tgz 未纳入。 + +## 2026-09-11 — STEP-072 V3-08 Android Native v2 与单次综合验收准备 +- Native Android 目标版本升为 `13.4.1-particle.2`;RN package `13.4.1-particle.3`、Expo plugin、RN Test Host 与 release manifest 统一到该坐标,当前 Android 状态保持 `not_run`,未伪造 AAR SHA、发布时间或 release tag。 +- 准备审计发现 Test Host 同时 autolink legacy local bridge 与正式 package bridge,二者均注册 `WeatherRuntimeBridge` / `WeatherRuntimeBridgeSpec`。STEP-072A 删除 legacy tracked bridge、根 `react-native.config.js`,并收敛 `tsconfig.json`;Expo autolinking 复核后只剩正式 `@navsea/maplibre-particle-rn` bridge。 +- 静态 hard gate:Host/package TypeScript、RN codegen、V3 fixture validator、两个 Expo plugin 语法、release manifest JSON、`git diff --check` 全部 PASS;legacy untracked `android/build/` 原样保留。 +- 已冻结 V3-08 单次 Android 验收流程:Linux 构建/发布 Native `.2` → SHA/API 检查 → Win11 Maven Local handoff → clean prebuild/dependencyInsight/一次 APK build → Metro 8091 → 固定设备 `192.168.1.146` → Wind/Rain/Wave/Tide/synthetic Wave 五组综合验收。Android Gradle/AAR/APK/ADB/Jest 均未执行,状态 `IMPLEMENTED / WAITING_ANDROID_VALIDATION`。