Compare commits

...

14 Commits

Author SHA1 Message Date
tei
a0a4787b43 release: finalize unified particle.23
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 23:52:26 +09:00
tei
061b24a4bb release: record v23 GSRL source lock and receipt instructions
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 23:52:01 +09:00
tei
87f3d1c59a release: prepare unified particle.23
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 21:53:06 +09:00
tei
93fb20479e fix(release): support incremental particle revisions after native 13.6.1 upgrade
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 21:52:08 +09:00
tei
51c38b1296 docs: record STEP-107 iOS build and remote verification
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 21:47:40 +09:00
tei
016bf5ad93 fix(ios): guard Darwin style CoreOnly layer peer access
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 21:46:59 +09:00
tei
24105b1be7 docs(ios): record MLNStyle nil peer tap crash investigation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 20:44:36 +09:00
tei
bf7f6193e0 release: finalize unified particle.22
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 16:17:29 +09:00
tei
7cfad1c72f build(release): pin built source and correct SwiftPM RN dependency note
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 16:13:30 +09:00
tei
ea5bd393c6 release: prepare unified particle.22
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 14:08:10 +09:00
tei
40b79764ae build(release): support 13.6.1 v22 and RN 11.5.0 version lock 2026-10-09 14:07:18 +09:00
tei
69922b795a docs(android): close STEP-106 FZG T2 regression PASS
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 13:17:29 +09:00
tei
23f7b8a4d7 docs(android): record T2 old-version reproduction and pending rebuild gate
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 13:03:02 +09:00
tei
86906c8a6a test(android): isolate internal annotation symbol from STEP-102 retained ranges 2026-10-09 13:01:43 +09:00
35 changed files with 1154 additions and 131 deletions

View File

@@ -0,0 +1,152 @@
# iOS 地图点击重叠 Source 触发 MLNStyle 空 Peer 崩溃调查 v1
- 日期:2026-10-09(Asia/Tokyo)
- 状态:**问题已复现 / 原生崩溃堆栈已确认 / 修复与回归未执行**
- 分类:Particle iOS Native / MapLibre Darwin Style API 与 maplibre-react-native 点击兼容
- 严重程度:高;普通用户点击海图物体可使 NavSea 直接退出
- 本文件性质:跨项目缺陷移交与定位证据,**不是已实施修复、测试 PASS 或新版本发行结论**。
## 1. 用户复现现象
在 iOS NavSea 正常启动并显示地图后:
1. 直接点击地图上的「唐津ヨット ハーバ」位置点,App 异常退出。
2. 点击其他普通地图物体,未观察到同样问题。
3. 打开「+键盘」(十字光标)后,将光标移动到该港湾位置,查询/选取正常,不退出。
因此该问题优先定位 **地图直接 Tap 的原生可点击 Source 命中/图层排序链路**,而不是仅根据地点名称推断港湾数据损坏、JS 详情渲染异常或粒子 GPU 绘制故障。
## 2. 实际环境与 Git 事实
| 项目 | 本次调查事实 |
| --- | --- |
| iOS 设备 | Pleiades,iPad mini(第 5 代,iPad11,2) |
| iOS 崩溃日志声明的系统 | iPhone OS 26.5.2 (23F84) |
| App | NavSea,bundleID `com.navsea.app` |
| App 调查工作树 | `/Users/tei/dev/NavSea-release-v22` |
| App 调查 HEAD | `4bfe5257`(`release/particle-v22-android-debug-20261009`) |
| Particle 本次核对仓库 | `/Users/tei/dev/MapLibreParticleLab`,`particle/main` |
| Particle 调查起点 HEAD | `6df21dac38f072c5ad35b8dfc7ec5217509602bc` |
| 崩溃时间 | 2026-10-09 20:22:40 +0900 |
| 崩溃日志 | 设备端 `NavSea-2026-10-09-202240.ips`(调查时只读复制到 Mac `/tmp/`) |
设备还存在多份同日晚间 NavSea 崩溃报告,本次详细分析的证据为上述 **20:22:40** 报告。请不要把其余报告自动认定为相同堆栈,除非逐份核查。
Particle 工作区在调查前已有其他未跟踪文档,均不属于本问题,不得顺手删除、覆盖或纳入本次提交。
## 3. 崩溃证据(直接来自 iPad .ips)
```text
exception: EXC_CRASH / SIGABRT
exceptionReason.name: NSInvalidArgumentException
exceptionReason.composed_message:
*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil
faultingThread: 0 (com.apple.main-thread)
-[MLRNMapView didTapMap:]
-> -[MLRNMapView handleTapAtPoint:]
-> -[MLRNMapView getTouchableSourceWithHighestZIndex:]
-> -[MLNStyle layers]
-> -[__NSArrayM insertObject:atIndex:]
-> NSInvalidArgumentException / abort()
```
上述调用栈直接证明 **崩溃发生在地图原生 Tap 处理访问 Style Layer 列表时,且 Objective-C 数组插入了 `nil`**。它并不证明具体是哪一个 Style Layer 的 Peer 为 `nil`;这一点需要后续补充定位日志或调试证据。
## 4. 已核实的对应代码
### 4.1 Darwin Style 列表 getter:空对象缺少防护
文件:`platform/darwin/src/MLNStyle.mm`,调查时约第 285–293 行。
```objc
- (NSArray<__kindof MLNStyleLayer *> *)layers {
auto layers = self.rawStyle->getLayers();
NSMutableArray<__kindof MLNStyleLayer *> *styleLayers =
[NSMutableArray arrayWithCapacity:layers.size()];
for (auto layer : layers) {
MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer];
[styleLayers addObject:styleLayer];
}
return styleLayers;
}
```
`layerFromMBGLLayer:` 位于同一文件约第 376 行。该方法在已有 Peer 缺失时调用 `LayerManagerDarwin::createPeer(rawLayer)`;若工厂无法产生 Objective-C Peer,返回值可能为 `nil`,随后无条件 `addObject` 会直接触发本次异常。
### 4.2 部分 Particle/Weather Layer 仅注册为 CoreOnly
文件:`platform/darwin/src/MLNStyleLayerManager.mm`。
```cpp
addLayerTypeCoreOnly(std::make_unique<ParticleValidationLayerFactory>());
addLayerTypeCoreOnly(std::make_unique<RasterParticleLayerFactory>());
addLayerTypeCoreOnly(std::make_unique<WeatherColorLayerFactory>());
```
同文件的 `LayerManagerDarwin::createPeer`:当 `getPeerFactory(layer->getTypeInfo())` 不存在时返回 `nullptr`。因此以上 CoreOnly Layer **是造成空 Peer 的高优先级怀疑对象**,但需要定位日志确认实际 layer id/type,不能将具体类型预写为已证实。
### 4.3 RN MapView:多 Source 命中触发列表遍历
NavSea 当前安装依赖源码:
`node_modules/@maplibre/maplibre-react-native/ios/components/map-view/MLRNMapView.m`。
- `handleTapAtPoint:` 查询所有可点击 Source 的命中;存在命中时调用 `getTouchableSourceWithHighestZIndex:`。
- `getTouchableSourceWithHighestZIndex:` 在 `touchableSources.count == 1` 时直接返回唯一 Source。
- 在 **同时命中两个或更多可点击 Source** 时,方法读取 `self.style.layers` 并逆序遍历,决定顶部 Source;因此会进入第 4.1 节的危险路径。
崩溃堆栈已进入 `getTouchableSourceWithHighestZIndex:` 的 `self.style.layers`,符合多 Source 命中逻辑。需要现场记录所命中的具体 Source / layer IDs,以确认唐津位置的重叠对象组成。
### 4.4 NavSea 点击与十字光标是不同路径
- 直接点击地图:`src/map/MapScreen.tsx` 的 `MapLibreMap onPress`,以及 `src/map/layers/MooringSource.tsx` 的 `GeoJSONSource onPress`。
- 港湾点直点 Host 回调:`src/map/cursor/crossCursorEngine.ts` 的 `handleMooringPress`。
- 「+键盘」选取:`crossCursorEngine.ts` 中的 `queryRenderedFeaturesAtPointCompat` / `queryRenderedFeaturesInRectCompat` 查询。
本次异常在原生触摸处理阶段已发生;**即使 JS 侧在 `onPress` 增加 try/catch,也无法覆盖此 Objective-C SIGABRT**。
## 5. 修复方向(待 Particle iOS 实施者决定)
1. **首先精确定位**:在不改变业务和地图 Style 行为的前提下,记录 `layerFromMBGLLayer:` 转换失败的 raw layer id/type、Peer Factory 类型及命中 Source / Layer 列表,确认 CoreOnly Layer 是否为实际来源。
2. **修复 Darwin 公共访问契约**:`MLNStyle.layers` 不得把 `nil` 加入 `NSMutableArray`;优先设计与现有 CoreOnly Layer 共存的稳定方案。可评估安全过滤不可桥接 Layer,或补齐对应的 Peer/排序能力,但不可仅凭猜测引入无意义的 ObjC wrapper。
3. **保持集合访问一致性**:若采取过滤不可桥接 Layer 的方式,须同时检查 `countOfLayers`、`objectInLayersAtIndex:`、`getLayers:range:`、KVC/KVO 以及插入/移除行为,避免 getter 屏蔽后出现数量/索引语义不一致或再次写入 `nil`。
4. **保持点击 z-order 正确性**:RN 的多 Source 命中不能因为跳过 CoreOnly Layer 而改变真实可点击 Layer 的前后顺序;必要时直接在 Native 查询层顺序而不是依赖不完整 ObjC Peer 列表。
5. **限定修改范围**:优先在 Particle iOS Native / MapLibre 层完成,不通过 NavSea JS 屏蔽唐津地点、删除天气层或建立私有桥接来回避崩溃。若确实需要改 RN 公共接口或跨项目契约,应返回 Integration 项目协调。
**切勿直接删除 Particle、WeatherColor 或 Validation 的 Core Layer 来“消除异常”**;它们是正式渲染能力。
## 6. 修复后必须验证的场景
| 场景 | 期望 |
| --- | --- |
| 同位置只命中一个可点击 Source | 正常选取,无崩溃 |
| 两个及以上可点击 Source 重叠(重点复现) | 正确选中 z-order 最高且有效的 Source,无崩溃 |
| 唐津ヨット ハーバ 直接点击 | 不退出,港湾选取/摘要功能正常 |
| 「+键盘」移动到同一港湾 | 行为与修复前保持一致 |
| 其他海图对象直接点击 | 继续正常,无点击回归 |
| Style 含 RasterParticleLayer / WeatherColorLayer / ParticleValidationLayer 组合 | Style introspection 稳定,不向数组插入 nil |
| 天气/粒子图层启用与关闭后重复点击 | 无崩溃、无源层级混乱、无隐藏的点击遮挡 |
| `layers` getter、KVC 索引 getter、层增删 | 不出现 nil/越界,计数与索引契约保持一致 |
最少应使用 **Mac 构建出的实际 iOS Native 二进制 + iPad 真机** 做点击复现/回归。仅能编译或仅有单元测试通过,不等于用户场景已修复。
## 7. 责任、验收与完成条件
- **Particle iOS**:调查具体 nil layer、修复 Darwin Style API/多 Source 点击兼容、实施 iOS 构建与真机验证。
- **Particle Shared Core**:仅在确认是通用 Layer/Peer 协议缺陷时参与;不得未经证据改变 Android 行为。
- **NavSea**:在收到经过验证的新 iOS 发行后进行最小消费端验收;业务层不承担原生崩溃掩盖责任。
- **Integration 项目**:若涉及公共 API、跨平台协议、消费版本协调,则在现有 COORD/RFC/ADR/Contract/Status 框架内正式记录。
修复完成报告应写明:引发空 Peer 的真实 layer id/type、实际改动文件、验证命令及结果、真机版本和复现结果、Git commit/分支,以及正式发行时遵照 GSRL 的 Source Lock/获取方式/校验材料。**本调查报告本身不构成新发行,也不授权覆盖现有版本。**
## 8. 当前未完成事项
- [x] 找到 iPad NavSea 原生崩溃日志,确认主线程异常及调用栈
- [x] 核对 Particle `MLNStyle.mm`、`MLNStyleLayerManager.mm` 与 RN `MLRNMapView.m` 源码
- [x] 说明直接点击与十字光标查询的不同调用路径
- [ ] 获取本次返回 `nil` 的具体 Style Layer id/type
- [ ] 编写并执行 Native 修复及目标回归
- [ ] 重新构建 iOS、在实际 iPad 复现原场景并确认不再退出
- [ ] 按 GSRL 验证新版本消费与正式发行(如进入发行)
**结论:高可信度机制为 Native Style CoreOnly/缺失 Peer 与 RN 多 Source hit-test 排序不兼容;异常位置已由 .ips 确认,具体 nil Layer 身份与最终修复仍待 Particle iOS 验证。**

View File

