chore: prepare V3-08 android validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# STEP-072A V3-08 RN Test Host 移除 Legacy 双 Bridge
|
||||
|
||||
## 基线
|
||||
- 仓库:`/mnt/sda1/MapLibreParticleLab`
|
||||
- 分支:`particle/main`
|
||||
- 已提交 HEAD:`dd356a277db05ce0075bee43f431ddeb2aba19cc`
|
||||
- 当前工作区已有 STEP-072 未提交修改,必须保留,不得回退或覆盖。
|
||||
- 只处理 RN Test Host legacy bridge 重复 autolink 问题。
|
||||
|
||||
## 已确认事实
|
||||
- App 已只从 `@navsea/maplibre-particle-rn` 使用 `FieldRuntime`。
|
||||
- Expo autolinking 当前同时链接:
|
||||
- `@maplibre-particle/weather-runtime-bridge`
|
||||
- `@navsea/maplibre-particle-rn`
|
||||
- 两者都注册 Native module `WeatherRuntimeBridge`,codegen library 都是 `WeatherRuntimeBridgeSpec`。
|
||||
- Test Host 根 `react-native.config.js` 主动注册 `./modules/weather-runtime-bridge`。
|
||||
- `tsconfig.json` 仍 include `modules/weather-runtime-bridge/src`。
|
||||
- V3-08 必须只保留正式 package `.3` 的 bridge。
|
||||
|
||||
## 必做修改
|
||||
1. 删除整个 tracked legacy 目录:
|
||||
`integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/`
|
||||
- 只删除其中 Git tracked 源码/配置;不要清理、提交或处理其 untracked `android/build/`。
|
||||
2. 删除 Test Host 根:
|
||||
`integration/react-native/MapLibreParticleRNTestHost/react-native.config.js`
|
||||
- 该文件目前唯一用途是注册 legacy bridge;删除后不要新造替代配置。
|
||||
3. 修改:
|
||||
`integration/react-native/MapLibreParticleRNTestHost/tsconfig.json`
|
||||
- include 收敛为 `App.tsx` 与 `src`,移除 legacy module src。
|
||||
4. 更新 Test Host README 当前 V3-08 章节:
|
||||
- 明确 legacy local `@maplibre-particle/weather-runtime-bridge` 已移除。
|
||||
- `@navsea/maplibre-particle-rn@13.4.1-particle.3` 是唯一 `WeatherRuntimeBridge` 提供者。
|
||||
- 历史 STEP-061/062 章节可保留为历史说明,不得改写历史事实。
|
||||
## 禁止事项
|
||||
- 不得运行 Android Gradle、AAR/APK build、publish、install 或 ADB。
|
||||
- 不得运行或加入 Jest。
|
||||
- 不得修改 `packages/maplibre-particle-rn/`、MapLibre Core/JNI/WeatherRuntime、fixture、NavSea 或 release manifest。
|
||||
- 不得删除/清理任何 untracked/generated 文件;尤其不得处理 legacy 目录下现有 `android/build/`。
|
||||
- 不得执行 `git add .`、`git add -A`、commit、push、reset、clean、checkout 或 stash。
|
||||
- 不得修改 STEP-062 dirty 文档和其它历史 dirty/untracked 文件。
|
||||
|
||||
## 完成后必做报告
|
||||
逐项报告“已完成 / 未完成 / 无法验证”,并说明原因:
|
||||
1. tracked legacy bridge 源码/配置是否全部删除。
|
||||
2. 根 `react-native.config.js` 是否删除。
|
||||
3. `tsconfig.json` 是否只包含 `App.tsx` 与 `src`。
|
||||
4. README 是否明确正式 package 为唯一 bridge provider。
|
||||
5. 明确说明未执行 Android/ADB/Jest/Git。
|
||||
|
||||
不要自行做 Android 验证;完成文件修改后停止。
|
||||
@@ -0,0 +1,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 未执行”。
|
||||
@@ -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 五组场数据。
|
||||
108
docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md
Normal file
108
docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md
Normal file
@@ -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 <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 验收状态;本文件本身不代表已执行或通过。
|
||||
@@ -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。
|
||||
@@ -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` 临时文件。
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||
@@ -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<WeatherRuntime.ColorStop>()
|
||||
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 <T : Enum<T>> enumCommand(reactTag: Double, promise: Promise, raw: String, values: Array<T>, 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 { "<empty>" })
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = { dependency: { platforms: { android: { sourceDir: "./android" } } } };
|
||||
@@ -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<RuntimeSnapshot>;
|
||||
detach(reactTag: number): Promise<void>;
|
||||
getState(reactTag: number): Promise<RuntimeSnapshot>;
|
||||
clearWindFields(reactTag: number): Promise<CommandResult>;
|
||||
submitWindField(reactTag: number, metadataJson: string, payloadFileUri: string): Promise<FieldSubmitResult>;
|
||||
setWeatherEnabled(reactTag: number, enabled: boolean): Promise<CommandResult>;
|
||||
setWeatherColorVisible(reactTag: number, visible: boolean): Promise<CommandResult>;
|
||||
setParticleVisible(reactTag: number, visible: boolean): Promise<CommandResult>;
|
||||
setTargetFps(reactTag: number, target: TargetFps): Promise<CommandResult>;
|
||||
setForecastTime(reactTag: number, timestampMs: number): Promise<CommandResult>;
|
||||
setPlaybackState(reactTag: number, playback: PlaybackState): Promise<CommandResult>;
|
||||
setPlaybackSpeed(reactTag: number, speed: "HALF" | "NORMAL" | "DOUBLE"): Promise<CommandResult>;
|
||||
setWeatherColorStops(reactTag: number, stops: ColorStop[]): Promise<CommandResult>;
|
||||
setParticleColor(reactTag: number, red: number, green: number, blue: number, alpha: number): Promise<CommandResult>;
|
||||
setTrailColor(reactTag: number, red: number, green: number, blue: number, alpha: number): Promise<CommandResult>;
|
||||
setParticleCount(reactTag: number, count: number): Promise<CommandResult>;
|
||||
setParticleLineWidth(reactTag: number, value: number): Promise<CommandResult>;
|
||||
setParticleLength(reactTag: number, value: number): Promise<CommandResult>;
|
||||
setParticleOpacity(reactTag: number, value: number): Promise<CommandResult>;
|
||||
setTrailOpacity(reactTag: number, value: number): Promise<CommandResult>;
|
||||
setTrailFade(reactTag: number, value: number): Promise<CommandResult>;
|
||||
setWeatherColorOpacity(reactTag: number, value: number): Promise<CommandResult>;
|
||||
}
|
||||
|
||||
export default TurboModuleRegistry.getEnforcing<Spec>("WeatherRuntimeBridge");
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
dependencies: {
|
||||
"@maplibre-particle/weather-runtime-bridge": {
|
||||
root: "./modules/weather-runtime-bridge"
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`。
|
||||
|
||||
## 安装与发布
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
13.4.1-particle.1
|
||||
13.4.1-particle.2
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`。
|
||||
|
||||
Reference in New Issue
Block a user