@@ -0,0 +1,48 @@
# STEP-106 STEP-102 T2 自动 Annotation 层边界修正记录 v1
日期:2026-10-09(JST)
**状态:SOURCE_FIXED / STATIC_PASS / ANDROID_BUILD_NOT_RUN / DEVICE_RETEST_NOT_RUN**
## 事实
- 旧版 13.4.1-particle.21 在 FZG 同样复现 T2 R1 placementCapable=true/gate=false/live/static-placement/reuse0,已保存同场景旧版日志。新版 13.6.1 在比较结束后成功恢复,设备 APK SHA 正确。
- 两版 AnnotationManager 会自动在 Style 末尾插入 Symbol Layer org.maplibre.annotations.points。T2 测试 Fixture 的上层自身为 Fill,但原 R-C-R Plan 的 R1 从 step102-upper 延续至最后一层,把该内部 Symbol 也算进去。
- 不允许修改或跳过 Shared Core/Renderer Placement 安全 Gate。此次只改两个 Android Test App Kotlin 文件;新增 C2 live 尾部区段,单独承载 MapLibre Annotation Symbol,从而使 T2 的 R1 仅含 Fill;T1 R1 仍含自身 Symbol。
- 为 tick8 新增 STEP102_STYLE_LAYER_LAYOUT 日志,供下次实际 APK 验证真实 Style Layer 顺序、分段边界。
- 代码 Git 提交:86906c8a6af474a8d064c6f6d7d431e2a7e3efe6;静态检查 PASS,没有执行 Gradle、ADB 修正版验证、Jest。
## 需用户手工编译
在 Mac 终端:
```bash
cd /Users/tei/dev/MapLibreParticleLab-upgrade-android-1361
export JAVA_HOME=/usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
export ANDROID_HOME=/Users/tei/Library/Android/sdk
export ANDROID_SDK_ROOT="$ANDROID_HOME"
export PATH="$JAVA_HOME/bin:$ANDROID_HOME/platform-tools:$PATH"
export MAPLIBRE_VALIDATION_OUT_DIR="$PWD/artifacts/maplibre-particle/step106-t2-fix"
./tools/particle_android_validation/build_validation_apk.sh
```
脚本内部使用 Gradle `--console=plain`。请保留 BUILD SUCCESS 和 build-info.txt,APK 生成后再由 RDC 安装到 FZG。不得把目前设备上的 13.6.1 旧 APK 当成包含本修复的版本。
## 复测门槛
- APK 完整性、包名、版本、Native x86_64、哈希校验;签名不符禁止卸载重装。
- 新版 STEP102_STYLE_LAYER_LAYOUT 必须显示自动 Annotation Symbol 在最后的独立 Continuous C2;Plan valid、segments=4。
- T1_FREE_SYMBOL:R0 continued Composite/Reuse、R1 placementCapable=true 且需要时 Live、C2 包含 Annotation;不崩溃。
- T2_TWO_FREE:R0 和 R1 均 placementCapable=false、gate=true、actualExecution=composite;tick8→130 的 buildDelta=0、invalidationDelta=0、reuseDelta>0;C2 Live;不崩溃。
- 复测 STEP-099 T4 R-C-R R0 locality,STEP-101 单次/20次 Native Reset(日志须持续采集),V4 Golden 最小烟测。保留失败路径及原始日志。
- 修正作用域限定 Test App 分段验证,不等同生产快帧性能达标。若仍 fallback,应保留 Placement fail-closed,重新诊断而非强制 PASS。
## 2026-10-09 手工边界获用户明确授权后的 RDC 编译与 FZG 闭环
- 本轮用户明确要求“你编译和验证吧”。在 Mac 执行 Android Test App Gradle,源码 HEAD 23f7b8a4d7、干净;Gradle/Validation PASS。新版 APK SHA256=0ef3b033cd33139db07258745e9cd9d023170ce69767e3f8a5159821776ec9ef,66,505,495 字节,x86_64 MapLibre Native。
- FZG1-2 签名检查一致,adb install -r Success;设备 base.apk SHA 与 Mac 相同、versionName=13.6.1。
- T1_FREE_SYMBOL(两窗口):Style 实际 5 层/4 段;R0 reuseΔ+3682、buildΔ0、invalidΔ0;R1 Symbol 继续保守 live/static-placement,PASS。
- T2_TWO_FREE(两窗口):R0、R1 均 placementCapable=false/gate=true/composite;两段各 reuseΔ+3683、buildΔ0、invalidΔ0;尾部 Annotation Symbol C2 live,PASS,原 T2 阻塞正式解除。
- STEP099 T4:R0 reuseΔ+3675、build/invalidΔ0,PASS。STEP101 T1:1 个 nativeReset 完成,PASS;T2:20 Native reset 完整配对、21 ready、40 Render reset enter/complete,PASS。
- QBIN V4 Golden t0/t1 插入、Runtime V4 READY 最小烟测 PASS。Crash Buffer 均无故障记录。FZG 已恢复默认 GLOBAL/ACTIVE/EAST/MIX0%/256,修正版 APK 哈希再次核对一致。
- 状态更新:**STEP-106 CLOSED/PASS(FZG Android x86_64/OpenGL 定向问题及回归)**。不代表 ARM64、RN、iOS、Vulkan、全链发行通过。原始日志保存在 Git 忽略的 artifacts/maplibre-particle/step106-t2-fix/。

View File

@@ -0,0 +1,48 @@
# STEP-107 iOS Style CoreOnly Peer 集合一致性修复 v1
- 日期:2026-10-09(JST)
- 状态:**进行中 / iOS Native 构建 PASS / XCTest 与 iPad 实机回归未执行**
- 机器:Mac.lan;源码:`/Users/tei/dev/MapLibreParticleLab`;分支:`particle/main`
- 开工 HEAD:`24105b1be7a2c8bd3a547117a30887fbaa0a308b`
- 对应调查:`docs/20_调查/iOS地图点击重叠Source触发MLNStyle空Peer崩溃调查_v1.md`
- 涉及的已正式发行基线:`maplibre-particle/rn/13.6.1-particle.22`(不修改原 Tag / Source Lock)。
## 目标与边界
消除 Darwin `MLNStyle.layers` 因 CoreOnly Layer 没有 ObjC Peer 而插入 nil 引发的 NSInvalidArgumentException;统一 `layers`、KVC 数量/索引/区间访问与增删位置语义,保持 C++ 原始 Style 的 CoreOnly 渲染能力以及可点击 ObjC 层的可见 Z-order。
只允许修复 Darwin `MLNStyle.mm`、对应 iOS XCTest、Step 与报告;不得修改 NavSea、RN 11.5.0 依赖、Android、Shared Core、粒子 Shader/Renderer、V22 已发布内容或已有工作树。不得新建 worktree,禁用 Jest;不把编译通过等同 iPad 修复验收。
## 本轮实施
- [x] 检查实际 Native `MLNStyle.layers` 中未判空的 `addObject:`,及 `LayerManagerDarwin::createPeer` 对 CoreOnly 类型返回空的现状。
- [x] getter 仅暴露有效 ObjC Peer,跳过无 Peer CoreOnly Layer;DEBUG 日志包含不可桥接 Layer ID/类型,以便后续现场确认触发者。
- [x] `countOfLayers`、`objectInLayersAtIndex:`、`getLayers:range:` 统一使用 ObjC 可见集;修正原区间 getter 的反向遍历顺序。
- [x] ObjC 可见索引用于 `insertObject:inLayersAtIndex:`、`removeObjectFromLayersAtIndex:`;`insertLayer:aboveLayer:` 使用下一个可见 sibling,防止隐藏 CoreOnly 层造成 nil。
- [x] 补充 `MLNStyleTests.mm` 定向 XCTest,构造 Native-only Layer 与普通 Layer 混合、索引/顺序/增删场景。
- [x] `git diff --check` PASS。
- [x] RDC 在 Mac 执行 `bazel build //platform/ios:MapLibre.dynamic --color=no --curses=no --show_progress_rate_limit=15`,exit 0,Build completed successfully(982.084 秒)。
- [x] XCFramework ZIP 存在、`unzip -tqq` PASS,SHA-256:`f16b7f2be761a101bb4b906b900175b95412abfeef09d715e2c9321b300a7b8a`。
- [ ] 运行新增 `MLNStyleTests` XCTest,包括 KVC 索引与 CoreOnly 层回归。
- [ ] 使用实际 iPad 和此修复构建的 Native 验证「唐津ヨット ハーバ」直接点击、多 Source 重叠、十字光标、天气开关及粒子渲染。
- [ ] 从真实日志确认当次缺失 Peer 的具体 Layer ID/type;若原因超出 CoreOnly 假设,应补充分析。
- [ ] 如进入正式发行,按 GSRL 创建**新版本**、独立构建与 Source Lock,并做公网校验;不得改写 V22。
## 事实与遗留
新增 XCTest 源码已编写,但本轮 Bazel XCFramework 构建目标不包含 Darwin XCTest,故不能声称测试 PASS;实际 iPad 未测试。编译日志路径:`/tmp/maplibre_v22_ios_style_fix_bazel_20261009.log`。编译警告仅见无效搜索路径 `/opt/homebrew/lib`,不影响构建。构建产物为未发行验证 ZIP,不是 V22 原版或正式新版本。
原本就存在的 `vendor/expected-lite` 子模块本地状态与两份未跟踪文档必须保持原状。
## 下一步
由 Particle-iOS 在 Mac 真正执行 Darwin XCTest,必要时改正测试编译接口,再由用户将包含本次修复的 iOS Native 安装到 iPad 重放现场;上述验收未完成前仅按 PARTIAL / 待实机验收处理。不得转 NavSea JS workaround 或修改跨平台业务契约。
报告:`docs/报告/STEP-107_iOS_StyleCoreOnlyPeer集合修复_RDC实施报告_v1.md`。
## 代码交付 Git 核验(2026-10-09)
- Scoped 修复提交:`016bf5ad93e0fdd625034b29fedd150ea6af3943`,包含 iOS 实现、XCTest 源码、本 Step、实施报告和开发日志,共五个文件。
- 已 push 至 `origin/particle/main`;远端 `refs/heads/particle/main` 回读等于提交完整 SHA。
- 这是**尚未完成 XCTest / iPad 事故复测的代码交付**,不是 GSRL 正式发行;原 V22 Tag/Source Lock 不动。
- 原有 `vendor/expected-lite` 本地状态与两份未跟踪文档未触碰;未创建新 worktree。

View File

@@ -0,0 +1,20 @@
# STEP-108 V23 同源统一正式发行 v1
- 日期:2026-10-09(JST)
- 状态:**构建 PASS / 六项公网回读 PASS / GSRL 锁与 Tag 收尾中**
- 冻结源码提交:`87f3d1c59a52581fb8339f0ea7d106b9f4b94fef`
- 主目录:`/Users/tei/dev/MapLibreParticleLab`,分支 `particle/main`
- 版本:Android/RN `13.6.1-particle.23`,iOS Metal `6.29.0-particle.23`
- 前序修复:STEP-107 Darwin Style CoreOnly Peer 防崩溃代码已纳入。
## 分阶段事实
1. Prepare:冻结并 push 源码版本,commit `87f3d1c59a52581fb8339f0ea7d106b9f4b94fef`,版本互锁正确。
2. Build:`tools/particle_release_pipeline/release.sh 23 build`,exit 0;Android OpenGL 四 ABI Release、Maven Local、iOS Metal Bazel opt 与 RN tgz 均 PASS。日志:`/tmp/maplibre_v23_unified_release_build_20261009.log`。
3. Publish:`tools/particle_release_pipeline/release.sh 23 publish`,exit 0;六文件 NAS 公网逐字节回读 PASS,SwiftPM `6.29.0-particle.23` Tag 可取。日志:`/tmp/maplibre_v23_unified_release_publish_20261009.log`。
4. GSRL:六项文件实际 SHA-256 与下载地址见 `release/maplibre-particle/13.6.1-particle.23/SOURCE_LOCK.json`。
5. Finalize:正式源码 Tag `maplibre-particle/rn/13.6.1-particle.23` 及最终远端核验执行后记录。
## 未验收及禁区
- STEP-107 XCTest 与 iPad 直接点击崩溃场景尚未运行;不得宣称已实机修复。
- V23 Android 实机、React Native 宿主整体集成、iOS Metal 真机 NOT_TESTED。
- 原 V22 Tag/Source Lock 与发行制品不覆盖;禁止新建 worktree、擅自纳入无关 dirty 文件。

View File

@@ -0,0 +1,78 @@
# STEP-106 STEP-102 T2 Placement 边界修正 — RDC 实施报告 v1
## 1. 当前判定
**源码修正已提交、静态检查通过;Android/Kotlin 编译 NOT RUN、FZG 修正版设备验证 NOT RUN。**
本阶段专门处理 STEP-105 的 T2_TWO_FREE 未达到预期问题。保留原 STEP-105 PARTIAL 状态直至修正版 APK 手工编译和 FZG 定向运行 Gate 全部通过。没有改动核心 Renderer 的 Placement 容错判定。
## 2. 旧版同设备复现
设备 FZG1-2,Android 13,x86_64,ADB 192.168.200.139:5555。
先核对旧、新 APK 的包名相同为 org.maplibre.android.testapp、versionCode 均为14、证书 SHA-256 均为 e3f8fae6451ed6322259d090dd8e8788df554ab25ed89a01c78014f1d5ef8236。旧版文件 SHA-256 为 999f773276b29fffae05c1edc977e19df6cebecd94e4a4095508d0c5e97e07c2。
临时 adb install -r 安装旧版 13.4.1-particle.21,运行:
`adb -s 192.168.200.139:5555 shell am start -W -n org.maplibre.android.testapp/.activity.particle.ParticleCoreLayerValidationActivity --es step102_scenario T2_TWO_FREE`
流式 Logcat 在 tick29/30 记录:R0 placementCapable=false/gate=true/composite/reuse 正常;R1 placementCapable=true/gate=false/actual=live/fallback=static-placement/build0/reuse0。因而确认该现象旧 .21 **同样存在**,不是已证明由官方 Native 13.6.1 升级新引入。
立即 adb install -r 恢复已签名的新 13.6.1,返回 Success;设备已安装 base.apk 的 SHA-256 为 77bc0b238fb476494410a603443030b104d78f97a737307be52460bf5a68f5aa,和 Mac 现有新 APK 完全一致。随后重新启动不带 Extra 的 Particle Core Layer Validation Activity,版本仍 13.6.1。
证据位于 Git 忽略目录 `artifacts/maplibre-particle/step105-fzg-t2-rootcause-20261009/old21-t2-stream.log`,以及先前 `step105-fzg-runtime-20261009/09-step102-t2/stream.log`。
## 3. 可核对的源级原因
13.6.1 的 `src/mln/annotation/annotation_manager.cpp` 中,`AnnotationManager::updateStyle()` 会创建名为 `org.maplibre.annotations.points` 的 Symbol Layer 并追加至 Style。旧 .21 的 `src/mbgl/annotation/annotation_manager.cpp` 保留同样逻辑。
原测试计划为 R0 RETAINED → C0 CONTINUOUS 从 `step102-particle` 开始 → R1 RETAINED 从 `step102-upper` 开始并延伸到 Style 最后一层。测试 fixture 的 T2 `step102-upper` 是 Fill,但 R1 还包含默认追加的 Annotation Symbol。RenderSegment 层级的 `renderSegmentHasPlacementCapableLayer()` 只要在范围内看到 Symbol 即正确地判 true;在 fade/placement 期间 fallback `static-placement` 是安全行为,不能绕过。
这是对代码结构和两版设备现象的一致解释。真实 Style 的完整 `styleLayers` 数组先前没有输出,因此最终边界还要等待修正版新增诊断进一步核对;不得在这里宣称 Pixel/GPU 原因已由运行时完整证明。
## 4. 修改与保护边界
源码分支:`upgrade/android-native-13.6.1-20261009`。
代码提交:`86906c8a6af474a8d064c6f6d7d431e2a7e3efe6` — `test(android): isolate internal annotation symbol from STEP-102 retained ranges`。
两个文件:
1. `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step102PlacementFixture.kt`:为 T1、T2 共用计划增加最后一段 CONTINUOUS,startLayerId=`org.maplibre.annotations.points`,使原 R1 只包含自有上层。两场景保持相同边界。若默认 Layer 不存在,现有 Plan resolver 会因 boundary-layer-missing fail-closed,不会错误地忽略该层。
2. `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt`:在 tick8 输出一次 `STEP102_STYLE_LAYER_LAYOUT`,包含 Style Layer id/type/index 与四段 begin/end,用实际测试证据验证上层边界。
没有修改 `src/mln`、`include/mln`、Renderer、Shared Core、NavSea 或 iOS,也未改变任何公开 API/ABI。
新增 C2 使本测试由 R-C-R 变为 R-C-R-C,并可能失去“仅一个 Continuous”的 Particle fast-path 优化条件:这是仅用于证明两个 Retained 在真实内部 Annotation 层存在时安全局部复用的独立测试布局,不能当作生产性能验证或建议宿主一律使用四区段布局。
## 5. 当前检查结果
- `git diff --check` PASS。
- 静态合成 Style 解析:T1 的 R1 为 Symbol,T2 的 R1 为 Fill,Annotation Symbol 位于 C2;模拟边界覆盖完整且无空段,PASS(**非 Kotlin 构建、非真实 Runtime**)。
- Android Gradle/Kotlin Build:NOT RUN(用户手工编译)。
- 更新版 Validation APK:NOT BUILT。
- FZG 新计划的 T1/T2 tick8/130:NOT RUN。
- 其它 ABI / iOS / NavSea / Jest:NOT RUN。
## 6. 用户手工编译与 RDC 验收
Mac 终端:
```bash
cd /Users/tei/dev/MapLibreParticleLab-upgrade-android-1361
export JAVA_HOME=/usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
export ANDROID_HOME=/Users/tei/Library/Android/sdk
export ANDROID_SDK_ROOT="$ANDROID_HOME"
export PATH="$JAVA_HOME/bin:$ANDROID_HOME/platform-tools:$PATH"
export MAPLIBRE_VALIDATION_OUT_DIR="$PWD/artifacts/maplibre-particle/step106-t2-fix"
./tools/particle_android_validation/build_validation_apk.sh
```
脚本自带 Gradle `--console=plain`、Native ABI/体积/ZIP 检查。用户编译后由 RDC 确认 Git 源码 SHA、包名、签名、APK SHA,再 adb install -r 到 FZG。
验收应分别运行 STEP-102 `T1_FREE_SYMBOL` 与 `T2_TWO_FREE`(明确传 `--es step102_scenario <SCENARIO>`),在 tick8 和 tick130 读取 `STEP102_STYLE_LAYER_LAYOUT` / `STEP102_SAMPLE` / `STEP102_WINDOW_MARKER`。对 T2 需要 R0 和 R1 同时 placementCapable=false、gate=true、actualExecution=composite;各自 buildDelta=0、invalidationDelta=0、reuseDelta>0;C2 live 且保持 Z-order。T1 的 R1 为 Symbol,应继续按现有安全 gate fallback;检查样本和崩溃日志。之后最小回归 STEP-099 T4、STEP-101 Native reset、QBIN V4 Golden,失败须保留现场。
## 7. Gate
**SOURCE_FIXED / STATIC_PASS / BUILD_PENDING / DEVICE_PENDING**。
任何未编译、未安装新版修正 APK 的设备日志不能被用来宣称 STEP-106 PASS。待 RDC 收到用户手工编译成功的制品后,才进入下一阶段设备验收和最终关闭。

View File

@@ -0,0 +1,100 @@
# STEP-106 STEP-102 T2 Placement 边界修正 — Mac 编译与 FZG 实机回归完成报告 v1
## 一、结论
**STEP-106 CLOSED/PASS(仅 FZG1-2 Android x86_64/OpenGL Test App 的 T2 问题及定向回归)。**
源码保留 RenderSegment Placement 安全规则,仅修复 STEP-102 Test App 的分段计划,将默认附加的 Annotation Symbol 从 R1 Retained 中隔离到末尾 Continuous C2。更新版经 Mac Gradle 打包、FZG 设备安装、两项 STEP-102 场景及 STEP-099/STEP-101/QBIN 回归,验收符合各自目标。
## 二、源码、构建、APK
- 工作区:/Users/tei/dev/MapLibreParticleLab-upgrade-android-1361
- 分支:upgrade/android-native-13.6.1-20261009
- 构建输入 HEAD:23f7b8a4d7b33d3fb63cd8691fc3a2f070936d03;构建时 git_dirty=false。
- 上游 Integration main 已经在本轮开始前同步到 80667d6c343411d03a760425438e3d292f452118。
- 构建:Mac,JDK 17,Android SDK/NDK,x86_64 OpenGL Debug,tools/particle_android_validation/build_validation_apk.sh(Gradle --console=plain);成功退出,BUILD SUCCESS,Kotlin/Java/Native 包装检查通过。
- APK:artifacts/maplibre-particle/step106-t2-fix/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk;66,505,495 bytes;lib/x86_64/libmaplibre.so=39,970,088 bytes。
- APK SHA-256:0ef3b033cd33139db07258745e9cd9d023170ce69767e3f8a5159821776ec9ef。
- 其它 ABI 带入的仅是 Validation 脚本明示允许的 Sentry 库;MapLibre Native 和测试 Custom Layer 是 x86_64。APK ZIP 检查、脚本 ABI/体积检查 PASS。
- 打包日志与 source lock:artifacts/maplibre-particle/step106-t2-fix/build.log、build-info.txt。
## 三、FZG 安装
- 设备:FZG1-2 / Android 13 / ADB 192.168.200.139:5555 / x86_64。
- 包名:org.maplibre.android.testapp;versionName=13.6.1,versionCode=14。
- 新旧证书 SHA-256 一致:e3f8fae6451ed6322259d090dd8e8788df554ab25ed89a01c78014f1d5ef8236。
- 2026-10-09 13:08:17 JST,adb install -r 返回 Success,保留 App 数据。
- 完成后设备内 base.apk SHA-256 与 Mac 完全一致;最后恢复默认页面时再次独立回读核实。
## 四、STEP-102 T1/T2 两窗口验收
窗口均从 STEP102_WINDOW_MARKER baseline tick8 测至 final tick130,期间持续 ADB Logcat 收集。
修正后的实际 Style/Segment 层级日志(两组均在 FZG 真实记录):
- 层 0:step102-background(background)
- 层 1:step102-static-fill(fill)
- 层 2:step102-particle(raster-particle)
- 层 3:step102-upper(T1 为 symbol,T2 为 fill)
- 层 4:org.maplibre.annotations.points(MapLibre 内部 symbol)
四段为 R0 RETAINED[0,2)、C0 CONTINUOUS[2,3)、R1 RETAINED[3,4)、C2 CONTINUOUS[4,5)。
### T1_FREE_SYMBOL — PASS
- R0:placementCapable=false、gate=true、actual=composite,build 1→1、reuse 180→3862(**+3682**)、invalidation 2→2。
- R1:placementCapable=true、gate=false、actual=live,fallback=static-placement,reuse0。这一安全退回是正确行为。
- C2:MapLibre 内部 Annotation Symbol 持续 Live。
- Crash Buffer=0,Style Plan 接受。
### T2_TWO_FREE — PASS,原阻塞解除
- R0:placementCapable=false、gate=true、actual=composite,build 1→1,reuse 195→3878(**+3683**)、invalidation 7→7。
- R1:placementCapable=false、gate=true、actual=composite,build 1→1,reuse 195→3878(**+3683**)、invalidation 7→7。
- C2:内部 Annotation Symbol 继续 Live,不破坏 Placement 安全。
- Crash Buffer=0,Style Plan 接受。
原失败表现(R1 placementCapable=true/fallback=static-placement)不再存在。之前对旧 13.4.1-particle.21 的同设备对照证明原 fixture 也有相同问题,不属于 13.6.1 新增回归。
## 五、定向回归
| 回归 | 实际设备证据 | 结论 |
|---|---|---|
| STEP-099 T4 R-C-R | 动态 GeoJSON 更新 69 次;tick8/130 R0 build 1→1,reuse 193→3868(+3675),invalidation 13→13;crash0 | PASS |
| STEP-101 T1 | request1、destroy enter/return1、nativeReset enter/complete1、新 Renderer ready2、DONE1;crash0 | PASS |
| STEP-101 T2 | request20、destroy enter/return20、nativeReset enter/complete20、Renderer reset enter/complete40、newReady21、DONE1;crash0 | PASS |
| QBIN V4 Golden | clear/color/particle/time=ACCEPTED;T0/T1 均 ACCEPTED_INSERTED,Runtime V4 READY,UI 显示 V4 GOLDEN | PASS(最小烟测) |
| 默认画面恢复 | GLOBAL/ACTIVE/EAST/MIX0%/256,Test App 前台,13.6.1,设备 APK SHA一致,crash0 | PASS |
特别说明:STEP-101 T2 从场景启动前至终态使用 Mac 持续流式 Logcat,避免 Android 环形 Logcat 溢出造成的假性少计数。
## 六、原始证据位置
所有文件保存在 Mac Git 忽略目录:
artifacts/maplibre-particle/step106-t2-fix/
- build.log 与 build-info.txt
- runtime-T1/stream.log、crash.log
- runtime-T2/stream.log、crash.log
- runtime-STEP099-T4/stream.log、crash.log
- runtime-STEP101-T1/stream.log、crash.log
- runtime-STEP101-T2/stream.log、crash.log
- runtime-V4-smoke/load.log、v4.xml
- runtime-restored/window.xml、frame.png、crash.log
未将大型原始 Logcat、截图或 APK 纳入 Git。
## 七、保留边界
本次完成的是升级候选 **Android x86_64/OpenGL Test App** 上的明确阻塞与相关回归。STEP-102 改成 R-C-R-C 的测试布局引入第二 Continuous 区段,不能用它宣称生产环境单 Continuous Particle fast-path 的性能通过。没有修改 Shared Core 的 Renderer/Placement API、NavSea Host 或 iOS。
Android ARM64、RN 11.5.0 接入、iOS Metal、Vulkan、AAR 发行与全链用户验收均仍需独立完成。QB​​IN V4 本轮仅复测 T0/T1 Golden 插入,不声称六个时间控制动作再次全部验证;上轮 STEP-105 的六项回归保持独立历史事实。
本轮没有运行 Jest,没有使用 Codex 模型;编译/安装按用户此轮显式授权由 RDC 在 Mac 执行。
## 八、阶段判定和 Git
**STEP-106 / Test App 修正闭环:CLOSED / PASS。**
代码修正提交 86906c8a6af474a8d064c6f6d7d431e2a7e3efe6,之前根因记录提交 23f7b8a4d7b33d3fb63cd8691fc3a2f070936d03。本轮追加 recoder_step.md 与中文阶段记录,报告提交/远端 Push 以实际 Git 结果为准。正式 particle/main 不修改。

View File

@@ -0,0 +1,50 @@
# STEP-107 iOS Style CoreOnly Peer 集合修复 — RDC 实施报告 v1
## 1. 执行结论
**PARTIAL(代码已实施,iOS Native 构建 PASS,XCTest 与 iPad 事故场景 NOT RUN)**。
2026-10-09,直接在 Mac `/Users/tei/dev/MapLibreParticleLab` 的 `particle/main` 执行;没有创建 worktree、没有运行 Codex;未触碰 NavSea / Android / Shared Core / 任何正式发行 Tag。实际仍未获知崩溃现场的具体 nil Layer id/type。
## 2. Git 基线与修改范围
- 开工 HEAD:`24105b1be7a2c8bd3a547117a30887fbaa0a308b`,对应远端 `particle/main`。
- 修改:`platform/darwin/src/MLNStyle.mm` 与 `platform/darwin/test/MLNStyleTests.mm`。
- 新增:本报告、`docs/50_Step记录/STEP-107_iOS_StyleCoreOnlyPeer集合修复_v1.md`,追加 `粒子开发记录.md`。
- 与本任务无关的 `vendor/expected-lite` 子模块状态及原有两份未跟踪文档均保留。
- 未运行 Codex,因此 Codex 模型/推理档位及 Codex 完成事件不适用。
- 提交/PUSH 与远端校验以本报告后续 Git 核验补录为准。
## 3. 根因与实现
在 `MLNStyle.mm`,原 `layers` getter 对 `layerFromMBGLLayer:` 的 nil 返回直接调用 `addObject:`,遇到 CoreOnly Layer 则触发 Objective-C `NSInvalidArgumentException`。ParticleValidation、RasterParticle、WeatherColor 在 Darwin `MLNStyleLayerManager.mm` 仅注册 Core Factory,不具备 ObjC Peer。
修复后的 ObjC `layers` 只暴露有 Peer 的普通层,保留 C++ Style 内全部 CoreOnly Layer;缺失 Peer 时记录 DEBUG ID/type。KVC `countOfLayers`、单索引、range getter、插入/删除索引,以及 `aboveLayer:` 均按同一 ObjC 可见顺序处理。保持原始 C++ Style 与实际渲染的 Layer 存在及次序,不借由移除 Particle/Weather 来绕过问题。
新增 XCTest 用例通过 source-less `ParticleValidationLayer` 模拟夹在两个普通 Circle Layer 中间、以及位于顶部的 CoreOnly 层,验证 getter、KVC、区间读取、层标识、above/atIndex 插入和删除后 Native 层仍在。此 XCTest 尚未执行。
## 4. 验证记录
- `git diff --check`:PASS。
- Mac Xcode 26.3 / Bazel 8.6.0:
`bazel build //platform/ios:MapLibre.dynamic --color=no --curses=no --show_progress_rate_limit=15`:**PASS**,exit 0,耗时 982.084 秒,691 actions。
- 构建日志:`/tmp/maplibre_v22_ios_style_fix_bazel_20261009.log`。
- 新 XCFramework ZIP:`bazel-bin/platform/ios/MapLibre.dynamic.xcframework.zip`,SHA-256 `f16b7f2be761a101bb4b906b900175b95412abfeef09d715e2c9321b300a7b8a`;`unzip -tqq` PASS。
- 编译警告:链接器提示 `/opt/homebrew/lib` 不存在,但整个 Build PASS。
- `MLNStyleTests.mm` XCTest:**NOT RUN**,Bazel XCFramework 目标不包含测试文件。
- iOS Test Host / iPad / 唐津原始点击 / 双 Source Z-order / 天气粒子组合:**NOT RUN**。
- Android、Gradle、ADB、Jest、NavSea、正式 Release/Tag/Source Lock:**NOT RUN**。
## 5. 阻塞与验收边界
Native 编译 PASS 不代表原始崩溃完全修复。需运行 XCTest 并用包含本次改动的新 Native 二进制真机重放:普通对象、唐津港湾直接点击、两个以上可点击 Source、十字光标、天气粒子 Layer 开关和 Style 变更。需核实 DEBUG 输出实际无 Peer Layer 的 id/type,不应将 CoreOnly 推断当作唯一已证实源。
原 V22 正式 Tag 和分发 ZIP 未修改,当前构建仅作为本地未发行验证制品。任何正式版本迭代需新 Tag / Source Lock / SHA-256 / 远端校验,按 GSRL 执行。
## 6. Git 实际交付补录
- Scoped commit:`016bf5ad93e0fdd625034b29fedd150ea6af3943`,提交信息 `fix(ios): guard Darwin style CoreOnly layer peer access`。
- `git push origin HEAD:refs/heads/particle/main`:PASS(fast-forward,无 force)。
- `git ls-remote origin refs/heads/particle/main`:`016bf5ad93e0fdd625034b29fedd150ea6af3943`,和本地 HEAD 完全相同。
- 只提交五个本次任务文件;原有 dirty 子模块及用户的两份未跟踪文档保持不变。
- 修复状态继续为 **PARTIAL**:实际真机点击、XCTest 尚未通过,不能关闭 STEP-107 或进行新版本 GSRL 发行。

View File

@@ -10,7 +10,7 @@
},
"dependencies": {
"@navsea/maplibre-particle-rn": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.13.tgz",
"@maplibre/maplibre-react-native": "11.3.6",
"@maplibre/maplibre-react-native": "11.5.0",
"expo": "56.0.4",
"expo-asset": "~56.0.14",
"expo-file-system": "56.0.11",

View File

@@ -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.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.21")\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.6.1-particle.23")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.6.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`;
return project;
});
};

View File

@@ -2,6 +2,12 @@
## Unreleased
## 13.6.1-particle.23(Android / iOS 统一发行)
- Mac unified release pipeline 统一生成 Android Native、RN package 与 iOS SwiftPM/XCFramework;共享 Particle revision 23。
## 13.6.1-particle.22(Android / iOS 统一发行)
- Mac unified release pipeline 统一生成 Android Native、RN package 与 iOS SwiftPM/XCFramework;共享 Particle revision 22。
## 13.4.1-particle.21(Android / iOS 统一发行)
- Mac unified release pipeline 统一生成 Android Native、RN package 与 iOS SwiftPM/XCFramework;共享 Particle revision 21。

View File

@@ -9,7 +9,7 @@ $MLRN_PARTICLE = Object.new
def $MLRN_PARTICLE.post_install(installer)
unless defined?($MLRN) && $MLRN.respond_to?(:_add_spm_to_target)
raise "MapLibreParticleRN requires MapLibreReactNative's $MLRN._add_spm_to_target helper. Ensure MapLibreReactNative 11.3.6 is installed before calling $MLRN_PARTICLE.post_install."
raise "MapLibreParticleRN requires MapLibreReactNative's $MLRN._add_spm_to_target helper. Ensure MapLibreReactNative 11.5.0 is installed before calling $MLRN_PARTICLE.post_install."
end
unless defined?($MLRN_SPM_SPEC) && $MLRN_SPM_SPEC.is_a?(Hash)
@@ -44,7 +44,7 @@ Pod::Spec.new do |s|
s.platforms = { :ios => "15.1" }
s.source = { :git => package.dig("repository", "url"), :tag => s.version.to_s }
s.source_files = "ios/**/*.{h,m,mm,cpp}"
s.dependency "MapLibreReactNative", "11.3.6"
s.dependency "MapLibreReactNative", "11.5.0"
# $MLRN_PARTICLE.post_install reuses MapLibreReactNative's one MapLibre SPM
# product. Do not declare a MapLibre pod, vendored framework, or SPM product here.
install_modules_dependencies(s)

View File

@@ -1,6 +1,6 @@
# @navsea/maplibre-particle-rn
当前版本:`13.4.1-particle.21`。Android Native 必须配套 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.21`。本包是 React Native 的薄控制面,不负责 Map 组件、provider、HTTP、manifest、下载、缓存或 NavSea 业务策略。
当前版本:`13.6.1-particle.23`。Android Native 必须配套 `cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.23`。本包是 React Native 的薄控制面,不负责 Map 组件、provider、HTTP、manifest、下载、缓存或 NavSea 业务策略。
## 当前推荐主链:QBIN V4
@@ -24,11 +24,11 @@ V4 主要公共方法:`submitEncodedField`、`setEncodedColorBinding`、`setEn
## Map / Camera
Map 与 Camera 直接从 `@maplibre/maplibre-react-native@11.3.6` 使用。本包把 MLRN 作为 peer dependency,不转导出 MapLibre React Native。
Map 与 Camera 直接从 `@maplibre/maplibre-react-native@11.5.0` 使用。本包把 MLRN 作为 peer dependency,不转导出 MapLibre React Native。
## iOS 集成
autolinking 使用 `MapLibreParticleRN.podspec` 和 RN codegen 的 `WeatherRuntimeBridge` provider。pod 只依赖 `MapLibreReactNative`,不声明、链接或嵌入第二份 `MapLibre.framework`。Consumer 必须以 MLRN 11.3.6 的 `$MLRN_SPM_SPEC` 配置同一个 custom `MapLibre` product;Particle pod 不接受独立 MapLibre binary。
autolinking 使用 `MapLibreParticleRN.podspec` 和 RN codegen 的 `WeatherRuntimeBridge` provider。pod 只依赖 `MapLibreReactNative`,不声明、链接或嵌入第二份 `MapLibre.framework`。Consumer 必须以 MLRN 11.5.0 的 `$MLRN_SPM_SPEC` 配置同一个 custom `MapLibre` product;Particle pod 不接受独立 MapLibre binary。
Podfile 的 `post_install do |installer|` 必须同时调用 `$MLRN.post_install(installer)` 与 `$MLRN_PARTICLE.post_install(installer)`。两者复用同一个 `$MLRN_SPM_SPEC`:前者接入 MLRN/App,后者将同一 package reference/product 加到 `MapLibreParticleRN` pod target。Expo plugin 会幂等注入后一个调用;它不设置 `$MLRN_SPM_SPEC`,该值仍由 MLRN 官方 plugin 或宿主 Podfile 提供。
@@ -37,7 +37,7 @@ Podfile 的 `post_install do |installer|` 必须同时调用 `$MLRN.post_install
RN tgz:
```text
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.21.tgz
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.23.tgz
```
Native Maven repository:
@@ -49,7 +49,7 @@ https://pbf.jpcn.ccwu.cc/maven
Native coordinate:
```text
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.21
cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.23
```
Expo plugin 与 `android/build.gradle` 必须指向同一 `.21`。正常 Consumer 不复制 AAR、不依赖 MapLibreParticleLab 源码路径,也不以 Maven Local 作为公网验证主路径。完整 Linux 发布与 Win11 验证流程见 `docs/50_Step记录/STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md`。
Expo plugin 与 `android/build.gradle` 必须指向同一 `.23`。正常 Consumer 不复制 AAR、不依赖 MapLibreParticleLab 源码路径,也不以 Maven Local 作为公网验证主路径。完整 Linux 发布与 Win11 验证流程见 `docs/50_Step记录/STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md`。

View File

@@ -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.21"
implementation "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.23"
compileOnly project(":maplibre_maplibre-react-native")
testImplementation "junit:junit:4.13.2"
}

View File

@@ -20,7 +20,7 @@ const iosTag = "navsea-maplibre-particle-rn-maplibre-spm";
module.exports = function withNavSeaMapLibreParticle(config) {
config = withProjectBuildGradle(config, (project) => {
if (project.modResults.contents.includes(androidMarker)) return project;
project.modResults.contents += `\n${androidMarker}\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.21")\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${androidMarker}\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.6.1-particle.23")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.6.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`;
return project;
});

View File

@@ -1,6 +1,6 @@
{
"name": "@navsea/maplibre-particle-rn",
"version": "13.4.1-particle.21",
"version": "13.6.1-particle.23",
"description": "NavSea MapLibre Particle React Native pure Field Renderer API.",
"license": "BSD-2-Clause",
"main": "src/index.ts",
@@ -29,7 +29,7 @@
"url": "https://git.eyaku.jp/tei/MapLibreParticleLab"
},
"peerDependencies": {
"@maplibre/maplibre-react-native": "11.3.6",
"@maplibre/maplibre-react-native": "11.5.0",
"expo": ">=54.0.0",
"react": ">=19.1.0",
"react-native": ">=0.80.0"
@@ -53,6 +53,6 @@
}
},
"devDependencies": {
"@maplibre/maplibre-react-native": "11.3.6"
"@maplibre/maplibre-react-native": "11.5.0"
}
}

View File

@@ -105,6 +105,26 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
"fullCpuFrame=${snapshot.optLong("fullCpuFrame")} ranges=${ranges.joinToString(";")}"
diagnostics.text = "STEP-102 $message"
Log.i(TAG, "STEP102_SAMPLE $message")
if (step102Tick == 8L) {
// One-shot proof of actual runtime style ordering and annotation boundary.
val styleLayers = snapshot.optJSONArray("styleLayers")
val layerOrder = buildList {
for (index in 0 until (styleLayers?.length() ?: 0)) {
val layer = styleLayers?.optJSONObject(index) ?: continue
add("${layer.optInt("layerIndex")}:${layer.optString("layerId")}:${layer.optString("layerType")}")
}
}
val rangeOrder = buildList {
for (index in 0 until (segments?.length() ?: 0)) {
val segment = segments?.optJSONObject(index) ?: continue
add("${segment.optInt("segmentIndex")}:${segment.optString("strategy")}" +
"[${segment.optInt("beginIndex")},${segment.optInt("endExclusive")})" +
"=${segment.optJSONArray("layers")?.length() ?: 0}")
}
}
Log.i(TAG, "STEP102_STYLE_LAYER_LAYOUT scenario=$scenario layers=${layerOrder.joinToString(";")}" +
" segments=${rangeOrder.joinToString(";")}")
}
if (step102Tick == 8L || step102Tick == 130L) {
Log.i(TAG, "STEP102_WINDOW_MARKER phase=${if (step102Tick == 8L) "baseline" else "final"} " +
"scenario=$scenario tick=$step102Tick")

View File

@@ -3,7 +3,13 @@ package org.maplibre.android.testapp.activity.particle
import org.json.JSONArray
import org.json.JSONObject
/** Offline STEP-102 R-C-R fixture; only the upper retained range changes placement capability. */
/**
* Offline STEP-102 locality fixture.
* R0 / particle C0 / upper R1 keep identical boundaries for T1 and T2.
* MapLibre appends its internal annotation Symbol layer after the style layers:
* keep that layer in a final live C2 range, outside the upper Retained cache.
* Never bypass Renderer placement safety gates to make a fixture pass.
*/
internal object Step102PlacementFixture {
enum class Scenario { T1_FREE_SYMBOL, T2_TWO_FREE }
@@ -12,6 +18,8 @@ internal object Step102PlacementFixture {
private const val PARTICLE_SOURCE = "step102-field"
private const val PARTICLE_LAYER = "step102-particle"
private const val UPPER_LAYER = "step102-upper"
// Built into MapLibre AnnotationManager; it is a Symbol even when the fixture has no symbols.
private const val INTERNAL_ANNOTATION_LAYER = "org.maplibre.annotations.points"
fun styleJson(scenario: Scenario): String {
val sources = JSONObject()
@@ -56,7 +64,8 @@ internal object Step102PlacementFixture {
.put("segments", JSONArray()
.put(JSONObject().put("strategy", "RETAINED"))
.put(JSONObject().put("strategy", "CONTINUOUS").put("startLayerId", PARTICLE_LAYER))
.put(JSONObject().put("strategy", "RETAINED").put("startLayerId", UPPER_LAYER)))
.put(JSONObject().put("strategy", "RETAINED").put("startLayerId", UPPER_LAYER))
.put(JSONObject().put("strategy", "CONTINUOUS").put("startLayerId", INTERNAL_ANNOTATION_LAYER)))
.toString()
private fun geoJsonSource(geometryJson: String): JSONObject = JSONObject().put("type", "geojson")

View File

@@ -1 +1 @@
13.6.1
13.6.1-particle.23

View File

@@ -283,12 +283,20 @@ const MLNExceptionName MLNRedundantSourceIdentifierException =
// MARK: Style layers
- (NSArray<__kindof MLNStyleLayer *> *)layers {
auto layers = self.rawStyle->getLayers();
// Core-only layers (including Particle and WeatherColor) are intentionally
// not exposed as Objective-C peers. Keep them in the native Style for
// rendering, but never insert a missing peer into an NSArray.
auto rawLayers = self.rawStyle->getLayers();
NSMutableArray<__kindof MLNStyleLayer *> *styleLayers =
[NSMutableArray arrayWithCapacity:layers.size()];
for (auto layer : layers) {
MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer];
[styleLayers addObject:styleLayer];
[NSMutableArray arrayWithCapacity:rawLayers.size()];
for (auto rawLayer : rawLayers) {
MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:rawLayer];
if (styleLayer) {
[styleLayers addObject:styleLayer];
} else {
MLNLogDebug(@"Skipping native-only style layer id=%s type=%s (no ObjC peer)",
rawLayer->getID().c_str(), rawLayer->getTypeInfo()->type);
}
}
return styleLayers;
}
@@ -304,30 +312,30 @@ const MLNExceptionName MLNRedundantSourceIdentifierException =
}
- (NSUInteger)countOfLayers {
return self.rawStyle->getLayers().size();
// KVC indices count the same ObjC-visible layers as the public getter.
return self.layers.count;
}
- (MLNStyleLayer *)objectInLayersAtIndex:(NSUInteger)index {
auto layers = self.rawStyle->getLayers();
if (index >= layers.size()) {
NSArray<MLNStyleLayer *> *visibleLayers = self.layers;
if (index >= visibleLayers.count) {
[NSException raise:NSRangeException
format:@"No style layer at index %lu.", (unsigned long)index];
return nil;
}
auto layer = layers.at(index);
return [self layerFromMBGLLayer:layer];
return visibleLayers[index];
}
- (void)getLayers:(MLNStyleLayer **)buffer range:(NSRange)inRange {
auto layers = self.rawStyle->getLayers();
if (NSMaxRange(inRange) > layers.size()) {
NSArray<MLNStyleLayer *> *visibleLayers = self.layers;
if (inRange.location > visibleLayers.count ||
inRange.length > visibleLayers.count - inRange.location) {
[NSException raise:NSRangeException
format:@"Style layer range %@ is out of bounds.", NSStringFromRange(inRange)];
return;
}
NSUInteger i = 0;
for (auto layer = *(layers.rbegin() + inRange.location); i < inRange.length; ++layer, ++i) {
MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer];
buffer[i] = styleLayer;
for (NSUInteger i = 0; i < inRange.length; ++i) {
buffer[i] = visibleLayers[inRange.location + i];
}
}
@@ -339,37 +347,26 @@ const MLNExceptionName MLNRedundantSourceIdentifierException =
@"of MLNStyleLayer.",
styleLayer];
}
auto layers = self.rawStyle->getLayers();
if (index > layers.size()) {
NSArray<MLNStyleLayer *> *visibleLayers = self.layers;
if (index > visibleLayers.count) {
[NSException
raise:NSRangeException
format:@"Cannot insert style layer at out-of-bounds index %lu.", (unsigned long)index];
} else if (index == 0) {
try {
MLNStyleLayer *sibling = layers.size() ? [self layerFromMBGLLayer:layers.at(0)] : nil;
[styleLayer addToStyle:self belowLayer:sibling];
} catch (const std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
}
} else {
try {
MLNStyleLayer *sibling = [self layerFromMBGLLayer:layers.at(index)];
[styleLayer addToStyle:self belowLayer:sibling];
} catch (std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
}
return;
}
// Insert before the next visible layer, or above the entire native stack.
// Indices exposed to KVC must not count hidden core-only layers.
MLNStyleLayer *sibling = index < visibleLayers.count ? visibleLayers[index] : nil;
try {
[styleLayer addToStyle:self belowLayer:sibling];
} catch (const std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
}
}
- (void)removeObjectFromLayersAtIndex:(NSUInteger)index {
auto layers = self.rawStyle->getLayers();
if (index >= layers.size()) {
[NSException
raise:NSRangeException
format:@"Cannot remove style layer at out-of-bounds index %lu.", (unsigned long)index];
}
auto layer = layers.at(index);
MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer];
MLNStyleLayer *styleLayer = [self objectInLayersAtIndex:index];
[styleLayer removeFromStyle:self];
}
@@ -466,36 +463,22 @@ const MLNExceptionName MLNRedundantSourceIdentifierException =
sibling];
}
auto layers = self.rawStyle->getLayers();
std::string siblingIdentifier = sibling.identifier.UTF8String;
NSUInteger index = 0;
for (auto siblingLayer : layers) {
if (siblingLayer->getID() == siblingIdentifier) {
break;
}
index++;
// A hidden core-only layer may immediately follow sibling in the native
// stack. Find the next *visible* sibling to avoid passing a nil ObjC peer
// to addToStyle:belowLayer:.
NSArray<MLNStyleLayer *> *visibleLayers = self.layers;
NSUInteger siblingIndex = [visibleLayers
indexOfObjectPassingTest:^BOOL(MLNStyleLayer *candidate, NSUInteger idx, BOOL *stop) {
return [candidate.identifier isEqualToString:sibling.identifier];
}];
if (siblingIndex == NSNotFound) {
[NSException raise:NSInvalidArgumentException
format:@"The sibling layer %@ is not present in this style.", sibling];
return;
}
[self willChangeValueForKey:@"layers"];
if (index + 1 > layers.size()) {
[NSException raise:NSInvalidArgumentException
format:@"A style layer cannot be placed above %@ in the style. "
@"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].",
sibling];
} else if (index + 1 == layers.size()) {
try {
[layer addToStyle:self belowLayer:nil];
} catch (std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
}
} else {
MLNStyleLayer *nextSibling = [self layerFromMBGLLayer:layers.at(index + 1)];
try {
[layer addToStyle:self belowLayer:nextSibling];
} catch (std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
}
}
[self insertObject:layer inLayersAtIndex:siblingIndex + 1];
[self didChangeValueForKey:@"layers"];
}

View File

@@ -1,6 +1,13 @@
#import <Mapbox.h>
#import "MLNStyle_Private.h"
#import "MLNVectorTileSource_Private.h"
#include <mln/style/layers/particle_validation_layer.hpp>
#include <mln/style/style.hpp>
#include <memory>
#include <string>
#import "NSBundle+MLNAdditions.h"
#import <XCTest/XCTest.h>
@@ -238,6 +245,70 @@
XCTAssertEqual(self.style.layers.count, initialLayers.count);
}
// Native-only layers must remain renderable while ObjC/KVC accessors expose
// only layers for which Darwin can construct a valid MLNStyleLayer peer.
- (void)testCoreOnlyLayersPreserveObjCCollectionAndOrdering {
NSUInteger initialCount = self.style.layers.count;
NSUInteger initialNativeCount = self.style.rawStyle->getLayers().size();
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"core-only-test-source"
shape:nil
options:nil];
[self.style addSource:source];
MLNCircleStyleLayer *lower = [[MLNCircleStyleLayer alloc]
initWithIdentifier:@"core-only-visible-lower" source:source];
MLNCircleStyleLayer *upper = [[MLNCircleStyleLayer alloc]
initWithIdentifier:@"core-only-visible-upper" source:source];
[self.style addLayer:lower];
[self.style addLayer:upper];
// Mix hidden core layers between and above the public ObjC layers.
self.style.rawStyle->addLayer(
std::make_unique<mln::style::ParticleValidationLayer>("core-only-middle"),
std::string("core-only-visible-upper"));
self.style.rawStyle->addLayer(
std::make_unique<mln::style::ParticleValidationLayer>("core-only-top"));
NSArray<MLNStyleLayer *> *visible = self.style.layers;
XCTAssertEqual(visible.count, initialCount + 2);
XCTAssertEqual([self.style countOfLayers], initialCount + 2);
XCTAssertEqual(self.style.rawStyle->getLayers().size(), initialNativeCount + 4);
XCTAssertEqualObjects(visible[initialCount].identifier, lower.identifier);
XCTAssertEqualObjects(visible[initialCount + 1].identifier, upper.identifier);
XCTAssertNil([self.style layerWithIdentifier:@"core-only-middle"]);
XCTAssertNotNil(self.style.rawStyle->getLayer("core-only-middle"));
XCTAssertNotNil(self.style.rawStyle->getLayer("core-only-top"));
id kvcStyle = self.style;
XCTAssertEqualObjects([kvcStyle objectInLayersAtIndex:initialCount], lower);
MLNStyleLayer *__unsafe_unretained rangeLayers[2] = {nil, nil};
[kvcStyle getLayers:rangeLayers range:NSMakeRange(initialCount, 2)];
XCTAssertEqualObjects(rangeLayers[0], lower);
XCTAssertEqualObjects(rangeLayers[1], upper);
XCTAssertThrowsSpecificNamed([kvcStyle objectInLayersAtIndex:visible.count], NSException,
NSRangeException);
// The native layer immediately above lower lacks an ObjC peer. Inserting
// above lower must still work and retain the relative visible order.
MLNCircleStyleLayer *above = [[MLNCircleStyleLayer alloc]
initWithIdentifier:@"core-only-visible-above" source:source];
[self.style insertLayer:above aboveLayer:lower];
XCTAssertEqualObjects(self.style.layers[initialCount + 1], above);
XCTAssertEqualObjects(self.style.layers[initialCount + 2], upper);
// KVC's index applies to public peers, not the internal native stack.
MLNCircleStyleLayer *inserted = [[MLNCircleStyleLayer alloc]
initWithIdentifier:@"core-only-visible-inserted" source:source];
[self.style insertLayer:inserted atIndex:initialCount + 1];
XCTAssertEqualObjects(self.style.layers[initialCount + 1], inserted);
[kvcStyle removeObjectFromLayersAtIndex:initialCount + 1];
XCTAssertNil([self.style layerWithIdentifier:inserted.identifier]);
XCTAssertEqualObjects(self.style.layers[initialCount + 1], above);
XCTAssertNotNil(self.style.rawStyle->getLayer("core-only-middle"));
XCTAssertNotNil(self.style.rawStyle->getLayer("core-only-top"));
}
- (void)testAddingLayersTwice {
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
shape:nil

View File

@@ -72,3 +72,32 @@
- 候选原因:需要核实 Style 中默认注入的 annotation/symbol layer 是否被划入 R1,以及 Segment 范围;不要移除保守安全 gate 来凑 PASS。
- 收尾:已恢复正常 GLOBAL/ACTIVE/EAST/MIX 0%/256 默认粒子页面,FZG 前台 Test App 13.6.1,进程存活,Crash Buffer 0。整体状态 PARTIAL / T2 EXPECTATION FAIL。
- 原始证据保存在 Git 忽略目录 artifacts/maplibre-particle/step105-fzg-runtime-20261009/01-baseline 到 10-restored。未执行 Gradle、Jest、NavSea、iOS;下阶段先解决 T2 再复测。
## 2026-10-09 / STEP-106 / STEP-102 T2 Placement 边界修正(仅 Test App,待手工编译)
- 问题来源:STEP-105 FZG Android Native 13.6.1 运行回归中,STEP-102 T2_TWO_FREE 的 R1 虽然 fixture 自有的 step102-upper 是 Fill,却仍 placementCapable=true、gate=false、actual=live、fallback=static-placement、reuse=0。
- 老版对照:FZG1-2 临时覆盖安装原 13.4.1-particle.21 APK,签名相同、包名及 versionCode=14 不变;通过 --es step102_scenario T2_TWO_FREE 运行,Logcat tick29/30 **同样** R1 placementCapable=true、gate=false、live、static-placement、reuse=0,R0 composite/reuse。说明这不是 13.6.1 新引入的特有回归。
- 设备恢复:旧版比较后 adb install -r 恢复 **13.6.1**,设备 SHA-256 与当前新版 APK 完全一致:77bc0b238fb476494410a603443030b104d78f97a737307be52460bf5a68f5aa;已重新启动普通 Particle Core Layer Validation 页面,NavSea 未被修改。
- 根因:源码核对两版 AnnotationManager::updateStyle() 都自动在 Style 末尾添加 org.maplibre.annotations.points(Symbol),STEP-102 原 R-C-R plan 最后 Retained 从 step102-upper 一直延伸至 Style 末尾,包含内部 Symbol。Renderer 对 Symbol 的 placementCapable=true / static-placement fail-closed 是合理行为;T2 原计划没有隔离隐式 Annotation 层。
- 源码修正(限定 Android Test App):Step102PlacementFixture.planJson() 在两场景共同的 R0 → 粒子 C0 → 上层 R1 之后增加明确的尾部 C2,startLayerId=org.maplibre.annotations.points,保持 Style Z-order 和 Symbol 安全门,T1/T2 用相同边界;ParticleCoreLayerValidationActivity 在 tick8 增加一次性 STEP102_STYLE_LAYER_LAYOUT,打印真实 Style Layer id/type 和各 Segment index 范围。
- 代码提交:86906c8a6af474a8d064c6f6d7d431e2a7e3efe6;只改 Test App 两个 Kotlin 文件,无 Shared Core、Renderer、公开 API、NavSea、iOS 修改。
- 静态检查 PASS:git diff --check;合成 Layer 顺序下 T1 R1 Symbol / T2 R1 Fill / 尾部 Annotation Symbol 分段吻合,4 段覆盖完整(这是模拟,不是实际编译或设备验证)。
- **PENDING**:本轮没有执行 Android Gradle/Kotlin 编译、没有构建更新 APK,也没有对新修正后的 T1/T2 进行真实 FZG 测试。设备目前仍是已经安装的**修正前** 13.6.1 APK。遵守用户手工编译边界,需用户在 Mac 运行 ./tools/particle_android_validation/build_validation_apk.sh;之后 RDC 核对 APK SHA、更新安装并运行 STEP-102 T1/T2 tick8/130 + STEP-099 T4 + STEP-101 定向回归。
- 重要边界:为了测试场景保留 Annotation 的 Z-order,新计划成为 R-C-R-C;增加第二个 Continuous 会失去原 fixture 的单一 Continuous fast-path 资格,**该修正仅用于验证 Retained Placement 局部缓存**,不能拿它证明生产路径快帧性能。未确认的目标必须继续标记待验收。
- 详细报告:docs/报告/STEP-106_STEP102_T2_Placement边界修正_RDC实施报告_v1.md;原始对照日志:artifacts/maplibre-particle/step105-fzg-t2-rootcause-20261009/old21-t2-stream.log(Git 忽略目录)。
## 2026-10-09 / STEP-106 编译、FZG 安装和真机验收最终结果
- 用户明确允许本轮 Mac Android 编译与 FZG ADB 安装。Integration main 在执行前通过 RDC 同步到 80667d6c343411d03a760425438e3d292f452118。
- 源码基线:upgrade/android-native-13.6.1-20261009 / 23f7b8a4d7b33d3fb63cd8691fc3a2f070936d03;工作区干净,无额外代码更改。构建脚本 tools/particle_android_validation/build_validation_apk.sh,Mac Android x86_64/OpenGL Debug,BUILD SUCCESS,Kotlin 编译 PASS,APK ZIP/ABI/大小校验 PASS;build-info.txt git_dirty=false。
- 修正版 APK:artifacts/maplibre-particle/step106-t2-fix/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk;66,505,495 bytes,libmaplibre.so 39,970,088 bytes,SHA-256 0ef3b033cd33139db07258745e9cd9d023170ce69767e3f8a5159821776ec9ef。
- FZG1-2 / Android 13 / 192.168.200.139:5555:与现有签名相同,adb install -r 于 2026-10-09 13:08:17 JST 返回 Success;设备内 base.apk SHA 与 Mac 完全一致,versionName 13.6.1,保留数据。
- STEP-102 T1_FREE_SYMBOL tick8→130:真实 Style/Segment 顺序为 R0 [0,2) Background+Fill、C0 [2,3) Particle、R1 [3,4) 测试 Symbol、C2 [4,5) 内部 Annotation Symbol;R0 buildΔ0、reuseΔ+3682、invalidationΔ0、composite;R1 placementCapable=true/gate=false、Live、static-placement;crash 0。PASS。
- STEP-102 T2_TWO_FREE tick8→130:同样四段,测试 R1 为 Fill、Annotation Symbol 在 C2;R0 与 R1 都 placementCapable=false/gate=true/composite,分别 buildΔ0、reuseΔ+3683、invalidationΔ0;C2 Live;crash 0。**PASS,STEP-105 T2 阻塞解除**。
- STEP-099 T4 R-C-R 局部复用回归:69 次动态 GeoJSON 源更新,tick8/130 R0 build 1→1、reuse 193→3868(Δ+3675)、invalidation 13→13;crash 0。PASS。
- STEP-101 T1 Native 单次重建:request1、destroy enter/return1、nativeReset enter/complete1、newReady2、DONE1、crash0。PASS。
- STEP-101 T2 20 次重建:request20、destroy enter/return20、nativeReset entered/completed20、Renderer reset enter/complete40、newReady21、DONE1、crash0;使用 Mac 持续流式 Logcat 防止早期事件被设备环形缓冲覆盖。PASS。
- QBIN V4 Golden 最小回归:color/particle/clear/time 均 ACCEPTED,t0/t1 都 ACCEPTED_INSERTED,状态 V4 READY;PASS(不宣称 Seek/Play/Pause 六项全量本轮重复验收)。
- 最终 FZG 已冷启动回默认 Particle Core Layer Validation,GLOBAL/ACTIVE/EAST/MIX0%/256,versionName 13.6.1,APK SHA 0ef3b...,crash 0,前台 Test App;NavSea 未修改。
- 原始运行证据在 Mac Git 忽略目录 artifacts/maplibre-particle/step106-t2-fix/runtime-{T1,T2,STEP099-T4,STEP101-T1,STEP101-T2,V4-smoke,restored};本轮未执行 Jest、Android ARM64 编译、RN 11.5.0、iOS、Vulkan、性能 profile。
- 结论:**STEP-106 FZG x86_64/OpenGL 修正与定向回归 CLOSED/PASS**。新增第二 Continuous 只为测试 Annotation 隔离;不得将 R-C-R-C 场景当作生产 Particle fast-path 性能验收,更不代表整个 MapLibre 13.6.1 Android/iOS 发布完成。

24
release/README.md Normal file
View File

@@ -0,0 +1,24 @@
# Particle 正式发行入口
本仓库按 GSRL v1.0.0 维护源码发行记录。**正式二进制在 NAS/PBF 公网仓库,Git 不存放 AAR、tgz 或 XCFramework。**
## 当前推荐发行
| 发行代号 | Android Native / RN | iOS Metal | 状态 | 入口 |
| --- | --- | --- | --- | --- |
| v23 | `13.6.1-particle.23` | `6.29.0-particle.23` | GSRL Source Lock 正式发行 | [v23 版本说明](maplibre-particle/13.6.1-particle.23/README.md) |
| v22 | `13.6.1-particle.22` | `6.29.0-particle.22` | GSRL Source Lock 正式发行 | [v22 版本说明](maplibre-particle/13.6.1-particle.22/README.md) |
- v23 Source Lock:[SOURCE_LOCK.json](maplibre-particle/13.6.1-particle.23/SOURCE_LOCK.json)
- v22 Source Lock:[SOURCE_LOCK.json](maplibre-particle/13.6.1-particle.22/SOURCE_LOCK.json)
- v22 统一发布清单:[release-manifest.json](maplibre-particle/release-manifest.json)
- Maven:`https://pbf.jpcn.ccwu.cc/maven`
- RN 包:`https://pbf.jpcn.ccwu.cc/npm`
- iOS Metal 二进制:`https://pbf.jpcn.ccwu.cc/swift/maplibre-particle`
- SwiftPM:`https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git`
## 历史版本
v21:`13.4.1-particle.21` / iOS `6.28.0-particle.21`;正式 Git tag 为 `maplibre-particle/rn/13.4.1-particle.21`。它是 GSRL v22 启用前的历史发布,不应将当前 v22 Source Lock 套用到 v21。历史二进制仍可从此前 PBF Maven/npm/SwiftPM 的对应版本取得。
正式接收方应先阅读对应版本说明并校验 Source Lock,不能仅依据文件名安装。

View File

@@ -0,0 +1,55 @@
# MapLibre Particle 13.6.1-particle.22 正式发行
发行说明:本目录保存 **GSRL 源码与制品身份记录**;二进制正式存放在 NAS 的公网发行入口(不会提交进 Git)。v22 同源冻结提交为 `ea5bd393c664a54513531998bf8609a0535639f7`。
## 版本与发行范围
- Android OpenGL Native:`cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.22`,包含 `arm64-v8a`、`armeabi-v7a`、`x86`、`x86_64`。
- React Native package:`@navsea/maplibre-particle-rn@13.6.1-particle.22`,peer `@maplibre/maplibre-react-native@11.5.0`。
- iOS Metal:`MapLibre` XCFramework / SwiftPM `6.29.0-particle.22`(ios-arm64)。
- 正式源码 Tag:`maplibre-particle/rn/13.6.1-particle.22`;Build Source Commit 见 `SOURCE_LOCK.json`。
## 公网制品(NAS 发布)
| 制品 | 获取地址 |
| --- | --- |
| Android AAR | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.22/android-sdk-particle-opengl-13.6.1-particle.22.aar |
| Android POM | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.22/android-sdk-particle-opengl-13.6.1-particle.22.pom |
| Android Gradle Module Metadata | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.22/android-sdk-particle-opengl-13.6.1-particle.22.module |
| RN tgz | https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.22.tgz |
| iOS Metal XCFramework zip | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.22/MapLibre.dynamic.xcframework.zip |
| iOS XCFramework SHA-256 文件 | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.22/MapLibre.dynamic.xcframework.zip.sha256 |
完整 SHA-256 以本目录 `SOURCE_LOCK.json` 的 `files[]` 为权威记录。以上文件均已按 NAS 公网重新下载与原始构建文件逐字节比对。
## 使用方法
**Android**:Gradle Maven repository 增加 `https://pbf.jpcn.ccwu.cc/maven`,依赖:
`implementation("cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.22")`
本版 AAR 采用 OpenGL,不宣称包含 Vulkan 制品。
**React Native**:从上表 RN tgz 的 HTTPS URL 安装 `@navsea/maplibre-particle-rn`;宿主需使用 `@maplibre/maplibre-react-native@11.5.0`。按 package 内 `README.md` 配置 Android Maven、Expo plugin、iOS Podspec/codegen;勿继续锁定 v21 的 `13.4.1` Native。
**iOS**:SwiftPM repository:`https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git`,选择 exact tag `6.29.0-particle.22`,product `MapLibre`。将 RN iOS 依赖与该同源 Metal binary 按宿主 Post Install 说明接入。
## GSRL 来源核对与接收
1. 从此目录获取 `SOURCE_LOCK.json`,确认项目名、发行版本、Git Remote、完整 `gitCommit` 和 `gitRef`。
2. 按上述 URL 下载所需制品,重新计算各文件 SHA-256,与 `files[]` 逐项比较;确认 Gradle/RN/SwiftPM 依赖版本。
3. SHA 或来源不同不得按本版接收。已发行版本不可覆盖;修正须使用新版本。
## 验证边界
- Mac 统一源码构建、四 ABI Android Release AAR、iOS Metal ios-arm64 XCFramework、RN tgz 与公网逐字节回读:PASS。
- FZG1-2 x86_64/OpenGL Test App 的 STEP-106 粒子/RenderSegment/Renderer/QBIN 定向回归:PASS;该测试不能替代正式 Release AAR 在 NavSea 上的集成测试。
- Android ARM64 实机、RN 11.5.0 宿主整体集成、iOS Metal 真机:**本次未验证,不宣称这些运行环境 PASS**。
- GSRL PASS 仅代表源码与发行文件身份一致、可获取、依赖及说明完整。
## 溯源
- Build Source Commit:`ea5bd393c664a54513531998bf8609a0535639f7`
- 主发行清单:`release/maplibre-particle/release-manifest.json`
- GSRL Source Lock:`release/maplibre-particle/13.6.1-particle.22/SOURCE_LOCK.json`
- 原始构建与发布详细日志位于 Mac 临时工作区 `artifacts/maplibre-particle/`,**不是使用方获取入口**。

View File

@@ -0,0 +1,103 @@
{
"schemaVersion": "gsrl/source-lock/v1",
"project": "MapLibreParticleLab",
"releaseUnit": "Particle unified Android OpenGL / React Native / iOS Metal",
"version": "13.6.1-particle.22",
"versions": {
"android": "13.6.1-particle.22",
"reactNative": "13.6.1-particle.22",
"ios": "6.29.0-particle.22"
},
"source": {
"gitRemote": "https://git.eyaku.jp/tei/MapLibreParticleLab",
"gitCommit": "ea5bd393c664a54513531998bf8609a0535639f7",
"gitRef": "refs/tags/maplibre-particle/rn/13.6.1-particle.22",
"note": "gitCommit 是实际构建所用的冻结源代码提交;发行清单和 GSRL 记录在后续发行提交。发行 Tag 保留完整历史,不要求 Tag 指向的发行记录提交等于构建源码提交。"
},
"files": [
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.22.aar",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.22/android-sdk-particle-opengl-13.6.1-particle.22.aar",
"sha256": "ed62a67a0882896d3d59f315351d6807941881333494590bc943a858b213a130",
"sizeBytes": 16541912
},
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.22.pom",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.22/android-sdk-particle-opengl-13.6.1-particle.22.pom",
"sha256": "bf66dad234d763aa9ad7ed17576177219063cd5e2a8ff32593608913bc442b3c",
"sizeBytes": 5687
},
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.22.module",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.22/android-sdk-particle-opengl-13.6.1-particle.22.module",
"sha256": "a64e1ac9b9562f947b38c0553679c94aba1b912147825fec6aedfa72fd843f43",
"sizeBytes": 6999
},
{
"releaseUnit": "reactNative",
"name": "navsea-maplibre-particle-rn-13.6.1-particle.22.tgz",
"location": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.22.tgz",
"sha256": "dcd30dd64a3d140934f8ea3a042cbac5c0450e0510c017127824d6a1a53557b5",
"sizeBytes": 29008
},
{
"releaseUnit": "ios",
"name": "MapLibre.dynamic.xcframework.zip",
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.22/MapLibre.dynamic.xcframework.zip",
"sha256": "79b1b8dcd6c647afc150fc33f0771ae81c3ca0c8be93a9a9fa6cce49a1776670",
"sizeBytes": 4147794
},
{
"releaseUnit": "ios",
"name": "MapLibre.dynamic.xcframework.zip.sha256",
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.22/MapLibre.dynamic.xcframework.zip.sha256",
"sha256": "2b5a41897f83b1070c085d07e9e780756fbec599c826dfa3f43d2598751dcfa0",
"sizeBytes": 99
}
],
"dependencies": [
{
"name": "@maplibre/maplibre-react-native",
"version": "11.5.0",
"role": "React Native host peer, iOS MapLibreReactNative Pod"
},
{
"name": "React Native",
"version": ">=0.80.0",
"role": "RN package peer requirement"
},
{
"name": "Expo",
"version": ">=54.0.0 (optional)",
"role": "RN optional peer"
},
{
"name": "Android Native SDK",
"version": "13.6.1-particle.22",
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.22",
"role": "OpenGL AAR, arm64-v8a/armeabi-v7a/x86/x86_64"
},
{
"name": "iOS SwiftPM MapLibre",
"version": "6.29.0-particle.22",
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git",
"role": "Exact version, product MapLibre, Metal ios-arm64"
}
],
"usage": "release/maplibre-particle/13.6.1-particle.22/README.md",
"releaseManifest": "release/maplibre-particle/release-manifest.json",
"status": "RELEASED",
"validation": {
"publicReadback": "PASS",
"buildSourceCommit": "ea5bd393c664a54513531998bf8609a0535639f7",
"androidBuild": "PASS - four ABI OpenGL Release AAR",
"androidDevice": "FZG1-2 x86_64 OpenGL Test App smoke/regression PASS (STEP-106; release AAR separate)",
"androidArm64Device": "NOT_TESTED",
"reactNative115HostIntegration": "NOT_TESTED",
"iosMetalDevice": "NOT_TESTED",
"note": "GSRL verifies source and artifact identity, not runtime certification of every target."
}
}

View File

@@ -0,0 +1,59 @@
# MapLibre Particle 13.6.1-particle.23 正式发行
发行说明:本目录保存 **GSRL 源码与制品身份记录**;二进制正式存放在 NAS 的公网发行入口(不会提交进 Git)。v23 同源冻结提交为 `87f3d1c59a52581fb8339f0ea7d106b9f4b94fef`。
## 版本与发行范围
- Android OpenGL Native:`cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.23`,包含 `arm64-v8a`、`armeabi-v7a`、`x86`、`x86_64`。
- React Native package:`@navsea/maplibre-particle-rn@13.6.1-particle.23`,peer `@maplibre/maplibre-react-native@11.5.0`。
- iOS Metal:`MapLibre` XCFramework / SwiftPM `6.29.0-particle.23`(ios-arm64)。
- 正式源码 Tag:`maplibre-particle/rn/13.6.1-particle.23`;Build Source Commit 见 `SOURCE_LOCK.json`。
## 公网制品(NAS 发布)
| 制品 | 获取地址 |
| --- | --- |
| Android AAR | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.23/android-sdk-particle-opengl-13.6.1-particle.23.aar |
| Android POM | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.23/android-sdk-particle-opengl-13.6.1-particle.23.pom |
| Android Gradle Module Metadata | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.23/android-sdk-particle-opengl-13.6.1-particle.23.module |
| RN tgz | https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.23.tgz |
| iOS Metal XCFramework zip | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.23/MapLibre.dynamic.xcframework.zip |
| iOS XCFramework SHA-256 文件 | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.23/MapLibre.dynamic.xcframework.zip.sha256 |
完整 SHA-256 以本目录 `SOURCE_LOCK.json` 的 `files[]` 为权威记录。以上文件均已按 NAS 公网重新下载与原始构建文件逐字节比对。
## 使用方法
**Android**:Gradle Maven repository 增加 `https://pbf.jpcn.ccwu.cc/maven`,依赖:
`implementation("cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.23")`
本版 AAR 采用 OpenGL,不宣称包含 Vulkan 制品。
**React Native**:从上表 RN tgz 的 HTTPS URL 安装 `@navsea/maplibre-particle-rn`;宿主需使用 `@maplibre/maplibre-react-native@11.5.0`。按 package 内 `README.md` 配置 Android Maven、Expo plugin、iOS Podspec/codegen;勿继续锁定 v21 的 `13.4.1` Native。
**iOS**:SwiftPM repository:`https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git`,选择 exact tag `6.29.0-particle.23`,product `MapLibre`。将 RN iOS 依赖与该同源 Metal binary 按宿主 Post Install 说明接入。
## GSRL 来源核对与接收
1. 从此目录获取 `SOURCE_LOCK.json`,确认项目名、发行版本、Git Remote、完整 `gitCommit` 和 `gitRef`。
2. 按上述 URL 下载所需制品,重新计算各文件 SHA-256,与 `files[]` 逐项比较;确认 Gradle/RN/SwiftPM 依赖版本。
3. SHA 或来源不同不得按本版接收。已发行版本不可覆盖;修正须使用新版本。
## 验证边界
- Mac 统一源码构建、四 ABI Android Release AAR、iOS Metal ios-arm64 XCFramework、RN tgz 与公网逐字节回读:PASS。
- 本版 V23 尚未执行 Android 实机 / RN 11.5.0 宿主整体验收;不得引用 V22 的 STEP-106 通过事实冒充 V23 运行时测试。
- Android 实机、RN 11.5.0 宿主整体集成、iOS Metal 真机:**V23 尚未验证**。STEP-107 已包含 iOS nil Peer 崩溃代码修复,但唐津港湾 iPad 直接点击及 XCTest 仍未完成,不宣称崩溃实机修复 PASS。
- GSRL PASS 仅代表源码与发行文件身份一致、可获取、依赖及说明完整。
## 溯源
- Build Source Commit:`87f3d1c59a52581fb8339f0ea7d106b9f4b94fef`
- 主发行清单:`release/maplibre-particle/release-manifest.json`
- GSRL Source Lock:`release/maplibre-particle/13.6.1-particle.23/SOURCE_LOCK.json`
- 原始构建与发布详细日志位于 Mac 临时工作区 `artifacts/maplibre-particle/`,**不是使用方获取入口**。
## V23 修复范围
本版包含 STEP-107 的 Darwin MLNStyle CoreOnly Layer 无 ObjC Peer 的集合访问保护及层顺序修正。原 V22 发行身份保持不变,后续真机验收状态单独追踪。

View File

@@ -0,0 +1,104 @@
{
"schemaVersion": "gsrl/source-lock/v1",
"project": "MapLibreParticleLab",
"releaseUnit": "Particle unified Android OpenGL / React Native / iOS Metal",
"version": "13.6.1-particle.23",
"versions": {
"android": "13.6.1-particle.23",
"reactNative": "13.6.1-particle.23",
"ios": "6.29.0-particle.23"
},
"source": {
"gitRemote": "https://git.eyaku.jp/tei/MapLibreParticleLab",
"gitCommit": "87f3d1c59a52581fb8339f0ea7d106b9f4b94fef",
"gitRef": "refs/tags/maplibre-particle/rn/13.6.1-particle.23",
"note": "gitCommit为实际构建的V23冻结源码提交;发行清单与Source Lock在其后续发行提交中创建,Tag保留历史。"
},
"files": [
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.23.aar",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.23/android-sdk-particle-opengl-13.6.1-particle.23.aar",
"sha256": "263821b686a569110ad704f817b288522eb2a153f49bf4162ace2226b28220b7",
"sizeBytes": 16541816
},
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.23.pom",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.23/android-sdk-particle-opengl-13.6.1-particle.23.pom",
"sha256": "15e63a9936b015c6539ec7cdad271b4472e879e628e4f5ea9c3f1f3e581c61b9",
"sizeBytes": 5687
},
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.23.module",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.23/android-sdk-particle-opengl-13.6.1-particle.23.module",
"sha256": "d7fbf0aef8128a55c8a4d9b591e131a495d87fce6486b5e01834b2cc14635fb1",
"sizeBytes": 6999
},
{
"releaseUnit": "reactNative",
"name": "navsea-maplibre-particle-rn-13.6.1-particle.23.tgz",
"location": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.23.tgz",
"sha256": "6d8d2fa2312deee97fe9304c62887cd11490a70bc08e4a90f4ee8cf7ec83ed87",
"sizeBytes": 29009
},
{
"releaseUnit": "ios",
"name": "MapLibre.dynamic.xcframework.zip",
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.23/MapLibre.dynamic.xcframework.zip",
"sha256": "9dbc477458377ab7e207bfd125d8f4106d3d83e7f923499a7bc048e39f74a76a",
"sizeBytes": 4147380
},
{
"releaseUnit": "ios",
"name": "MapLibre.dynamic.xcframework.zip.sha256",
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.23/MapLibre.dynamic.xcframework.zip.sha256",
"sha256": "df1a37167f151f9d0ef215f18be60b8181b1b80f26584fe63dec7307a9c80293",
"sizeBytes": 99
}
],
"dependencies": [
{
"name": "@maplibre/maplibre-react-native",
"version": "11.5.0",
"role": "React Native host peer, iOS MapLibreReactNative Pod"
},
{
"name": "React Native",
"version": ">=0.80.0",
"role": "RN package peer requirement"
},
{
"name": "Expo",
"version": ">=54.0.0 (optional)",
"role": "RN optional peer"
},
{
"name": "Android Native SDK",
"version": "13.6.1-particle.23",
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.23",
"role": "OpenGL AAR, arm64-v8a/armeabi-v7a/x86/x86_64"
},
{
"name": "iOS SwiftPM MapLibre",
"version": "6.29.0-particle.23",
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git",
"role": "Exact version, product MapLibre, Metal ios-arm64"
}
],
"usage": "release/maplibre-particle/13.6.1-particle.23/README.md",
"releaseManifest": "release/maplibre-particle/release-manifest.json",
"status": "RELEASED",
"validation": {
"publicReadback": "PASS - release.sh 23 publish, all six files retrieved and byte-compared",
"androidBuild": "PASS - four ABI OpenGL Release AAR",
"iosMetalBuild": "PASS - Bazel opt Metal ios-arm64 XCFramework",
"reactNativePack": "PASS - npm pack",
"androidDevice": "NOT_TESTED - V23",
"reactNative115HostIntegration": "NOT_TESTED - V23",
"iosMetalDevice": "NOT_TESTED - V23",
"iosMapTapCrashRegression": "NOT_TESTED - STEP-107 code included, iPad direct-tap validation pending",
"note": "GSRL PASS means source/artifact identity and availability, not runtime device certification."
}
}

View File

@@ -9,8 +9,8 @@ let package = Package(
targets: [
.binaryTarget(
name: "MapLibre",
url: "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.28.0-particle.21/MapLibre.dynamic.xcframework.zip",
checksum: "fd78ed8f504c3d0ee969954f44501719b94e699aa5b160de849e712b32d24f72"
url: "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.23/MapLibre.dynamic.xcframework.zip",
checksum: "9dbc477458377ab7e207bfd125d8f4106d3d83e7f923499a7bc048e39f74a76a"
),
]
)

View File

@@ -1,14 +1,14 @@
# MapLibre Particle iOS 发行
当前正式发行:6.28.0-particle.21
当前正式发行:6.29.0-particle.23
- Renderer:Metal
- Product / Module:MapLibre
- Binary:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.28.0-particle.21/MapLibre.dynamic.xcframework.zip
- SHA-256:fd78ed8f504c3d0ee969954f44501719b94e699aa5b160de849e712b32d24f72
- Binary:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.23/MapLibre.dynamic.xcframework.zip
- SHA-256:9dbc477458377ab7e207bfd125d8f4106d3d83e7f923499a7bc048e39f74a76a
- SwiftPM:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git
- Exact Version:6.28.0-particle.21
- Source Commit:16a5d15f164dc93d220e3fac62de66a9c9527ccc
- Exact Version:6.29.0-particle.23
- Source Commit:87f3d1c59a52581fb8339f0ea7d106b9f4b94fef
构建命令固定为:

View File

@@ -1,20 +1,20 @@
{
"schemaVersion": 2,
"status": "PUBLISHED_PUBLIC_READBACK_PASS",
"distributionVersion": "6.28.0-particle.21",
"distributionVersion": "6.29.0-particle.23",
"moduleName": "MapLibre",
"compilationMode": "opt",
"artifact": {
"fileName": "MapLibre.dynamic.xcframework.zip",
"sizeBytes": 4121573,
"sha256": "fd78ed8f504c3d0ee969954f44501719b94e699aa5b160de849e712b32d24f72",
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.28.0-particle.21/MapLibre.dynamic.xcframework.zip"
"sizeBytes": 4147380,
"sha256": "9dbc477458377ab7e207bfd125d8f4106d3d83e7f923499a7bc048e39f74a76a",
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.23/MapLibre.dynamic.xcframework.zip"
},
"sourceCommit": "16a5d15f164dc93d220e3fac62de66a9c9527ccc",
"sourceCommit": "87f3d1c59a52581fb8339f0ea7d106b9f4b94fef",
"swiftPackage": {
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git",
"requirement": "exactVersion",
"version": "6.28.0-particle.21",
"version": "6.29.0-particle.23",
"productName": "MapLibre"
},
"notes": "Metal renderer optimized release built with bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic"

View File

@@ -1,49 +1,49 @@
{
"schemaVersion": 2,
"releaseState": "PUBLISHED_PUBLIC_READBACK_PASS",
"particleRevision": 21,
"sourceCommit": "16a5d15f164dc93d220e3fac62de66a9c9527ccc",
"generatedAtUtc": "2026-10-08T04:32:42.009872+00:00",
"particleRevision": 23,
"sourceCommit": "87f3d1c59a52581fb8339f0ea7d106b9f4b94fef",
"generatedAtUtc": "2026-10-09T14:48:11.461982+00:00",
"reactNative": {
"name": "@navsea/maplibre-particle-rn",
"version": "13.4.1-particle.21",
"version": "13.6.1-particle.23",
"artifact": {
"fileName": "navsea-maplibre-particle-rn-13.4.1-particle.21.tgz",
"sizeBytes": 28965,
"sha256": "8862dda5ad03727967465edea42053c71e59485e45afe7b99ed153d5c0a27eb5",
"url": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.21.tgz"
"fileName": "navsea-maplibre-particle-rn-13.6.1-particle.23.tgz",
"sizeBytes": 29009,
"sha256": "6d8d2fa2312deee97fe9304c62887cd11490a70bc08e4a90f4ee8cf7ec83ed87",
"url": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.23.tgz"
},
"mapLibreReactNative": "11.3.6"
"mapLibreReactNative": "11.5.0"
},
"android": {
"version": "13.4.1-particle.21",
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.21",
"version": "13.6.1-particle.23",
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.23",
"renderer": "opengl",
"artifact": {
"fileName": "android-sdk-particle-opengl-13.4.1-particle.21.aar",
"sizeBytes": 170777014,
"sha256": "59248c9a80aafa03da202a2666cfefe919090a16ae21d425370fcf8b39d0898c",
"url": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.4.1-particle.21/android-sdk-particle-opengl-13.4.1-particle.21.aar"
"fileName": "android-sdk-particle-opengl-13.6.1-particle.23.aar",
"sizeBytes": 16541816,
"sha256": "263821b686a569110ad704f817b288522eb2a153f49bf4162ace2226b28220b7",
"url": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.23/android-sdk-particle-opengl-13.6.1-particle.23.aar"
},
"ndk": "28.2.13676358",
"gradle": "9.5.1",
"jdk": "openjdk version \"17.0.20.1\" 2026-08-18"
},
"ios": {
"distributionVersion": "6.28.0-particle.21",
"distributionVersion": "6.29.0-particle.23",
"renderer": "metal",
"compilationMode": "opt",
"moduleName": "MapLibre",
"artifact": {
"fileName": "MapLibre.dynamic.xcframework.zip",
"sizeBytes": 4121573,
"sha256": "fd78ed8f504c3d0ee969954f44501719b94e699aa5b160de849e712b32d24f72",
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.28.0-particle.21/MapLibre.dynamic.xcframework.zip"
"sizeBytes": 4147380,
"sha256": "9dbc477458377ab7e207bfd125d8f4106d3d83e7f923499a7bc048e39f74a76a",
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.23/MapLibre.dynamic.xcframework.zip"
},
"swiftPackage": {
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git",
"requirement": "exactVersion",
"version": "6.28.0-particle.21",
"version": "6.29.0-particle.23",
"productName": "MapLibre"
},
"xcode": "Xcode 26.3 Build version 17C529 ",

View File

@@ -77,7 +77,7 @@ def main() -> None:
"name": "@navsea/maplibre-particle-rn",
"version": android_version,
"artifact": tgz,
"mapLibreReactNative": "11.3.6",
"mapLibreReactNative": json.loads((ROOT / "packages/maplibre-particle-rn/package.json").read_text())["peerDependencies"]["@maplibre/maplibre-react-native"],
},
"android": {
"version": android_version,

View File

@@ -20,14 +20,20 @@ def base_version(value: str) -> str:
return re.sub(r"-particle\.\d+$", "", value.strip())
def replace_current_particle(path: Path, base: str, target: str) -> None:
def replace_current_particle(path: Path, previous: str, target: str) -> None:
text = path.read_text()
pattern = rf"{re.escape(base)}-particle\.\d+"
updated, count = re.subn(pattern, target, text)
updated, count = re.subn(re.escape(previous), target, text)
if count == 0:
die(f"no particle version reference found in {path}")
die(f"previous RN/Native version {previous} not found in {path}")
path.write_text(updated)
def replace_exact(path: Path, old: str, new: str) -> None:
text = path.read_text()
if text.count(old) != 1:
die(f"expected exactly one version reference {old!r} in {path}")
path.write_text(text.replace(old, new, 1))
def update_changelog(target: str, revision: int) -> None:
path = RN_ROOT / "CHANGELOG.md"
text = path.read_text()
@@ -64,7 +70,11 @@ def main() -> None:
package_path = RN_ROOT / "package.json"
package = json.loads(package_path.read_text())
previous_rn_version = package["version"]
package["version"] = android_version
# v22 upgrades the host MapLibre React Native baseline together with native.
package["peerDependencies"]["@maplibre/maplibre-react-native"] = "11.5.0"
package["devDependencies"]["@maplibre/maplibre-react-native"] = "11.5.0"
package_path.write_text(json.dumps(package, ensure_ascii=False, indent=2) + "\n")
for relative in (
@@ -72,15 +82,14 @@ def main() -> None:
"packages/maplibre-particle-rn/app.plugin.js",
"integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js",
):
replace_current_particle(ROOT / relative, android_base, android_version)
replace_current_particle(ROOT / relative, previous_rn_version, android_version)
readme = RN_ROOT / "README.md"
readme_text = readme.read_text()
readme_text = re.sub(
rf"{re.escape(android_base)}-particle\.\d+",
android_version,
readme_text,
)
if previous_rn_version not in readme_text:
die("README missing previous RN version")
readme_text = readme_text.replace(previous_rn_version, android_version)
readme_text = readme_text.replace("11.3.6", "11.5.0")
readme_text = re.sub(
r"必须指向同一 `\.\d+`",
f"必须指向同一 `.{revision}`",
@@ -88,6 +97,39 @@ def main() -> None:
)
readme.write_text(readme_text)
# The Expo plugins already pin the upstream native base on subsequent
# revisions of the same MapLibre release. Accept the current base without
# requiring the long-obsolete 13.4.1 literal on every release.
for relative in (
"packages/maplibre-particle-rn/app.plugin.js",
"integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js",
):
path = ROOT / relative
text = path.read_text()
matches = re.findall(r'nativeVersion", "([0-9]+\.[0-9]+\.[0-9]+)"', text)
if len(matches) != 1:
die(f"expected one Expo nativeVersion in {path}")
current_native = matches[0]
if current_native not in {base_version(previous_rn_version), android_base}:
die(f"unexpected Expo nativeVersion {current_native} in {path}")
if current_native != android_base:
replace_exact(path,
f'nativeVersion", "{current_native}"',
f'nativeVersion", "{android_base}"')
podspec = RN_ROOT / "MapLibreParticleRN.podspec"
podtext = podspec.read_text()
if podtext.count("11.3.6") == 2:
# One-time upgrade from the original MapLibre RN peer baseline.
podspec.write_text(podtext.replace("11.3.6", "11.5.0"))
elif podtext.count("11.5.0") != 2:
die("podspec must contain exactly two pinned RN 11.5.0 references")
host_path = ROOT / "integration/react-native/MapLibreParticleRNTestHost/package.json"
host = json.loads(host_path.read_text())
host["dependencies"]["@maplibre/maplibre-react-native"] = "11.5.0"
host_path.write_text(json.dumps(host, ensure_ascii=False, indent=2) + "\n")
update_changelog(android_version, revision)
print(f"PARTICLE_REVISION={revision}")

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
@@ -8,6 +9,7 @@ from pathlib import Path
PUBLIC_ROOT = "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle"
GIT_REMOTE = "nas:/volume1/work/pbf/swift/maplibre-particle-spm.git"
WORK = Path.home() / "dev/MapLibreParticleDistribution"
ROOT = Path(__file__).resolve().parents[2]
def run(*args: str, cwd: Path | None = None) -> str:
@@ -26,6 +28,8 @@ def main() -> None:
if len(sys.argv) != 3:
raise SystemExit("usage: publish_swiftpm.py <ios-version> <zip-sha256>")
version, checksum = sys.argv[1], sys.argv[2]
rn_pkg = json.loads((ROOT / "packages/maplibre-particle-rn/package.json").read_text())
rn_version = rn_pkg["peerDependencies"]["@maplibre/maplibre-react-native"]
if not WORK.exists():
run("git", "clone", GIT_REMOTE, str(WORK))
@@ -66,7 +70,7 @@ let package = Package(
(WORK / "README.md").write_text(
"# MapLibre Particle SwiftPM Distribution\n\n"
"该仓库只保存 NavSea MapLibre Particle iOS binary distribution 的 SwiftPM manifest。\n"
"产品和模块名固定为 MapLibre,供 MapLibre React Native 11.3.6 消费。\n\n"
f"产品和模块名固定为 MapLibre,供 MapLibre React Native {rn_version} 消费。\n\n"
f"当前版本:{version}。\n"
f"对应 binary:{url}\n"
)

View File

@@ -73,6 +73,8 @@ prepare_release() {
packages/maplibre-particle-rn/app.plugin.js \
packages/maplibre-particle-rn/README.md \
packages/maplibre-particle-rn/CHANGELOG.md \
packages/maplibre-particle-rn/MapLibreParticleRN.podspec \
integration/react-native/MapLibreParticleRNTestHost/package.json \
integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js
if ! git -C "$ROOT" diff --cached --quiet; then
git -C "$ROOT" commit -m "release: prepare unified particle.$REV"
@@ -145,7 +147,10 @@ publish_release() {
local remote_aar_sha remote_rn_sha remote_ios_sha
local pom_name module_name
source_commit="$(git -C "$ROOT" rev-parse HEAD)"
source_commit="${PARTICLE_RELEASE_SOURCE_COMMIT:-$(git -C "$ROOT" rev-parse HEAD)}"
[[ "$source_commit" =~ ^[0-9a-f]{40}$ ]] || die "invalid frozen release source SHA"
git -C "$ROOT" cat-file -e "$source_commit^{commit}" || die "release source commit unavailable"
git -C "$ROOT" merge-base --is-ancestor "$source_commit" HEAD || die "release source commit is not an ancestor of current HEAD"
android_root="/volume1/work/pbf/maven/cc/navsea/maplibre/android-sdk-particle-opengl"
android_final="$android_root/$ANDROID_VERSION"
android_tmp="$android_root/.$ANDROID_VERSION.uploading"

View File

@@ -1081,3 +1081,16 @@ Android V3-08 单次综合验收已完成并关闭。Native `13.4.1-particle.2`
- Metal `bazel build -c opt --//:renderer=metal //:mbgl-core` 构建 PASS(642/642,875.118s,产物 `bazel-bin/libmbgl-core.a`)。
- Mac CMake/GTest 配置遇缺失 `libuv`,未安装依赖,正式 GTest NOT RUN。
- 修复 STEP-102 Android Validation 入口切换时残留 STEP-093/097 状态与轮询的问题。Android APK 编译、T1/T2 真机及 STEP-100/101 回归仍待手工构建与运行;STEP-102 维持“进行中”,尚非 CLOSED/PASS。
### 2026-10-09 STEP-107 iOS CoreOnly Style Peer 崩溃修复(Native BUILD PASS,iPad 待验收)
- 基于远端最新 `particle/main` 的 V22 后 HEAD `24105b1be7`,在唯一 Mac 主目录直接修复 Darwin `MLNStyle.layers` 对 CoreOnly 无 Peer Layer 的 nil 插入问题;补齐 KVC 数量/索引/区间、插入/删除与 aboveLayer ObjC 可见顺序语义,Native 粒子天气渲染层保持不变。
- 新增 `MLNStyleTests.mm` 混合 CoreOnly/普通 Layer 的集合及顺序回归用例;`git diff --check` PASS。Mac Bazel iOS Native `//platform/ios:MapLibre.dynamic` PASS,982.084 秒,ZIP 校验 PASS,SHA-256 `f16b7f2be761a101bb4b906b900175b95412abfeef09d715e2c9321b300a7b8a`。
- XCTest 尚未执行;iPad 唐津海图物体直接点击、多 Source、天气开关及粒子渲染尚未真实验收;不能宣称 Bug 已在设备上消失。未修改 V22 Tag、正式发行资料、NavSea、Android 或 Shared Core。
- Step:`docs/50_Step记录/STEP-107_iOS_StyleCoreOnlyPeer集合修复_v1.md`;报告:`docs/报告/STEP-107_iOS_StyleCoreOnlyPeer集合修复_RDC实施报告_v1.md`。
### 2026-10-09 STEP-108 V23 正式发行(构建/公网回读 PASS,Tag 收尾中)
- 构建源码 `87f3d1c59a52581fb8339f0ea7d106b9f4b94fef`。统一发行 Android/RN `13.6.1-particle.23`、iOS `6.29.0-particle.23`。Mac 四 ABI Android、iOS Metal opt、RN pack 成功,公网六项文件回读 PASS。
- GSRL Source Lock 记录六文件 sha256;V23 Android/iOS 实机及 STEP-107 iPad 崩溃复测仍 NOT_TESTED,不能写成设备 PASS。