Compare commits

...

53 Commits

Author SHA1 Message Date
tei
3c3dcb9d18 release: finalize unified particle.27
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-14 10:53:27 +09:00
tei
7f92f71416 release: record v27 GSRL source lock and receipt instructions
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-14 10:53:07 +09:00
tei
970e3ad606 release: prepare unified particle.27
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-14 10:16:33 +09:00
tei
a9cea8b581 fix: retain correct Metal composite vertex buffer slot
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-14 10:16:14 +09:00
tei
b0f34cc9a2 docs: record V26 GSRL final public receipt
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-13 07:39:31 +09:00
tei
e3cd9eba3c release: finalize unified particle.26
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-13 07:35:27 +09:00
tei
21c4450e01 release: record v26 GSRL source lock and receipt instructions
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-13 07:35:04 +09:00
tei
fce326a900 release: prepare unified particle.26
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-12 23:29:55 +09:00
tei
343ca2fa68 fix: secure Symbol Retained drawable lifetime and record investigations
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-12 23:27:06 +09:00
tei
badb515d59 docs: audit v25 GSRL release and remote verification
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-12 07:26:56 +09:00
tei
3774d0a841 release: finalize unified particle.25
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-12 07:25:01 +09:00
tei
4b2f3c8d38 release: record v25 GSRL source lock and receipt instructions 2026-10-12 07:24:23 +09:00
tei
f32f6f45fd build: fix v25 iOS device-only Bazel release inputs
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 22:28:58 +09:00
tei
d5841e6471 release: prepare unified particle.25
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 19:57:17 +09:00
tei
e3f5760527 test: record STEP-119 Android RCRR CPU validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 19:50:28 +09:00
tei
32ac795fc4 test: benchmark particle-only C FastPath CPU across R layouts
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 18:02:33 +09:00
tei
1072816740 test: add STEP-119 RCRR boundary variants and audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 17:42:26 +09:00
tei
46c88b0209 docs: record STEP-119 first observed Android PBF Fast
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 17:18:06 +09:00
tei
499f6fb2c2 docs: record STEP-119 real PBF Fast and missing-source control
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 17:07:35 +09:00
tei
3d989a00a8 feat: admit settled Symbol render results to retained cache
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 16:47:21 +09:00
tei
869930046b docs: record STEP-118 no-tide PBF Android Fast result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 14:08:49 +09:00
tei
3051196054 test: add isolated no-tide PBF retained cache scenario
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 13:57:53 +09:00
tei
fb8e78ac1a test: satisfy STEP-117 strict NDK C++ warning checks
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 13:06:11 +09:00
tei
696899e113 test: record STEP-117 PBF R cache admission blockers
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 13:03:47 +09:00
tei
43e5f5b915 docs: record STEP-116 Android PBF Fast Path device result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 12:19:33 +09:00
tei
c48a72a9d4 fix: guard STEP-113 empty annotation and expose PBF blockers
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 11:50:03 +09:00
tei
e2d202b560 test: prepare STEP-113 PBF Fast Path APK retest
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 10:34:08 +09:00
tei
21a1081643 feat: prepare STEP-114 guarded fast smoke validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 10:01:10 +09:00
tei
b02e54de3d docs: record STEP-113 full-frame source mutation validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 09:24:08 +09:00
tei
92efa36f4c feat: add STEP-113 Android PBF fast gate diagnostics
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 09:11:27 +09:00
tei
c6d13e1059 docs: record STEP-112 RDC Git verification
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 01:14:10 +09:00
tei
abc137ca96 docs: investigate STEP-112 particle full render and symbol cache
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 01:12:24 +09:00
tei
41b416c261 docs: add STEP-112 particle full-render cache investigation task
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 00:48:49 +09:00
tei
50ae48ad70 docs: record STEP-111 per-R placement feasibility partial
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-10 17:07:15 +09:00
tei
7ec9ac8c16 docs: record STEP-110 Git delivery evidence
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-10 16:18:29 +09:00
tei
cdb45c023b docs: investigate STEP-110 R0 placement attribution
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-10 16:17:16 +09:00
tei
2df504cdc0 docs: record STEP-109 V23 CPU investigation handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-10 12:15:10 +09:00
tei
2aae38f402 perf(particle): skip repeated field snapshot deep comparisons
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-10 12:14:06 +09:00
tei
5b8bdfbfbb docs: close STEP-108 GSRL v23 final release audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 23:55:51 +09:00
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
141 changed files with 11187 additions and 268 deletions

View File

@@ -675,6 +675,9 @@ MLN_CORE_SOURCE = [
})
MLN_CORE_HEADERS = [
"src/mln/renderer/symbol_render_inputs.hpp",
"src/mln/renderer/symbol_render_result.hpp",
"src/mln/renderer/symbol_shader_provenance.hpp",
"include/mln/particle/field/field_data_adapter.hpp",
"include/mln/particle/field/field_profile_contract.hpp",
"include/mln/particle/field/field_profile_validator.hpp",

View File

@@ -0,0 +1,116 @@
# R0 Placement 跨段归因与缓存安全边界调查 v1
**STEP-110,2026-10-10;状态:PARTIAL(只读调查,非修复验收)。**
**执行记录**:Codex CLI 0.161.0 实际会话模型 gpt-6-astra,推理 high,完成源码和历史证据审查;其报告写入阶段重复无响应,故本报告由 GPT 指挥 RDC 根据 Codex 调查结论独立回读关键源码后落盘。未运行测试、编译、ADB,也未修改业务实现。所有源码行号以调查起点 particle/main 2df504c 为准。
## 1. 发行源码核对
| 来源 | Git 提交 | 实际意义 |
|---|---|---|
| V23 GSRL Source Lock | 87f3d1c59a52581fb8339f0ea7d106b9f4b94fef | RN / Android Native 13.6.1-particle.23 构建源码 |
| V23 Tag 剥离后提交 | a0a4787b4379ab2819943d20ce05a7ae760a88c3 | 构建提交之后的发行资料提交;注释 Tag 对象本身是 3ce42184… |
| 当前 particle/main 和 origin/particle/main 调查起点 | 2df504cdc0403bdad85d3f31f16fd54a8744abac | 包含后续 STEP-109 尚未发行的 FieldSnapshot 比较优化 |
Codex 通过 Git Blob 对照确认:src/mln/renderer/render_orchestrator.cpp、renderer_impl.cpp、src/mln/text/placement.cpp、include/mln/renderer/render_segment.hpp、src/mln/renderer/retained_underlay_cache.cpp 和 particle_fast_path.hpp 在上述三个提交完全相同。此范围内 main 的分析适用于 V23;不能反推 STEP-109 优化已在 V23 APK 生效。
NavSea 输入冻结事实:Android 13 FZG1-2 V23,R0 [0,31) RETAINED 含地名 Symbol,C1 后续 82 层 CONTINUOUS。15.24 秒期间 placementChanged=true、R0 placementGatePassed=false、build/reuse=0、fallback/invalidation=453、fastPathHit Δ0、fastPathMiss Δ452,CPU 约 99%。**快照只能证实 Gate 未通过,不能证实 C1 引发 R0 Symbol 实际位置变化。**
## 2. Q1:Placement 产生、传播、状态语义
1. src/mln/renderer/renderer.cpp:53-65:先尝试 tryRenderParticleFastFrame;未命中即创建 RenderTree、prepare、完整 render,再缓存 prepared tree。
2. src/mln/renderer/render_orchestrator.cpp:737-749:逐个 RenderLayer.prepare,依据动态 needsPlacement() 构建 layersNeedPlacement。该集合与 R0 的结构性 placementCapable 不是一回事。
3. 同文件 :758-773:反向遍历整个 RenderTree 的需要 Placement 的图层,CrossTileSymbolIndex.addLayer 汇聚 Tile/Symbol 变化。该过程是整图范围,不按 Segment 独立产生。
4. 同文件 :775-810:在 Continuous 模式,placementChanged = !placementController.placementIsRecent(now, zoom, override)。为 true 才调用 Placement::create、placeLayers、setPlacement;否则 setPlacementStale。随后取全局 symbolFadeChange,调用 hasTransitions 控制 needsRepaint。它表示**本轮要重新 Placement**,不表示 R0 最终视觉结果必然不同;为 false 也不保证新旧 R0 内容相等。
5. 同文件 :811-823:非 Continuous 模式还有每次非空 Symbol Layers 就重算的特殊路径,不应混同天气播放运行态。
6. 同文件 :840-848:全局 Placement、layersNeedPlacement、symbolBucketsChanged 进入 RenderTree。Renderer 再独立判断各 Retained Segment。
src/mln/text/placement.cpp:45-59 CollisionGroups::get 在 crossSourceCollisions=false 时为不同 Source 创建隔离 collision group;开启时跨 Source Symbol 可以互相影响。**Source 不是 Segment**,同 Source 跨段仍存在相互影响,反之分段并不天然保证视觉隔离。
src/mln/text/placement.cpp:688-748 根据新旧 placed icon/text 更新 opacity 与 fadeStartTime;:1282、:1319 提供 symbolFadeChange / hasTransitions。shaders/symbol_sdf.vertex.glsl:153-158 和 shaders/symbol_text_and_icon.vertex.glsl:153-158 表明 alpha 依赖 symbol fade uniform。即使符号几何未变,透明度也可能变化。symbolFadeChange、placementTransitionActive 是全局时间/过渡量,不是 R0 独立验收结果。
Source layout/tile/visibility、Camera、Animation、Transition 均可能令 RenderTree prepare 或 Placement 状态变化;普通非 Symbol 粒子/颜色动画可以触发 repaint,但**不能单靠 repaint 来源判定 R0 新旧 placed 结果**。
## 3. Q2:R0 Cache Gate 的真实数据流
src/mln/renderer/renderer_impl.cpp:634-686 对每个 RETAINED Segment 分别取得:
- retainedRangeContentFingerprint / layerContentChanged;
- retainedSegmentSourceState 的 ready、stable、revision;
- 组合的 segmentContentRevision;
- retainedSegmentIsStable;
- retainedRangeHasPlacementCapableLayer 结构性 placementCapable;
- renderSegmentPlacementGatePassed 的结果。
**确切 Gate**:include/mln/renderer/render_segment.hpp:85-90 规定:无 Placement 能力的段直接放行 Placement Gate;否则必须同时满足 placementChanged=false、symbolFadeChange=0、placementTransitionActive=false。它读取的后三者是全局状态,不读取“事件由 C1 发起”或 R0 实际视觉等价证明。
src/mln/renderer/renderer_impl.cpp:661-686:Cache 支持还需要 Style/Sprite/global resources ready、Continuous 模式、没有 3D/debug、segmentStable、无 legacy/unsupported layer。Gate 失败可选择 static-placement 并传递给 cache.evaluate;:681-692 只有 Build/Reuse 才申请资源。:998-1028 先判断 global-live fallback,否则以 visitRenderSegmentExecution 顺序执行每段 composite/live,保持层序;actualExecution=live 不是证明 R0 Symbol 真变化,只表示本帧未从缓存 Composite。
src/mln/renderer/renderer_impl.cpp:590-630 还保留聚合的 retainedPlacementStable 和 Fast Path 支持判定,说明每段 Gate 局部化**不等于**整个 Fast Path 可局部化。
src/mln/renderer/retained_underlay_cache.cpp:85-105 将 camera/boundary、topology、content、tile、static-transition、static-placement 分别归类;:359-379 的 invalidate 更新 lastInvalidationReason,releaseResources 释放缓存,没有“每帧清零历史原因”的语义。因此早期快照当前 placementChanged=false 同时保留 lastInvalidationReason=static-placement **不是直接矛盾证据**;必须在同一帧比较 Gate、cacheSupported、decision、lastReason 的采样时序。
STEP-102/STEP-106 的作用边界是 **placement-free Retained 的局部复用**。本机原始 Android 保存日志 artifacts/maplibre-particle/step106-t2-fix/runtime-T2/stream.log 末尾 tick130:placementChanged=true、crossSourceCollisions=true,但两个 placementCapable=false 的 Retained 段显示 actual=composite、decision=reuse、build=1、reuse=3878;内部 Annotation Symbol 被放入后续 Continuous。T1 的 Symbol-bearing Retained 未解除保守回退。**历史验收未证明含地名 Symbol 的 R0 可复用。**
## 4. Q3:七种反事实场景
| 场景 | Placement 事件来源能否定位 | R0 视觉是否可独立证明不变 | Reuse 必要安全条件 | V23 结论 |
|---|---|---|---|---|
| 1 R0 地名静态,C1 只更新非 Symbol 粒子/颜色 | 可按 Layer/Repaint 来源条件性追踪,Placement Controller 仍是全局 | 不能凭 C1 非 Symbol 就证明 | R0 Tile/layout/content/Camera、全部 Symbol placed/opacity/fade 与缓存时等价,无其他全局影响 | 无充分逐段证明;fail-closed |
| 2 C1 Symbol 改动,不同 Source、crossSourceCollisions 关闭 | Source mutation 可区分 | 隔离碰撞降低风险,但仍不能独立证明 R0 Fade/Camera/其他 Source 状态不变 | 确认 Source 独立、Collision group 隔离、R0 自身配置、Tile、Fade 和层序均稳定 | 仅全局 Gate;fail-closed |
| 3 C1 Symbol 改动且跨源碰撞开启 | 可以部分追踪事件 Source | **不能**;跨 Source Symbol 可能排斥 R0 地名 | 比较新旧全局 Collision 后 R0 每个文本/图标位置、显隐、Opacity、层序 | 无独立证明;fail-closed |
| 4 R0 Symbol layout/tile/visibility/zoom 改动 | 局部 revision/fingerprint 可见 | 默认不能 | 重新 Placement / Build 缓存后验证 R0 版本和绘制结果 | 失效正确;fail-closed |
| 5 Camera pan/zoom/rotation/pitch 改动 | Camera/signature/matrix 可识别 | 旧屏幕纹理通常不等价 | 新 Camera、projection、viewport 对应重新构建缓存并核对碰撞 | Camera/Boundary Gate;fail-closed |
| 6 Fade/Placement Transition 活跃或刚结束 | Controller 可观察过渡 | 不能由几何稳定推出 opacity 稳定 | 所有 R0 Symbol opacity/fade、Transition 与缓存时等价,且状态稳定 | 只有全局 Fade/Transition Gate;fail-closed |
| 7 C1 自动插入 Annotation Symbol | 验证场景的 Annotation layer 可追踪;NavSea 实际归段需运行证据 | 不能:跨段 Annotation 可能参与全局 Collision | 明确真实层序、Source/collision 关系,证明 R0 新旧 placed/fade 等价 | STEP-106 只覆盖 placement-free 段;fail-closed |
重点:不同 Segment **不是** Collision 独立证据。仅识别“谁引起 Placement”并不足以让 R0 安全 Reuse;必须满足实际视觉、相机、图层顺序以及已缓存纹理的等价性。
## 5. Q4:Fast Path 的其他独立必要 Gate
Renderer 的 Fast Path 在完整 RenderTree 之前尝试。src/mln/renderer/renderer_impl.cpp:590-630 的 Retained eligibility/单 C 支持是前置条件;src/mln/renderer/particle_fast_path.hpp 的 Fast Path 判定还检查 boundary、topology、staticStable、hasPlacementTransition、pending changes、nonparticle transition/repaint、fading tiles、是否 Particle repaint、still image、continuous mode、debug、unsupported continuous layer、supported scene、annotation mutation 等。
因此**R0 cache 有效是相关 Fast Path 的必要条件之一,绝不是充分条件**。当前 cache-invalid 说明首先在此受阻,不允许宣称修好 R0 就一定 hit,也不能在本 STEP 扩大调查到 C1 82 层所有性能开销或实施多 Continuous 优化。
## 6. 数据流 / 状态流
Host 更新 / Style / Source / Camera / 动画
→ Renderer::update 尝试 Fast Path(未命中时)
→ RenderOrchestrator.createRenderTree
→ 全局 layer.prepare / needsPlacement / CrossTileIndex / PlacementController
→ 全局 placementChanged / symbolFadeChange / hasTransitions
→ 对各 Retained 段独立:Source ready/stable/revision + Fingerprint + structural placementCapable
→ placement-free 段绕开全局 Placement Gate;Symbol-bearing 段使用全局三项严格 Gate
→ Style/Resource/Camera/Revision 安全校验
→ Cache Build / Reuse / Fallback
→ 保序 RenderSegment Composite / Live
→ Fast Path 另有单 C、pending、unsupported continuous 等 Gate。
## 7. 确定事实、假设、证据不足
| 确定事实 | 不能直接断言的假设 | 需要补的证据 |
|---|---|---|
| V23 核心 Placement/Cache 逻辑与当前 main 等价 | C1 变化直接导致 R0 视觉改变 | NavSea 同一帧事件来源、各 Source mutation、R0 全 Symbol 新旧结果 |
| placementChanged 是全局计算触发量 | placementChanged=true 意味 R0 所有地名改变 | per-R0 placed/opacity/fade 结果签名 |
| 含 Symbol 的 R0 使用 global conservative Gate | R0 453 次 fallback 是已证的性能 Bug | 既有语义被违反的状态不变量;当前并无证据 |
| T2 实验 placement-free 段重复复用 | 现有 T2 足以支持含 Symbol R0 | NavSea 实际 Source/Annotation/跨源碰撞和真实设备对照 |
| STEP-109 main 更改没有改 Gate | V23 已有 STEP-109 修复 | 需下一正式发行 GSRL 和设备验收(本 Step 禁止) |
## 8. 必选结论矩阵
| 维度 | 明确判定 |
|---|---|
| Placement 事件来源能否定位 | **条件性可区分**(Source/Layer 可追踪,global Placement 本身不标 per-Segment 责任) |
| 能否证明 R0 视觉最终未变化 | **需扩展内部安全证据** |
| 现有 Gate 性质 | **正确的设计保守限制**,不是已证明违反 STEP-102 语义的 Bug |
| 缓存失效主要责任 | **多因素**:Native Placement 缺少 per-R0 结果证据 + Native 全局 Gate,受 Collision 语义和 Host Style/Annotation 组合影响 |
| 下一步 | **Native 内部诊断补强 → 安全判据设计 → 另行授权真机对照**;若改公共接口/Host,则交 Integration 协商 |
## 9. 依赖顺序与最小后续验证
1. 在不影响生产安全和公开 API 的前提下,记录 placement 重算触发原因与时间,拆分 Source/Layer/Camera/Tile/transition 事件;
2. 设计 per-R0 Placement **结果等价安全证据**,至少包含 R0 Symbol placed IDs、位置、可见性、text/icon opacity/fade、Collision group、Tile/glyph/source 版本、Camera 和合成顺序;先审查其不变量;
3. 只有确证 R0 不受 C1 影响时,才另立有边界的 Native Gate 优化任务;不能先去掉 placementChanged;
4. 再调查 Fast Path 单 C / unsupported / pending 等 Gate;进行等条件 V23 与后续版本 CPU/Frame/Visual A-B;
5. 如需要 NavSea 配合改 Style/Annotation 或改公共 Contract/API,形成 Integration 阻塞请求,不在 STEP-110 擅自实施。
**调查状态:PARTIAL。** 已完成静态因果审计、源码版本对照和 STEP-106 历史证据核对;缺少生产 R0 per-Symbol 等价、事件逐帧对应和 CPU 因果实测。这是安全证据边界,不是“R0 已修好”“缓存已命中”“CPU 已下降”。

View File

@@ -0,0 +1,56 @@
# STEP-111:R 段 Placement 结果等价与独立刷新可行性调查 v1
**日期:2026-10-10|状态:PARTIAL(初步可行性判断,不是 Codex 完整设计或运行验收)**
## 1. 范围及真实证据
- Mac `/Users/tei/dev/MapLibreParticleLab`,`particle/main`;调查时 HEAD `7ec9ac8c1635c7a3b607abc2ed4be4fd764e7b6d`。Integration `main` 与 `origin/main` 一致于 `80667d6c343411d03a760425438e3d292f452118`。
- 使用可见 GNU Screen `7445.codex`,真实界面显示 **GPT-6 Astra high**;Codex 进行两轮源码浏览/调查后长期无新事件,第二轮缩窄至直接写报告仍无输出,随后终止会话;未生成任何要求的 STEP-111 正式调查文档。此文件由 GPT/RDC 对当下代码和既有 STEP-110 证据重新核对后撰写,**不冒称 Codex 已完成 STEP-111**。
- 未改 C++/Swift/Kotlin/JS/测试代码,未编译、未测试、未跑设备;本文件是方案评估,不是新增 Native 功能。
## 2. 现有实现的确定事实(RDC 逐行核验)
1. `src/mln/renderer/render_orchestrator.cpp:775-810`:Continuous 渲染使用 `placementController.placementIsRecent` 得到全局 `placementChanged`,随后视情况新建 Placement、调用 `placeLayers(layersNeedPlacement)` 并更新 Controller;`symbolFadeChange` 和过渡也作为全局状态处理。这里的 `placementChanged` 表示重算触发,**不证明每一个 R 的最终输出改变**。
2. `include/mln/renderer/render_segment.hpp:78-90`:R 中只要出现 `layerType == "symbol"` 就被标为 placementCapable;Gate 对这种 R 要求全局 `!placementChanged && symbolFadeChange==0 && !placementTransitionActive`。没有 per-R 最终 Symbol 结果比较。
3. `src/mln/renderer/renderer_impl.cpp:638-686`:对每个 RETAINED 段已经独立计算内容 fingerprint、Source ready/stable/revision、segmentStable、placementCapable,并在 `cache->evaluate` 决定复用、构建或 fallback;`static-placement` 是 Gate 未通过时的失败原因。逐 R Cache 管理已存在,**缺的是含 Symbol 段的逐段结果等价证据**。
4. STEP-110 已核对的 `src/mln/text/placement.cpp` CollisionGroups/opacity/fade 说明:Collision Domain 与 RenderSegment 边界不等价;跨 Source 碰撞开关、同 Source 跨段、text/icon Fade 等可能让别的段影响本段可见结果。STEP-102/106 仅能证明 placement-free 的安全复用,不直接证明含 Symbol R 的安全复用。
## 3. 推荐的目标语义(待设计与实测)
`R0-C0-R1-C1-R2-C2` 六段按图层顺序合成;若 R1 内 Symbol 的**最终绘制结果**与其上次成功 Build 的缓存版本等价,允许 R1 复用,而不因某个 R0/R2/C 段触发 global placementChanged 自动 Fallback。跨段碰撞真的改变 R1 Symbol 的显隐、锚点、位置、opacity 或其它绘制依赖时,R1 必须失效。
**不等于**每个 R 拥有独立 Collision Index。第一阶段可以仍全图计算 Placement,之后对不同 R 的已完成结果分别判等,再控制各段纹理的 Build/Reuse;这只争取避免不必要的段重绘,不能自动省略全局 prepare/Placement、非 R 的 C 绘制或保证 Fast Path hit。
### 待验证的候选签名(不是已实现结构)
建议研究在 `placeLayers` 完毕之后、`renderer_impl.cpp:638-686` Cache 决定之前提供 `per-R PlacementResultRevision` 或严格结果比较,关联已缓存 Build 版本,而不只与上一帧比较。候选依赖包括:
- 按层序可稳定归属到 R 的 text/icon 实例、Tile/Bucket/Source 世代与 placement/collision 可见性、锚点、偏移、实际绘制位置;
- Fade/Opacity/Transition 的可绘制值或明确的“稳定可复用”条件;纹理/glyph/sprite/atlas 的有效世代;
- Camera/projection/zoom/viewport/pixelRatio、Style 顺序与合成边界;这些可优先复用已有 Camera/Content/Resource Gate,不应盲目重复扫描;
- 新旧结果缺失、ID 不稳定、资源生命周期不可证明、哈希碰撞或任意未知场景均 **fail-closed**。
上述字段只是**候选调查清单**,现有代码尚未证明可以低开销取得稳定、完整的 per-R 视觉语义。简单“Symbol ID/位置没变”或单一 64-bit hash 相等都不足以证明最终像素安全等价。
## 4. RCRCRC 反事实场景
| 情况 | 希望达到的复用决策 | 当前证据边界 |
|---|---|---|
| C1 只有粒子/颜色更新,R0/R1/R2 视觉稳定 | R0/R1/R2 独立复用 | 当前 global Gate 可能挡住含 Symbol R;仍须取得最终结果不变证据 |
| C1 Symbol 改变,仅使 R2 发生碰撞结果变化 | R2 重建;R0/R1 如证实未变可复用 | 事件来源不能替代逐 R 结果比较 |
| C1 Symbol 改变,同时影响 R0 与 R1 | R0/R1 失效、不得复用旧遮挡 | 必须保留全局 Collision 正确性 |
| R1 自身 Layer/Tile/Style 更改,R0/R2 不变 | R1 失效;其余分别判等 | 现有内容 revision 可局部区分,但 Symbol 仍有全局 Gate |
| Camera/Zoom/Pitch、Annotation、Fade、Glyph 变化 | 受影响 R 失效;未知则全体安全回退 | 不得将各段等同独立碰撞域;须验证跨平台一致性 |
## 5. 难度分级(GPT/RDC 初估,尚未经 Codex 最终复核)
| 阶段 | 复杂度 1–5 | 输出与主要难点 |
|---|---:|---|
| A:只读设计 + 逐 R trace/reason | 2–3 | 诊断 event 来源与 per-R Cache 状态,不改变业务 |
| B:Native per-R result fingerprint 的 shadow compare | 3 | 找齐最终 Placement 数据、稳定身份/世代、控制逐帧遍历成本;**只记录,不参与复用** |
| C:用已验证等价结果替换含 Symbol R 的 global-only Gate | 4 | 与 Cache 生命周期/资源/层序/Fade 联动,必须零 false-reuse;Android OpenGL、iOS Metal 对照 |
| D:真正把全图 Placement/Collision 改成分段增量计算 | 5 | 全局碰撞依赖与跨 Tile/Source 的更新传播,风险明显更高、不能简单按 R 切分 |
工程周期目前**无法从静态代码得到可信承诺**。复杂度 3 表示可做有限验证原型;4 代表需多阶段视觉/性能门槛;5 代表核心架构重构,不能与缓存命中优化混为一谈。
## 6. 后续建议及未完成项
1. 下一次正式任务先补只读源码数据所有权审计:Placement 完成后在哪个对象/时间点能列出每 R 所属 symbol 的最终 placed/opacity,并确定 crossTileID、tile/glyph 资源失效契约。输出字段级“已有/缺失/必须新增”清单。
2. 再授权仅做 **shadow-mode** 内部诊断:per-R `resultComparable/resultChanged/unknown` 与 global Gate 同步采样,**不改变 Gate**;采样成本与候选数量需量化,防止 O(N) 比较本身抵消收益。
3. 真实 RCRCRC(每个 R 含 Symbol)覆盖同 Source/跨 Source 碰撞开关、Annotation、Fade、Camera、Tile、Layer 顺序及前后多帧;全缓存 ON/OFF 视觉一致,**false reuse 必须为零**。
4. 通过后才另行授权 C 阶段优化,先看独立 R build/reuse/fallback 与 CPU/帧变化;Fast Path 对单 C、pending、non-particle repaint 等另有门槛,不作为此优化的当然收益。
5. 若出现跨平台公共语义/公共接口改变,回 Integration RFC/ADR 处理;本阶段不得更改现有 API/Contract。
**未完成:** Codex 未形成字段级可行性结论;无 per-R Placement 结果签名、运行对照、性能证据或真实 Native 修复。不可标“调查 PASS”或“独立刷新已实现”。

View File

@@ -0,0 +1,426 @@
# STEP-112 V23 粒子 Full Render 循环与 Symbol 缓存准入全面源码调查 v1
日期:2026-10-11(JST)。状态:本轮只读调查与文档交付完成;综合判定 **PARTIAL(现场逐帧归因及安全修复仍未验证)**。核心结论、证据等级和剩余事项见第11章。
## 1. 调查基线与证据规则
- 当前机器:Mac.lan,Darwin 24.6.0,x86_64;仓库 `/Users/tei/dev/MapLibreParticleLab`。
- 分支 `particle/main`,起点 HEAD `41b416c261fa72bd21171328045036a124694f47`。
- 实际模型 `gpt-6-astra`、推理 `high`:当前会话 `01a12686-49ee-70b1-953e-f95f8a4bd9ca` 的本地 rollout `turn_context.model/effort` 已核对。任务书建议模型由用户本轮明确要求覆盖。
- 起点只有 STEP-110、STEP-111 两份任务书未跟踪,无 tracked diff;不改动它们。子模块状态均为空格前缀,无未初始化/偏移标记。
- Integration `main=80667d6c` 使用用户提供的 RDC 核验事实,不重新同步或修改跨项目文件。
- 已按顺序读取 AGENTS、PARTICLE_BASELINE、8 份项目规范、总则 README 与四份现行总则、STEP-112、STEP-102/106/109/110/111 的相关任务/记录/报告。初始化基线及现行路线 v2 中旧版本坐标不冒充当前 V23;后续按 Git 实际源码比对。
- 证据分为 **源码证明**(指定基线静态控制流)、**FZG1 实测**(本任务引用历史现场输入,非本轮重测)、**条件推断**(前提明确的推演)、**仍需设备验证**(频率、真实配置、像素/性能)。
- 只允许写本主报告、STEP-112 阶段记录、完成报告、开发记录追加。禁止源码/测试/Style/脚本/跨项目写入,禁止编译测试、设备操作、Git 提交推送及分支/worktree 变更。
## 2. 已核实的入口事实
**源码证明**:`src/mln/renderer/renderer.cpp:45-64` 先同步 retained 状态,再尝试 Fast;失败才清除旧 prepared tree、`createRenderTree`、`prepare`、Full render、保存新 prepared tree。Fast 失败与粒子请求下一帧属于不同决策。
**源码证明**:`src/mln/renderer/particle_fast_path.hpp:99-126` 顺序短路。`cache-invalid` 在第 3 项返回,后续 revision、camera、staticStable、placement、pending、非粒子 repaint、C 层支持、scene 等条件没有因此得到通过证明。
## 3. 核心发现:Symbol R 首次 Build 的控制流矛盾
**源码证明(正常 Continuous Full 路径)**:含结构性 Symbol 的 R 没有通过当前逐段 Placement Gate 的窗口。这比“Fade 的 `==0` 可能太严格”更强,且不依赖 Source 变化或实际 Symbol 是否移动。
1. `src/mln/renderer/render_orchestrator.cpp:792-807`:`placementChanged=true` 时执行 Placement;否则无条件 `setPlacementStale()`。
2. `src/mln/text/placement.cpp:160-175`:旧 Placement 禁用 transitions 时 `placementIsRecent()` 必为 false,故必进入 changed=true 分支;若进入 changed=false 分支,旧 Placement 必启用 transitions,随后 stale=true,使 Controller `hasTransitions()` 必为 true。
3. `src/mln/renderer/renderer_impl.cpp:593-594,655-659` 读取同一 Controller 的 transition;`include/mln/renderer/render_segment.hpp:85-90` 要求 `!changed && fade==0 && !transition`。
4. 因而 changed=true 被第一项挡住;changed=false 被 stale→transition=true 挡住。对结构性 Symbol R,两个分支均 false。`RenderTreeImpl::prepare`(`render_orchestrator.cpp:133-138`)仅更新 buckets,不清 Controller stale;没有中间解除路径。
此结论限定正常单线程 Renderer 调用链,不将任意人工注入/未来改动当作当前可达状态。隐藏/空 Symbol 仍按结构归为 capable(`render_segment.hpp:75-90`),所以也不能绕开这层保护。
**源码证明**:原始 `Placement::symbolFadeChange` 是相对 commitTime 的归一化时间增量加 zoomAdjustment,而非“本帧是否改变”布尔量(`placement.cpp:1282-1289`);transitions 关闭或 duration≤0 返回 **1**。正常 Full 的 `prepare()` 不改写它。只有已通过 Fast 的 `prepareParticleFastFrame()` 才把 RenderTree 字段设为 0(`render_tree.hpp:105-109`),不能反过来帮助首次 Build。
**FZG1 实测(任务书输入)**:末态 true/0/false 与“transitions 开启、此次重新计算、静止 zoom、先前实际 Fade 已结束”的状态相容;与通常 transitions 关闭后的 true/1/false 不同。不能仅凭此末态推断每帧走默认 300ms 周期或 Host 确实关闭 transitions。
**条件推断**:在 retained enabled、正常 Continuous Full、R2 含 Symbol 的前提下,逐帧 `segmentSupported=false` 可令 Build 始终为 0;`retained_underlay_cache.cpp:195-205,359-366` 即使此前没有有效纹理也每次记录 fallback/invalidation。故 1787 次 invalidation 不表示曾成功建成 1787 份纹理。
以下第4—10章已补齐调度、全部 Gate、状态机、时间表、反事实和提案;运行时无新增证明,不能据此声称修复 PASS。
## 4. 版本核验与 A:调度、Full、GPU 和 Idle
### 4.1 当前 main 与发行 V23
**源码证明**:执行只读 `git diff --name-only 87f3d1c59a52581fb8339f0ea7d106b9f4b94fef HEAD -- <相关路径>`,在 renderer/text/map、RasterArraySource、render_segment、TransitionOptions、Android WeatherRuntime/frontend、iOS FieldRuntime、GL/Metal shader 范围内,仅两份文件不同:`render_particle_validation_layer.cpp` 和 `render_weather_color_layer.cpp`。逐 hunk 核对为 STEP-109 的 immutable snapshot 身份快比较,未改变本调查的调度、Placement、Cache、Fast Gate。以下行号一律对应本轮 HEAD,不照搬 V23 两份 Layer 的旧行号。
V23 Source Lock 为 `87f3d1c…`(既有 STEP-110/108 发行证据),Android/RN `13.6.1-particle.23`、iOS `6.29.0-particle.23`。当前 main 包含尚无实机性能验收的 STEP-109;不得称它已在 V23 APK 生效。本轮没有重新下载制品或核对设备内 binary。
### 4.2 可核查调用图
下图箭头“条件”都不是每次必然发生。路径简称只用于图;实际位置见后表。
```mermaid
flowchart TD
H[Host 数据/Style/Camera 更新] --> U[Map::Impl::onUpdate]
W[WeatherRuntime 定时唤醒:条件成立] --> T[Map::triggerRepaint]
T --> U
U --> F[RendererFrontend update / 平台 requestRender]
F --> R[Renderer::render]
R --> G[tryRenderParticleFastFrame:24 项 Gate]
G -->|通过| Q[复用 prepared tree / 更新粒子 / GPU render]
G -->|失败| C[createRenderTree / Layer prepare / 全局 Placement 判定]
C --> P[RenderTree prepare / bucket opacity 与动态属性]
P --> B[各 R evaluate:Build / Reuse / Fallback]
B --> D[离屏 Build 或保序 composite/live / GPU 粒子]
D --> O[onDidFinishRenderingFrame]
Q --> O
O -->|needsRepaint 或 camera transition| U
O -->|loaded 且无需 repaint/transition| IDLE[onDidBecomeIdle]
```
| 环节及证据强度 | 文件、函数、行号与条件 |
|---|---|
| **源码证明**:粒子是否活动 | `src/mln/renderer/layers/render_particle_validation_layer.cpp:1112-1127`,`isParticleAnimationActive` 检查 active、正式 Source 的 T0/T1、visibility;`needsRepaint` 再检查 V4 cadence 或旧 timeline。active=false、无双端点、visibility=None 不请求粒子 repaint。它不含“必须 Full”标志。单帧输入可投影成可用双端点,不能以业务仅一帧推断无数据。|
| **源码证明**:粒子暂停和预报暂停不同 | 同上;`RasterArraySource::tickForecastClock`,`src/mln/style/sources/raster_array_source.cpp:487-510` 在 forecast 非 Playing 时不推进预报;这不把粒子 active 设 false。风场时间冻结仍可流动。无向量或语义不匹配另由 Layer `update:1247-1258` 拒绝 GPU 更新。|
| **源码证明**:Core cadence | `raster_array_source.cpp:149-175,208-227,260-281,1237-1255`;非 AUTO 以 `1/fps` 检查,30 对应约 33.33ms;consume 才推进 cadence revision。AUTO 每次可 due,不等于固定 30。|
| **源码证明**:Android SDK 唤醒 | `platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/WeatherRuntime.java:228-234,250-269`;有效可见粒子且非 AUTO,或 V4 forecast Playing 且已提交字段时,Handler 每次安排 34ms(60 为 17ms),调用 `map.triggerRepaint()`;disabled/disposed 停止,隐藏粒子但 forecast Playing 仍可唤醒。这里是 SDK Java 调度,不是 NavSea JS 每粒子循环。|
| **源码证明**:iOS 对应唤醒 | `platform/ios/src/MLNParticleFieldRuntime.mm:173-193`;一次性 NSTimer 0.034/0.017s,触发 `triggerRepaint` 后重新调度。两端意图相同,调度抖动不保证相同。|
| **源码证明**:进入地图更新 | `src/mln/map/map.cpp:93-95` 调 `Impl::onUpdate`;`src/mln/map/map_impl.cpp:108-153` tick forecast、更新 camera transition、装配 UpdateParameters、调用 frontend.update。非 Continuous 且无 still request 直接返回。|
| **源码证明**:Android 跨线程链 | `android_renderer_frontend.cpp:118-133,149-152`(路径前缀 `platform/android/MapLibreAndroid/src/cpp/`)AsyncTask 保存最新 params,再 `MapRenderer.update/requestRender`;`map_renderer.cpp:127-149,153-157,221-247` 请求 Java 渲染、后端 scope 中调用 `Renderer::render`。多个 update 可能合并为一次实际 render。|
| **源码证明**:iOS frontend | `platform/darwin/src/MLNRendererFrontend.h:28-54` 保存 params、setNeedsRerender,backend scope 中 render;不同于强制 Full 命令。|
| **源码证明**:Fast/Full 分岔 | `src/mln/renderer/renderer.cpp:45-64`;Fast 成功直接 return;否则 create/prepare/render/commit tree。`renderer_impl.cpp:324-327` 只在 `!particleFastFrame` 增加 Full CPU 计数。`FullRendererFrame` 在 :323 两种路径都会计数,名称不能混用。|
| **源码证明**:Full repaint 汇总 | `render_orchestrator.cpp:775-823,1107-1128`;light、Layer transition/repaint、Controller transition/stale、fading tiles 的 OR;Continuous 时赋 `RenderTreeParameters.needsRepaint`。|
| **源码证明**:通知与 Idle | `renderer_impl.cpp:1070-1074` → Android forwarding `android_renderer_frontend.cpp:38-44` → `map_impl.cpp:259-288`;needsRepaint 或 transform transition 时 onUpdate,否则 loaded 时通知 Idle。Observer 的 RenderMode::Full 表示 loaded,不表示执行 Full CPU 路径。|
| **源码证明**:GPU 推进 | Layer `update:1271-1283,1994-2055`;未到 cadence 可只重组 composite,不推进 state/trail;到期才准备 State/Trail pass。`renderer_impl.cpp:807-810,968-980,1047-1053` 执行 render targets 和 present。一次 repaint、一次 Full CPU、一次 simulation advance、一次成功呈现不是同一计数。|
**仍需设备验证**:FZG1 约 30FPS 的真正限速因素是 targetFps=30、平台帧率限制、耗时瓶颈或组合,任务快照不能区分。源码支持“Core 33.33ms 阈值 + SDK 34ms 唤醒”的候选机制,不能把 34ms 直接换算为实测 1792 次 Full 的唯一原因。
**源码证明**:所追踪的 PlacementController 仅在 render 链上比较时间,并无独立定时器调用强制 Full;stale/尚有 Fade 会通过 needsRepaint 延续正常地图帧。没有外部输入且粒子关、其它 transition 完成、资源 loaded 时可 Idle;WeatherRuntime 后续 wake 或 Tile 完成仍能再次唤醒,Idle 不是永久无任务保证。
### 4.3 Full 与 Fast 工作比较
| 工作 | Full | Fast | 证据 |
|---|---|---|---|
| diff Style/Source、prepare RenderLayer、CrossTile/Placement、新 RenderTree | 执行相应流程;不等于每帧都 placeLayers | 跳过,复用旧 tree | `renderer.cpp:53-61`;`render_orchestrator.cpp:445-539,737-848` |
| Source/bucket/atlas upload | 遍历 | 跳过整批 | `renderer_impl.cpp:470-487` |
| Layer update | 全部 render items | 仅 isParticleAnimationLayer | `renderer_impl.cpp:491-509`;`render_orchestrator.cpp:1458-1483` |
| tweaker/upload/postRender | 全范围 | 单 C 范围;render targets 仍处理 | `renderer_impl.cpp:707-749,1031-1040` |
| R GPU | 可 Build、Reuse composite 或 Live | 正常命中应复用;仍进入 cache evaluate/render | `renderer_impl.cpp:637-692,813-862,972-1028` |
| C GPU、State/Trail、最终 framebuffer/present | 执行 | 仍执行 | 同上及 :1047-1053 |
Fast 不是零 CPU 或零 GPU;Full 不是所有 R 从零重画。PREFIX 中 R0/R3 已复用而 Full CPU 仍增长,正与此结构相符。
## 5. B:Fast 全 Gate 矩阵
下面按 **实际首错顺序**列出全部 24 项。简称:`F=src/mln/renderer/particle_fast_path.hpp`;`I=src/mln/renderer/renderer_impl.cpp`;`O=src/mln/renderer/render_orchestrator.cpp`。表中“Tick”特指静态场、仅粒子 State/Trail 时间推进,不包括 forecast 播放导致新 Source impl;“Full恢复”是假定其它门槛已满足的一次新 Full 能否建立相应证据;FZG1 列区分输入实测与静态推断。
| # / 条件与失败名(F 行) | 输入来源/通过条件 | Tick 本身改变? | 一次新 Full 后可能恢复? | FZG1 证据 |
|---|---|---|---|---|
| 1 prepared tree / no-prepared-tree (100) | I:247,290-302,tree 非空;reload/miss 清旧 :305-316 | 否 | 是,create 成功后保存 | **FZG1实测** cache-invalid 的那些判定已过此项 |
| 2 retained enabled / retained-disabled (101) | I:207-208,248,runtime snapshot.enabled | 否 | 需控制面启用,Full 不自动开启 | **FZG1实测** 同上已通过 |
| 3 所有 R valid / cache-invalid (102) | I:220-228,249,cache 容器与 plan 大小相同,每个 R cache 非空且 isValid | Tick 不改内容;Full Placement gate 可间接 invalidate | 无 Symbol 可建;当前含 Symbol 的 R 不可(第3章) | **FZG1实测** 首拒;R2 Build/Reuse=0 |
| 4 Build/Static revision / revision-mismatch (103) | I:220-230,250,每 R buildRevision==observedStaticRevision,prepared aggregate static revision==tracker.current | 否;Full 稳定状态变化可推进 tracker | 通常可以,但缓存未建仍失败 | **未知**,被 #3 掩盖 |
| 5 Content revision / content-mutation (104) | I:236-237,251,prepared 与 O 的 aggregate content revision 相等 | 固定场否;forecast/source 变化可改 | 是,Full 处理后 commit 新值,异步新变更再失效 | **未知**;有历史 Source Mutation 增量,无逐帧对应 |
| 6 Viewport / viewport-changed (105) | I:214-216,253,Transform size 与 prepared 相等且 backend renderable size 未变 | 否 | resize 后重建可 | **未知**,静止场景不等于逐帧已核验 |
| 7 Camera / camera-changed (106) | I:217,252,精确投影矩阵相等 | 否 | 新相机完成 Full 后可,持续交互继续失效 | **未知** |
| 8 non-camera signature / update-parameters-changed (107) | I:151-196,218-219,254,完整签名相等,字段见下段 | timePoint 不入签名;forecast 可能更换 sources | 是,新 params 被 prepare/commit;源持续变则持续 miss | **未知** |
| 9 Style loaded / style-not-loaded (108) | I:255,UpdateParameters.styleLoaded | 否 | 需真实加载完成 | **未知**(不能以段 ready 代替所有) |
| 10 Boundary/RangeConfig / boundary-changed (109) | I:209-234,256,唯一 C 的 begin/end、configRevision 与 prepared 相等 | 否 | plan 有效时可;多 C 另被 #22 拦截 | **未知**,已知 PREFIX 数字来自现场 |
| 11 Topology / topology-changed (110) | I:235,257,prepared topologyRevision==tracker.current;tracker I:542-563、cache.cpp:62-69 | 否 | Full 新拓扑和 cache 可重建 | **未知** |
| 12 staticStable / static-unstable (111) | I:238-244,258;O:1156-1170,所有 R 稳定、无 light、R 动态 needsPlacement 时 Controller 无 transition、R 无 transition/repaint、**任一 Source** 无 fading tiles | 粒子不直接改 R;时间和 stale 可阻塞 | 取决于 Controller/资源,当前 Symbol 周期不保障恢复 | **条件推断** 部分帧会失败,实际序列未知 |
| 13 Placement transition / placement-or-fade (112) | I:259-263;O:1281 → PlacementController::hasTransitions(now) 为 false;不读旧 tree 的 changed/fade 代替 | 仅时间推进可结束真实 Fade,但 stale 必须新 placement 清 | 稳定重算帧可清 stale;不重算会置 stale | **FZG1实测** 末态 false;不是所有 Fast 时刻都 false |
| 14 Pending / pending-changes (113) | I:264;O:1285,pendingChanges.empty;O:1341-1344 入队、1486-1490 消费 | 正常粒子 changes 于 render 中生成/消费;非必然积压 | 通常可以,异步继续到来则不保证 | **未知** |
| 15 non-particle transition / non-particle-transition (114) | I:265;O:1270-1273,无 light 或任意 Layer.hasTransition | 否;普通 Style transition 可活跃 | 等 transition 真结束再 Full | **未知** |
| 16 non-particle repaint / non-particle-repaint (115) | I:266;O:1275-1278,非粒子 Layer 均不需 repaint | 固定 V4 场通常否;V3 WeatherColor 播放可 true(Layer :177-180) | 活跃动画不会因 Full 自行结束 | **未知** |
| 17 fading tiles / fading-tiles (116) | I:267;O:1282-1284,所有 Source 无 fading tiles | 否 | fade 真结束且状态消费后可 | **未知**;与 #12 重复但诊断次序不同 |
| 18 ParticleNeedsRepaint / no-particle-repaint (117) | I:268;O:1274,至少一个 renderable 且 isParticleAnimationActive;**这里不是 cadenceDue** | active 粒子满足;每次 advance 不取消 active | 数据就绪/显示后可;粒子关不能通过 | **条件推断** 风场活动大概率满足,未逐帧取证 |
| 19 still image / still-image-request (118) | I:269,stillImageRequest=false | 否 | 请求消耗后可 | **未知** |
| 20 MapMode / unsupported-mode (119) | I:270,Continuous | 否 | Static/Tile 不自动转换 | **条件推断** 现场连续地图符合,仍需记录 |
| 21 Debug / debug-options (120) | I:271,NoDebug | 否 | 用户关闭后可 | **未知** |
| 22 C 支持 / unsupported-continuous-layer (121-123) | I:272,624-627;O:1241-1250,有且仅一个 C、每层 startsRetainedDynamicRange、至少一个 particle | 否 | 固定含普通 Symbol/Line 的 C,多少次 Full 都不恢复 | **源码证明+现场布局前提** PREFIX C[13,33) 必失败 |
| 23 scene / unsupported-scene (124) | I:273,619-630,preparedSceneSupported=true 且唯一 C;其内部含 loaded、无3D/debug、所有 R stable、aggregate Placement stable、无 legacy/custom、C 支持 | 否;Full 会重算并保存 false | 仅修 R2 局部 Gate 不够;需一致安全判据 | **条件推断** R 有活动 Symbol 或 C 普通层使其 false |
| 24 Annotation / annotation-mutation (125) | I:202-205,274,AnnotationManager 无 pending changes(weak guard) | 否 | 正常更新消费后可;新 annotation 继续阻塞 | **未知**;不能把末尾 Symbol 存在等同 pending=true |
#8 的逐字段完整范围(I:154-180,186-196):pixelRatio、mode、debugOptions、styleLoaded、spriteLoaded、transformChanging、gestureInProgress;light/images/sources/layers/fontFaces/fileSource 对象身份;glyphURL;transition duration/delay/enablePlacementTransitions;prefetchZoomDelta、crossSourceCollisions、fastPFOREnabled;tileLodMinRadius/Scale/PitchThreshold/ZoomShift/Mode。viewport、camera 矩阵独立比较;timePoint 不在签名。**对象身份改变可触发 Full,即使负载内容相同;反过来单纯 triggerRepaint 且签名相同没有“必须 Full”语义。**
所有输入在 I:246-275 装配,但 F:100-125 返回首错;现有记录 I:276-280 只有 candidate/reason。#3 的失败不能证明 #4—24 成功。修完 R2 仍会遇到 PREFIX 的 #22、prepared scene #23,以及真实资源/Source/Annotation 等独立门槛。
### 5.1 第二重全局锁
**源码证明**:Full 的 `retainedPlacementStable`(I:590-592)依据动态 `retainedRangeUsesPlacement`(O:1135-1141,即 needsPlacement),而逐 R gate 依据结构性 Symbol。活动 Symbol R 存在时:重算帧 aggregate placementStable=false;不重算帧 Controller stale=true,aggregate staticStable=false(O:1158)。二者分别让 `retainedSupported`(I:619-623)为 false,写入 `particlePreparedSceneSupported`(I:630)。这是独立于逐段 cache 的 **Fast scene 锁**,不是 main-buffer 的 global-live 判定;后者 I:998-1028 已不含 aggregate placement,允许无 Symbol R 继续 composite。
假设以后仅让 R2 valid,旧 preparedSceneSupported=false 仍会在 #23 阻塞;不能等时间到就认为 prepared flag 自己恢复。Fast 的动态 transition 检查已避免直接沿用旧 tree changed/fade(I:259-263 注释),但没有解除 Full 产出的 scene 保守状态。隐藏 Symbol 可能使动态 usesPlacement=false、scene 不受它影响,结构性每段 gate 仍会拒绝首次 Build。
## 6. C:R2 Build / Invalidate / Fallback 状态机
### 6.1 从层结构到每段决策
**源码证明**,按执行顺序:
1. `include/mln/renderer/render_segment.hpp:48-90` 由 Style 元数据列举 Source 依赖与 Symbol 能力;type=symbol 就 capable,不看 visibility、当帧 bucket 或可见像素。越界 range 保守 capable;custom/custom-drawable 依赖 unknown。动态 `needsPlacement()` 则要求 CrossTileIndex::Required 且 placementData 非空(`src/mln/renderer/render_layer.cpp:30-32`),两者不等价。
2. `render_orchestrator.cpp:1173-1202` 按段依赖查询 Source loaded/fading/revision,CustomMVTVector 暂无 revision contract 而 fail-closed;`retainedSegmentIsStable:1205-1216` 检查 light、相关 Placement transition 和本段 Layer transition/repaint,不再把无关 Source fade 当每段阻塞。对比 Fast 的 `retainedRangeIsStable:1156-1170` 仍检查所有 Source fade。
3. `renderer_impl.cpp:516-535` 拓扑代际或 cache 数量变化时释放/重建每段 cache 容器;:558-559 的 key 为 viewport、begin/end、cameraMatrix。:641-652 组合 immutable layer impl+index fingerprint、source/global revision、pixelRatio。fingerprint 不是 Symbol 最终视觉签名。
4. :653-666 将 segmentStable、结构性 placement gate、style/sprite/image ready、Continuous/no3D/noDebug/noLegacy/noCustom 合并为 segmentSupported。
5. :668-680 首错优先级:source not ready → source unstable → style/sprite/global resource → placement → transition → legacy → unsupported → boundary → 3D → debug → mode。`static-placement` 是此处 gate 的名字,不是“R2 像素变了”的探测器,也不证明其它更后项通过。
6. :681-691 调 evaluate;只有 Build/Reuse 才 prepareResources;失败改为 resource-or-compositor。所有 supported 条件通过仍不代表最终纹理已经绘制成功。
### 6.2 状态转换和计数
所有状态均据 `src/mln/renderer/retained_underlay_cache.cpp`;这是源码状态机,不是本轮运行截图。
```mermaid
stateDiagram-v2
[*] --> Invalid: 创建 / 资源释放
Invalid --> Fallback: supported=false
Ready --> Fallback: supported=false / invalidate
Fallback --> Fallback: 下帧仍不支持 / 再计 invalidation
Invalid --> BuildPending: enabled 且 key存在 且 supported
Fallback --> BuildPending: 准入恢复
Ready --> BuildPending: key或revision变化
BuildPending --> Ready: 资源准备 + 离屏绘制成功
BuildPending --> Fallback: resource或draw失败
Ready --> Reuse: valid 且 key/revision相同
Reuse --> Ready: 纹理保持有效
Ready --> Invalid: releaseResources
```
| 分支 | 行号及真实后果 |
|---|---|
| 无 key | :173-182,原来 valid/key 存在才 invalidate/release,返回 Disabled;与 R2 static-placement 不同 |
| runtime disabled | :184-193,记 fallback;控制面同步 :129-163 另负责 invalidation/release |
| supported=false | :195-205,**无条件** fallback++、invalidate;revisionChanged 才 release。即使 valid 本来 false,也再次 invalidation++。未走 first-frame 和 BuildPending 分支 |
| revision 变化 | :171-172,207-213,invalidate/release、装新 key;这里 staticRevision 参数实际是每段 content 合成 revision,区别 aggregate tracker |
| 初次无 key / key 改变 | :214-219,first-frame / viewport / camera-or-boundary;设 key。普通 key 变化未必释放同尺寸 GPU allocation,但 valid 失效 |
| buildRevision 不一致 | :220-224,invalidate/release;不能复用旧代际 |
| valid=true | :225-236,reuse++,返回 Reuse;不代表 main-buffer 必 composite,还要看全局 live gate |
| invalid 且可支持 | :238-243,BuildPending,返回 Build;此时 buildCount 未增 |
| GPU 资源 | :246-303,离屏 RGBA unsigned-byte texture(带 depth/stencil 参数)及 composite drawable;失败返回 false |
| Build 真成功 | renderer_impl.cpp:813-862,beginBuild→R opaque/translucent→finishBuild→markBuildSucceeded;cache.cpp:332-345 设置 valid、buildRevision、build++、Ready |
| Build 失败 | cache.cpp:347-356,fallback++、invalidate、release,reason=resource-or-compositor;需要该 reason/资源计数,不能从 Build=0 单独推断 GPU 坏了 |
| invalidate / release | :359-380,前者 valid=false、记原因和计数;后者清 key/buildRevision/texture/compositor,不必增加同一种 invalidation 计数 |
`lastInvalidationReason` 是最后一次记录,Reuse 或成功 Build 不在此函数中自动抹掉它;采样必须连同本帧 decision、gate、frameId 使用。`plan.valid=true` 也不等于 `cache.isValid=true`。
### 6.3 FZG1 数字支持什么
**FZG1 实测(任务书第二区,历史输入)**:PREFIX 约60秒,Full +1787,R0 reuse +1787,R2 build/reuse=0/0,fallback/invalidation≈1787,R3 reuse +1755,Fast=0,首拒 cache-invalid。
**源码证明 + 条件推断**:每次 Full 的 R2 结构 capable → 第3章矛盾使 placement gate=false → supported=false → evaluate 直接 Fallback/invalidate → 正常 main-buffer live → 新 prepared tree 保存但 R2 invalid → 下一粒子帧 Fast #3 再失败。该闭环不需要假设 Source 每帧变化,也不需要假设 GPU 资源损坏。
**仍需设备验证**:本轮没有60秒每帧原始 trace,不能把一次末态 static-placement 扩成每帧唯一首因;若某帧 Source not ready,:668-672 会先报 Source。提供的 build 是绝对值还是窗口增量、topology 重建是否清计数,也应在后续原始 diagnostics 中核对。若“Build=0”只表示窗口增量,不能排除窗口前构建过;当前源码正常含 Symbol Full 无准入窗口则给出更强的机制约束。
Full 中 Symbol 正确显示,是 main buffer 的 live 绘制结果;cache Build 是单独离屏 render pass,:817 的 decision!=Build 会直接不画离屏纹理。没有自动把 main framebuffer 的 R2 像素拷贝成缓存的路径。R3 少于1787的32次复用差额可能来自资源/Source/全局条件,**没有足够逐帧证据归因**。
### 6.4 Collision 与资源边界
- **源码证明**:Placement::placeLayers 按完整 layer 列表逆序碰撞(`placement.cpp:201-206`);CollisionGroups::get(:45-62)在 `crossSourceCollisions=false` 时才按 Source 隔离,开启时使用无过滤组。:46-48 的英文注释与实际 if 的开关文字不一致,本文依执行分支,不照抄注释。相同 Source 跨段仍同组;关闭跨源碰撞也不等于关闭段内碰撞。
- **源码证明**:`Placement::commit:687-749` 内部同名局部变量 placementChanged 根据 text/icon placed 变化更新 fadeStartTime;外层 RenderTree.placementChanged 表示是否运行新计算。两个变量作用域和语义不同;全局 commit 也没有比较 R2 最终纹理。
- **源码证明**:Source/Tile mutation 可按依赖映射每段,glyph/image/style/context 未能局部定位时推进 global epoch(`render_orchestrator.cpp:243-275,327-347,1519-1521,1555-1559,1582-1594`;`render_segment.hpp:114-133`)。Camera/viewport/pixelRatio/拓扑另有 key/revision 保护。Source ready 不等于每个 Symbol shader 输入稳定。
- **条件推断**:别段符号或 Annotation 可能改变 R2 text/icon 可见性;同一位置/ID 也可能 opacity、variable anchor、orientation、line reprojection 改变。只有新一次完整 Placement 后证明最终有效输出及资源代际等价,才有候选复用条件。任何无法比较项必须 fail-closed。
## 7. D:Placement/Fade 真语义与可达状态
### 7.1 两个时钟、一个 stale 标记
**源码证明**:TransitionOptions 默认 enablePlacementTransitions=true(`include/mln/style/transition_options.hpp:12-26`),默认 duration=300ms(`include/mln/util/constants.hpp:59`)。`Placement` 构造保存当次 options、commitTime、placementZoom(`placement.cpp:180-192`)。因此 UI 当前 option 与 Controller 中旧 Placement 的 option 在下一次新建前可能暂时不同,诊断必须同时记录。
当 transitions 开启且 duration=D>0:
- `symbolFadeChange(now) = (now - commitTime)/D + prevZoomAdjustment`(:1282-1289);这是无 clamp 的增量,可能>1,零只说明刚 commit 且 zoomAdjustment 为零等特殊条件。
- shader 使用 `clamp(baseOpacity ± symbolFadeChange,0,1)`,符号实际已终态时 uniform 仍能变化;Fade 终态不以 uniform==0 定义。
- `Placement::hasTransitions` 用 **fadeStartTime** 而非 commitTime:`now-fadeStartTime < D`(:1319-1322);commit 没有 text/icon placed 改变时继承旧 fadeStartTime(:748-749)。新计算与新的真实 Fade 不是同义词。
- `placementIsRecent` 用 commitTime+updatePeriod>now(:160-167);普通 period=`max(300ms,D)*(1-zoomAdjustment(currentZoom))`(:1307-1312);zoomAdjustment=max(0,(oldZoom-newZoom)/1.5)(:1292-1298),zoom-out 可缩短到零甚至负值;此处没有下限 clamp。
- 新 Symbol buckets 且非 tiltedView 才使用 30ms **periodOverride**(O:778-789),不是把 Fade duration 改成30ms,也不是所有 Source 变化都立即重算。
- Controller `hasTransitions` 在 transitions 开启时 **stale 优先返回 true**(:170-175)。它含“需要重新评估”的语义,不能直接当作真实 opacity 正在变化的精确检测器。
- delay 保存在 options/signature,但上述三个 Placement 时间公式没有读取 delay;不能将一般 Style paint transition 的 delay 机械套给 Symbol placement fade。
### 7.2 transitions 开启的逐帧示意
**条件推断(非 FZG1 实测时间线)**:固定 camera、无新 bucket、D=300ms、约33.33ms一帧,t0 首次 placement 后内容稳定。忽略调度抖动,t0+300ms 的边界实际取决于 Clock。
| 时刻/情况 | RenderTree changed | fade increment | Controller transition | Symbol R gate | 解释 |
|---|---:|---:|---:|---:|---|
| t0 新可见 Symbol | true | 0(静止 zoom) | true,fadeStart=t0 | false | 真实淡入开始;base opacity 可能0 |
| t0+33…267ms,不重算 | false | 约0.11…0.89 | **true,stale**(即使真实 Fade 早已结束也如此) | false | O:805-806 无条件 stale |
| ≥t0+300ms,重新计算且显隐未变 | true | 重新归零 | false(旧 fadeStart 已到期、setPlacement 清 stale) | false | 可出现现场 true/0/false,但 changed 阻塞 |
| 再下一帧,显隐仍未变 | false | >0 | true(重新置 stale) | false | 不是新像素变化的证据 |
| 后续每到普通 period 重算 | true | 通常0 | 若无新显隐变化则false | false | 循环重复,未提供 Build 窗口 |
| 新 bucket 触发30ms override | 到期时true | 新 commit 通常0 | 视 actual placed/Fade 而定 | false | 约30FPS时可能每次新增 bucket 帧均到期,需事件证据 |
| zoom-out、period≤帧间隔 | 可每次true | prevZoomAdjustment≥0,可非0 | 取决于真实Fade | false | 不使用固定300ms频率推断 |
### 7.3 transitions 关闭(及 duration=0 的区别)
**源码证明**:已有 Placement.transitionsEnabled=false 后,`placementIsRecent` 总是 false(:163),所以**每次到达 Continuous Full 都新建并执行 placeLayers**,即便 layersNeedPlacement 为空也调用函数;Controller.hasTransitions=false(:171),`symbolFadeChange=1`(:1289)。因此正常关闭配置是 **true / 1 / false**,不是 true/0/false。这并不意味着有独立线程每帧强迫 Full:必须先有 render request 且 Fast 失败。
若仅 duration=0 而 enablePlacementTransitions=true,recent 仍使用至少300ms(zoom/override 除外),fade=1;真实 Placement.hasTransitions 通常false,但 non-recompute Full 仍 stale=true。仍不能通过 Symbol gate。
**源码证明**:Android JNI `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp:1012-1017` 明确传 duration、delay、enable;iOS `platform/darwin/src/MLNStyle.mm:521-539` 可分别设置 transition 和 performsPlacementTransitions。当前任务没有生产 Host 此项逐帧配置证据;不能拿 SDK 默认值替代 FZG1 实际值。Android getter :1000-1008 对 unset duration 返回0,若只记录 getter 值还需区分“未设置的默认300ms”和“明确设置0”。
**对任务假设的明确判定**:`symbolFadeChange==0` 不能代表“Fade 已稳定”,此语义误读已被源码证伪;但缓存无窗口的充分原因是 changed/stale 的互斥准入,不是仅这个比较符。任务要求解释末态 true/0/false:该值与开启配置的稳态重算相容;在未改写字段的正常 Full 中,不支持直接解释为“关闭 transitions”。Fast 重置树字段为 false/0,而不是 true/0。若日志声称关闭且仍 true/0/false,应优先核对同帧采样、运行二进制/选项生命周期,不能改源码迎合假设。
### 7.4 真值表:P=!changed && fade==0 && !transition
下表针对 placementCapable=true;无 Symbol R 直接通过本 gate,但仍受其它保护。以 c=changed、z=(fade==0)、t=Controller transition:
| c | z | t | P | 正常 Full 可达性/意义 |
|---:|---:|---:|---:|---|
| 0 | 0 | 0 | 0 | Continuous 正常 no-recompute 分支不可达:会 stale;抽象状态不足以证明安全 |
| 0 | 0 | 1 | 0 | 开启且未重算、时间增量>0;Fade 活跃或已结束都可 |
| 0 | 1 | 0 | **1** | 唯一逻辑通过组合;**正常 Continuous Full 不可达**。已命中 Fast 的树重置可有此形态,但不能自举 |
| 0 | 1 | 1 | 0 | 同 timestamp 重复 render、增量仍0而 stale;可达也不通过 |
| 1 | 0 | 0 | 0 | transitions关闭(fade=1);或开启duration0/zoom修正且无实际Fade |
| 1 | 0 | 1 | 0 | 新显隐变化伴随非零zoom修正等 |
| 1 | 1 | 0 | 0 | 开启、静止zoom、重算但旧Fade结束;现场末态相容 |
| 1 | 1 | 1 | 0 | 开启、新显隐/Fade、刚commit |
Bucket 增减、跨段碰撞可能改变 `Placement::commit` 内部 placed 状态、重置 fadeStart;其是否真实改变 R2 需逐 Symbol 数据。即便完全不改变,也落入上述无窗口分支。没有 Source 变化也足以卡住;Source 变化不是这个机制的必要条件。
### 7.5 Shader 与 Full 后安全 Build 的必要证据
**源码证明**:`Placement::commit:694-724` 将上一 placement 的增量折入新的 OpacityState;`OpacityState:18-24` 保存 base opacity 和 placed 方向。`RenderTreeImpl::prepare`(O:133-138)→ `Placement::updateLayerBuckets:752-757` → `SymbolBucket::updateVertices`(`src/mln/renderer/buckets/symbol_bucket.cpp:346-366`)更新 opacity 与动态属性;`Placement::updateBucketOpacities:962-1029` 处理 duplicate、default、placed hidden 和属性编码。
`renderer_impl.cpp:462,755-766` 把树的 fade 送入全局 shader uniform。OpenGL:`shaders/symbol_sdf.vertex.glsl:153-158`、`symbol_text_and_icon.vertex.glsl:152-158`、`symbol_icon.vertex.glsl:127-129`;Metal:`include/mln/shaders/mtl/symbol.hpp:157-164,372-374,644-646`,都将 opacity 基值加/减 fade 并 clamp。Metal fragment :257-263,495-530 再乘 paint opacity;GL `shaders/symbol_sdf.fragment.glsl:77` 和 `symbol_text_and_icon.fragment.glsl:53-57,94` 也乘 paint opacity。零 fade 可以对应 base=0 的新符号或 base=1 的已完成符号,数值自身不足以判稳。
**条件推断(提案前提,尚非安全证明)**:可候选在完整 Placement 和 bucket 更新、shader 输入就绪后,使用一个与 Build 世代绑定的“输出已终态且不会仅随时间变化”证据,再建立 R2。必须同时覆盖 text/icon、duplicate、variable anchor/orientation、沿线重投影(placement.cpp:776-959)、paint opacity transition、glyph/image atlas、Tile/Source/camera/context、层序/深度/混合边界。仅 `!Controller.hasTransitions` 不足以替代这些证据,因 Controller 的 stale 是待重算语义;仅删除 changed 或把 ==0 改成≥1 也无法建立与缓存像素绑定的不变量。
## 8. E:S1—S5 反事实与因果闭环
### S1:仅粒子持续推进,所有静态输入就绪且不变
**源码证明/条件推断**:以任务要求的“仍有含 Symbol 的 R2、粒子30FPS”为前提,而不是把“纯粒子”误解为地图根本没有 Symbol。
- f0 无 prepared tree,必 Full;R2 gate 拒绝,R0/R3 若无 Symbol 且其余条件通过可 Build。
- f1 Fast 首先看到 R2 invalid;Full 中要么 changed=true,要么 stale=true,再次拒绝 R2。R0/R3 可 Reuse。
- f2…重复;启用 transitions 的重算频率由 period/zoom/bucket 决定,关闭时每次 Full 重算。**即使无任何外部 Source mutation,当前 Symbol R 仍无法从第一次正常 Full 自举。**
- 反例边界:若全部 R 都无 Symbol,单 C 只包含受支持的 WeatherColor/Particle,所有 Gate 满足,正常 Full 能建缓存、随后 Fast 可以命中;Placement 不需每300ms独立强迫新 Full。若 f0 还有真实全局 Fade,则等待必要 Full 结束后才可能命中。
- **仍需设备验证**:以上状态机不等于已测到30FPS、具体 CPU 成本或缓存最终像素正确。本轮未执行场景。
### S2:粒子关闭,只剩偶尔地图刷新
**源码证明/条件推断**:关闭 active/visibility 或清掉必要场数据会取消粒子 needsRepaint,#18 不能命中 Particle Fast。若同时停止 forecast、没有其它动画及 pending 资源,就失去高频 SDK wake/repaint 链;最终 loaded 且无 transition 可 Idle。期间每个真实 Full 仍进行 recent 判定,关闭 transitions 时每个 Full 都算 Placement,开启时仍可能用几帧完成 Fade/stale。
**FZG1 实测**:关闭粒子对照约83次Full/分钟来自任务输入。**不能证明**这83次都由导航、Placement 或 Tile 任一单源造成;也不能证明 Placement 从不运行。若只隐藏粒子却仍 forecast Playing,WeatherRuntime :252 仍唤醒,因此“粒子关=无天气高频唤醒”必须同时核对预报状态。83次与1792次的差异支持高频粒子场景增加 render 请求,但不提供精确按来源分摊。
### S3:真实 Host Source Mutation + Annotation + Tile 异步输入
**源码证明**:Style Source 更新先由 `src/mln/style/style_impl.cpp:348-351` 通知 onUpdate;新 sources identity 会令 Fast #8 失败。Full 的 O:525-537 每批非空 sourceDiff 推进一次 aggregate content revision,并为每个增删改 Source 推进 local revision。Tile callback O:1555-1559 则每次推进 aggregate/local 并 invalidate;Glyph/Image 无明确 source 时推进 global。Annotation dirty(`annotation_manager.cpp:213-227`)使 #24 拒绝,Full O:360-363 消费 dirty。
**理论量化(条件推断,非测量)**:
- 一批 Full sourceDiff 含 m 个 Source 修改:aggregate 增1、local advance m 次;m 不等于 m 个 Full。
- N 次独立 onTileChanged:当前实现 N 次 aggregate/local revision advance;若在下一 render 前合并,仅需要一次承载最新状态的 Full,若穿插 N 帧则可每帧拒 Fast。Fade、stale、继续到来的异步输入又可能延长 Full 序列,故不能用 N 推出总 Full=N 或每次 mutation 固定只需1帧。
- 已 valid 的无 Symbol R,若仅依赖 Source S,S 的新 revision 使下一次可支持的 evaluate invalidate/build;其它无 Symbol R 可复用。global epoch 变化则所有段的新 revision 都不同。每段 Build次数≤它实际有资格 Build 且绘制成功的 Full 次数;当前 Symbol R 无资格,所以 Build仍0并非 mutation太频繁的唯一解释。
- 只在 C 的 Source 变化可使整帧 Full,却不必令独立 R 的 local revision改变;碰撞关系仍可使 Symbol R 输出变化,不能以 source依赖表忽略跨段 Symbol。
**重要独立来源**:V4 forecast Playing 时 `Map::onUpdate` → `tickForecastClock` → `refreshV4RendererState`;后者 `raster_array_source.cpp:569-608` 在投影 sampleState/端点变化时换 Source impl 并通知,即使 T0/T1 纹理可复用,也可能令 #8 每次失败。这是 Native forecast 的真实输入更新,不等于静态风场粒子 advection,也不能无证据统归 NavSea 导航 Mutation。S1 必须固定 forecast 时间;S3 诊断应分别计 external Source 和 forecast projection。
### S4:布局对照,保留真实层序
| 布局及证据 | 分段 | R 缓存 | C / Fast 独立门槛 |
|---|---|---|---|
| 原 RC(STEP-110 现场历史) | R[0,31)、C[31,113),R含地名 Symbol | Symbol R当前无Build窗口 | 后段含大量普通业务层,若有普通 Symbol/Line,#22必拒;不能把 RC1/RC2 的所有字段与旧快照自动认作同一采样 |
| 原 RCRR(四段的泛型/候选基准) | 若为 R[0,31)、C[31,33)、R[33,107)、R[107,113),须由原始 plan 确认;**本轮任务未给该原配置完整边界,不将此数字当实测** | 首R和R2若含Symbol仍被阻塞 | 条件下 C仅 WeatherColor+Particle 则类型Gate可过,但 scene及所有R仍要过。泛型RCRR只说明策略排列,不保证边界和类型 |
| **RCRR_PREFIX(任务书现场输入)** | R0[0,13)、C1[13,33)、R2[33,107)、R3[107,113) | R0复用1787、R2不建、R3复用1755;与Full mixed执行相容 | C1含普通Symbol/Line+WeatherColor31+Particle32;`O:1247` 遇普通层返回false,#22确定被#3掩盖 |
| **理想保序反事实,未实施** | R[0,13)、R[13,32)、C[32,33)、R[33,107)、R[107,113) | 新R[13,32)及R2含Symbol,当前一样卡住;WeatherColor31也进入R | 恰好一个C且只有Particle可消除类型障碍;不消除Symbol Gate、scene、资源/source/annotation。若forecast/WeatherColor继续变化,新R必须重建/回退 |
`singleContinuousSegment` 在 `renderer_impl.cpp:55-63` 限定唯一 C;`RenderLayer::startsRetainedDynamicRange` 默认false(`render_layer.hpp:113-120`),Particle/WeatherColor 才 override true(对应 layer头 :31-33/:27),普通 Symbol/Line 即便暂时隐藏也不会因不可见自动成为受支持 C。多 R 本身不是禁止项;多 C 是当前 Fast 的独立阻塞。STEP-106 的 R-C-R-C 是缓存局部性测试,不是生产 Fast 性能证明。
**仍需设备验证**:补原始 RC1/RC2/RCRR plan JSON、113层完整 id/type/source/visibility/顺序,才能逐层定位实际布局;本轮没有跨项目改 Style。理想配置只做反事实,不能承诺 WeatherColor永远静态,更不能删海图Symbol或把普通层批量挪C作为优化。
### S5:动态安全回退
| 变化 | 必须处理的 R / Full 条件(源码证明+条件推断) | 不可盲复用的理由 |
|---|---|---|
| pan/zoom/rotate/pitch、viewport/pixelRatio | 屏幕缓存 key 改变的所有R重建;Fast camera/viewport/signature拒绝 | 旧屏幕纹理、anchor/reprojection不再对应当前相机;定图仍需碰撞正确 |
| 相关 Tile/source更新、缺失/fading | 依赖段 Source gate/revision失效;Fast aggregate/content/fading可能整帧Full | 异步完成意味着可见内容改变;非依赖段只在全局证据允许时继续复用 |
| Glyph/Sprite/Image/fontFace | 当前实现 global epoch及ready;通常所有R保守失效 | atlas/字形纹理可能变化,sourceReady不足以覆盖 |
| text/icon opacity Fade或paint transition | 受影响Symbol R不能冻结随时间变化的像素;现有全局Controller/gate更保守 | uniform与base opacity共同决定alpha;不能从fade=0/Controller=false单独得出结果等价 |
| 跨段Collision变化 | 所有碰撞影响到的Symbol R必须刷新;当前没有可靠per-R证据时保守回退 | source不等于segment;跨源关闭只隔离不同source,同source仍相互影响 |
| Annotation插入/更新 | 拓扑/Source/pending及碰撞具体属性分别处理 | 自动 points Symbol由 `annotation_manager.cpp:176-192` 添加,默认iconAllowOverlap/iconIgnorePlacement=true,**不能断言它一定排斥R2**;结构gate照样capable,实际影响还看其它Symbol/配置 |
| Style属性/顺序、Layer增删、Plan边界 | 本段fingerprint或全局topology变化;对应cache释放/重建 | 层序和混合语义不能被texture复用改变 |
| context/resource/compositor失效 | 已损坏资源全部重建;失败保持live/fallback | valid标志不能替代真实资源准备和draw成功 |
## 9. A—D 提案比较(均未实施)
优先顺序:**A诊断补齐 → B建立严格生命周期准入原型并同步处理scene → C按需求扩展per-R等价;D先交Integration确认公共“强制Full”语义,再决定实现**。C范围布局问题与这些方案独立:保序单粒子C是候选配置,任何产品Style变更都另行授权。
| 提案 | 首次Build / 全局scene / C Gate | 正确性与风险 | CPU / GPU / 内存代价 | 候选修改文件、复杂度、双平台与最小验证 |
|---|---|---|---|---|
| **A 全Gate+R2状态诊断,优先P0** | 不改变Build或scene/C结果;一次取齐被短路掩盖项 | 不改变fail-closed;读状态需同frame和phase,不能额外调用有计数副作用的needsRepaint造成伪观测 | CPU约O(现有gate数+R数),复用已算结果;固定环形缓冲有界内存;GPU零额外工作。采样成本须实测 | `particle_fast_path.hpp`、`renderer_impl.cpp`、`render_orchestrator.cpp/.hpp`、`placement.hpp/.cpp`、现有diagnostics结构;**2/5**。Shared Core同字段,Android/iOS只分别接低频导出。最小验证见第10章,记录schema无需新增公共API即可先做Native内部诊断 |
| **B Full后安全Build,优先P1** | **目标解决首次Build**;必须同时替换每段准入和prepared scene的旧证据,单改其中一处无效;不解决普通C层Gate | 保留全图Placement与全部资源保护,以已经完成的Symbol shader输入/终态证据授权。先shadow,不直接删除changed/stale/fade条件。危险为早冻结Fade、漏collision/atlas/paint状态、引用过期bucket | Full本来就做Placement,可在commit/bucket完成点记录代际,稳态尽量O(1)/段;首次离屏多一次R绘制,后续composite;每R现有全屏RGBA颜色约4WH字节,另depth/stencil和backend开销,更多R线性增加 | `placement.hpp/.cpp`、`symbol_bucket.cpp/.hpp`、`render_orchestrator.cpp/.hpp`、`renderer_impl.cpp`、`render_segment.hpp`、cache状态结构;按所需证据可缩减,非已定设计。**4/5**。GL/Metal shader不预设需要改;必须两后端同提交检查opacity精度和texture合成。最小:静止Symbol R开启/关闭transitions、首次可见、fade长尾、camera/context/资源失效,false-reuse=0 |
| **C 更细per-R结果等价,优先P2** | 可以为B提供首建/重建的证据;单纯“新旧签名相等”没有旧Build时不能自举;scene也必须接纳相同证据;不解决C Gate | 与STEP-111一致,仍全图碰撞,再按R比较结果;stable ID、世代和实际GPU输入齐全才允许reuse。hash相等不是完整安全证明;未知必须回退 | 每次Full placement后O(受影响symbol数),避免每粒子tick遍历;签名/版本及必要精确快照占O(symbol数)或O(段数+变化集);降低R GPU重绘但不自动省CPU prepare | `placement.hpp/.cpp`、`cross_tile_symbol_index.cpp/.hpp`、`symbol_bucket`、`render_layer`、`render_orchestrator`、cache/renderer接口。**4/5**;深改分段Collision为**5/5且不推荐本轮**。双平台共用结果证据、验证shader差异。最小:同Source跨R、跨源开关、C1仅影响R2/影响多个R、duplicate/variable-anchor/沿线字/Annotation/重载;以缓存关对照 |
| **D 外部Full与Particle Tick分流,Integration先行** | 单独不解决Symbol首次Build、scene或C类型;可明确某次更新必须Full,并在纯tick时优先Fast | 用户要求外部明确Full不能以数据相同去重/延迟;需明确序号、确认时点、合并规则、生命周期与平台一致性,禁止把重要资源更新当tick | 内部原因bit/epoch通常O(1);若每个命令都必须独立一次Full会增加CPU/GPU,额外队列可能有界内存需求。收益取决于频率,不能承诺2 Full+28 Fast | `UpdateParameters`、`Map::Impl`、Renderer入口、Android frontend、Darwin frontend及必要RN公共层;**3/5**(跨项目/生命周期可到4)。现有triggerRepaint不是此契约。最小:相同数据两次明确Full、命令与tick/异步tile竞态、禁用销毁重建、Android异步合并和iOS,检查每个accepted序号按决议完成;不得私建API |
### 9.1 B/C 的数据所有权与最轻量可行方向
**源码证明**:`Placement` 持有 placements、opacities、variableOffsets、placedOrientations(`placement.hpp:184-201`),以 crossTileID 索引;`getSymbolPlacement`(`placement.cpp:1301-1304`)查具体Symbol。`placeLayers` 后 commit 已完成,但最终bucket opacity/duplicate和动态投影更新在 RenderTree.prepare,GPU drawable/tweaker又在之后。`Placement` 至多保留上一代(:190-192),cache可能跨更多代存在,因此只能与上次成功Build版本绑定,不能仅比较相邻两次Placement。
`getPlacedSymbolsData` 对普通 Continuous Placement 返回静态空数组(:1325-1327),TilePlacement有专门实现(:1378-1387);不能把现有public查询当完整R2结果快照。STEP-111的候选字段仍需要逐项实现与验证,本任务不宣称已设计出可直接上生产的签名。
**候选轻量方案(条件推断)**:先争取“静态输入代际相同 + 完整Placement结束 + 所有目标symbol有效opacity已终态 + 无未消费资源/桶变更”的全局生命周期证书,授权该次Full离屏Build并保存buildEpoch;下次tick只比epoch和既有Gate。比每次tick全symbol hash轻。但必须证明碰撞/hidden/duplicate/variableOffset/paint动画均被证书覆盖;如不能低成本取得这一证明,继续fail-closed,退回shadow per-R比较,而非改用Controller.stale=false假装稳定。
更细C候选字段分工:Source/tile/bucket generation、crossTileID及R归属、text/icon placed/hidden、实际anchor/orientation/动态顶点和编码opacity需要结果证据;Camera/viewport/pixelRatio、Style顺序、Source/global资源代际优先复用已有key/revision;paint opacity/transition必须覆盖时间依赖。首次帧没有参考就Build,未知或资源重建丢弃证书,真实结果变化则重建。内存与比较开销需要新增计数验证,不能从“理论可hash”推出安全或高性能。
### 9.2 D 的 Interface Blocker
**源码证明**:当前 `Map::triggerRepaint`(map.cpp:93-95)只调用onUpdate;`UpdateParameters`(`src/mln/renderer/update_parameters.hpp:25-61`)没有 externalFull/原因序号字段;Fast签名不比较timePoint,入口每次都先试Fast。`packages/maplibre-particle-rn/src/RendererRuntime.ts:12-27` 只有retained控制、continuous range/segment plan和diagnostics,未提供“外部指令强制Full并确认”的方法。Android frontend AsyncTask/最新params设计还有合并行为,不能自动保证每次命令一帧。
**Interface Blocker(提案,不是当前调查阻塞)**:若用户希望公共Host“明确Full”命令具有不可被相同数据去重/延期的语义,Integration必须确认:完整更新是CPU prepare/全图Placement重新评估,还是强制所有R GPU重画;两个相邻命令能否同一Full满足、如何回执、如何与异步资源到达排序。既有C-001/C-002没有该公共语义。本轮仅只读 `/Users/tei/dev/navsea-particle-integration/contracts/PARTICLE_NAVSEA_BOUNDARY.md`、`CONTRACT_VERSION.md` 和COORD-005决议;C-001边界继续有效、C-002是QBIN窗口增补。不得通过Native私有通道绕过协调,未修改Integration,也未发送跨项目消息。
纯内部性能优化可先留在Shared Core,不需要借口更改Host职责;明确Full请求一旦获授权,不能用“内容相同”跳过或推迟它,普通粒子tick则优先Fast并保留相机/资源/Placement等安全Full。**2 Full+28 Fast只是目标例子,非当前实现承诺或验收门槛。**
## 10. 下一阶段最小诊断提案与独立验收证据
**仅提案,未插桩、未执行、未创建下一Step。** 当前源码已能证明控制流矛盾;诊断用于确认FZG1实际选择的配置/路径、其它Gate和主要CPU成本,并为安全改造建立基线。
### 10.1 同帧字段与采样点
| 位置 | 最小字段 | 判别目的 |
|---|---|---|
| Map::onUpdate / SDK cadence wake | map generation、update/request序号、SDK wake/observer repaint/Host mutation/forecast tick来源、targetFps、active/visibility、forecastPlaying、sourceId、Clock时间 | 区分下一帧请求与外部数据更新;调度来源标识仅Native内部提案,不私建公共命令 |
| I:199-278,Fast判定前 | frameId、UpdateParameters.timePoint、实际render时间、24项bitset、首错、changed signature字段mask、prepared tree/scene标志、所有R valid/buildRevision/staticRevision、config/topology/content revision | 一次记录所有Gate;多request合并和同一params重渲染可识别;避免只看cache-invalid |
| O:778-810,Placement前后 | **当前options与旧Placement options** enable/duration是否unset/delay、commitTime、fadeStartTime、now、zoom/prevZoomAdjustment、override及原因、bucketAdded/Changed、recent、stale前后、outer changed、commit实际placed变化计数、raw fade和Controller/Placement各自hasTransitions | 验证开关分支、300/30ms和zoom影响;区分实际Fade与stale |
| RenderTree.prepare后 / I:637-691 | tree fade、shader fade、每R capability、动态usesPlacement、placementGate、sourceReady/stable、fingerprint、content revision、supported、fallback各项bitset、cache前状态、evaluate decision | 确认是否Gate阻止首次Build、是否其它失败被placement reason遮蔽 |
| cache Build/execute / I:813-862,998-1028 | BuildPending/成功/失败、valid/key/资源世代、texture/compositor create/release、build/reuse/invalidation/fallback累计及delta、actualExecution、globalLiveReason、GPU pass耗时(如平台已有可靠计时) | 拒绝Build与GPU失败分开;Reuse与实际composite分开 |
| frame完成 | FullCPU/fast/simulationAdvance计数、createTree/placement/prepare/layerUpdate/encode耗时、repaint原因bits、loaded/Idle | 不能用Full计数代替Placement次数或simulation次数 |
字段不存在时新增只读诊断accessor应另行授权;不可通过额外一次placeLayers获得“测量值”。重用本来算出的gate bool与needsRepaint值,避免观察过程增加计数或推进cadence。
采样建议:每组先10秒预热、再60秒统计;前3秒及状态变化前后保留连续逐帧详情,60秒完整保留紧凑gate/段决策和计数;用固定容量Native环形缓冲、超限显式记录dropped计数。日志导出1Hz摘要或窗口结束统一写,不每帧JSON/JS bridge、不每Symbol默认打印。以实测结构大小和容量给出内存上限;对trace开/关同场景比较CPU/frame,目标开销预算先建议≤1%,达不到则缩减详细采样但保留全部gate bitset。这个预算不是当前已达到指标。
### 10.2 最小场景与判断
1. FZG1原V23、同场/同camera:粒子开/关,分别固定forecast暂停;保留原plan和所有113层元数据。复现计数只用于基线,不要求改变现有结果。
2. 同输入分别enablePlacementTransitions=true/false;true下记录D=默认/显式值,false预期Full组合true/1/false。每个Symbol R应在每个Full看到gate=false;若有正常Full gate=true,是本报告不变量反例,应保留原始帧/版本并停止假设式修复。
3. PREFIX,记录#22普通层第一个拒绝的id/type/index,同时#3失败;原RC和真实RCRR分别保留配置,不复用旧布局推断。
4. 无外部Mutation的固定field、forecast暂停,与导航/Annotation更新、Tile完成、forecastPlaying分别对照,按来源和phase对齐revision。不得靠降低粒子FPS制造好看CPU。
5. 后续B/C shadow原型才验证:首建可达、fade长尾/variable anchors/跨源碰撞/同源跨段/相机/Style/清场/context;缓存开关同帧结果对照,**false-reuse=0**,不能用总FPS掩盖陈旧字/图标、错层或缺字。Android OpenGL和iOS Metal同源码各跑,不能用一端替代另一端。
独立验收需要:源码commit及制品SHA/实际SDK版本、plan JSON与Style层表、全部gate bitset、Placement两套配置和时间、R状态机计数、trace开关开销、CPU阶段耗时、同帧视觉/必要图像差异证据、原始日志路径。任何提升Fast命中/CPU降低数字必须来自新实验。本轮不提供尚不存在的诊断开关命令,也不执行构建、测试或设备动作。
## 11. 最终结论、未确定事项与责任边界
### 11.1 已证明
1. **源码证明**:粒子/SDK只请求下一次正常地图渲染;每次先试Fast,没有粒子专属“必须Full”命令。当前Full CPU频率是请求频率与准入失败共同作用,不等于静态像素每帧变化。
2. **源码证明**:正常Continuous Full对结构性Symbol R的三项gate无可达通过分支:changed或stale→transition二者必挡其一。它足以解释在静态输入下仍无法首次Build;不是仅仅“可能比较符用错”的猜测。
3. **源码证明**:fade=0不是终态证明;关闭transitions正常返回1。FZG1 true/0/false快照不能用来断定关闭配置或每帧300ms周期。
4. **源码证明**:还有prepared全局scene锁与C范围类型锁;PREFIX普通Symbol/Line C不支持Fast。仅修R2不能承诺Fast命中。
5. **源码证明**:缓存invalidate计数不要求曾valid;Full可复用其它R并live画R2,正常主屏显示不等于离屏Build成功。
6. **源码证明**:V23相关核心gate与本轮main一致;STEP-109仅改变两Layer的field深比较快路,不解除以上限制。
### 11.2 假设判定与历史报告关系
| 假设 | 判定与证据强度 |
|---|---|
| 粒子每次tick直接改变静态R内容 | **不成立为必然关系,源码证明**;纯simulation不改R,forecast/Host更新另算 |
| C1每帧碰撞使R2符号真实变化,才导致Build0 | **未证实且非必要条件**;gate控制流在零mutation也不可达 |
| `symbolFadeChange==0`等于Fade结束 | **源码证伪**;它是增量,不是稳定布尔量 |
| FZG1 true/0/false证明关闭transitions | **源码不支持**;关闭的正常Full为true/1/false |
| 删除placementChanged或改==0即可安全修复 | **不支持**;stale/scene/C/资源/碰撞/opacity仍独立存在 |
| 现有Gate是合理保守且没有任何进展性问题 | **需细化**:fail-closed保护正确,但正常Symbol R Full首次Build不可达是可证的功能/性能准入限制;是否按产品契约命名为Bug由后续设计确认,不是视觉错误或可直接放宽安全的证据 |
STEP-110/111保留原样。它们强调“来源不等于视觉影响、缺逐R安全证据”的结论仍成立;本轮新增的是 **changed/stale互斥导致准入不可达** 的完整控制流证据,不能把旧PARTIAL说成已实现修复,也不重写历史来伪装一致。
### 11.3 尚不确定 / 仍需设备验证
- FZG1每一帧enable/duration、bucket添加、zoom修正、调用来源和24项Gate真实值;末态仅一个快照。
- 原RCRR完整边界和RC1/RC2精确同版Style列表;本报告不把推测布局写为实测。
- 导航Source、Native forecast投影、Annotation、资源异步输入各自占多少Full/CPU;83与1792不是按来源归因数据。
- R2每个Symbol实际最终输出是否与某个候选缓存时刻完全等价、可接受证书的充分字段及实现成本。
- B/C实现后的首建、Fast hit、CPU/GPU/内存收益、GL/Metal像素一致性和安全回退;本轮都没有实现或运行结果。
**交付判定:本轮A—E源码调查与提案已完成;综合结论保留PARTIAL(生产逐帧归因、完整原始布局及安全修复/性能未验证),不标实现/运行/性能PASS。** 已有决定性源码机制证据,不再以猜测“等Fade结束就可能Build”作为建议。下一阶段最小工作是A的同帧诊断和B的安全证据设计;维持现有fail-closed,不直接修Native。
Codex调查阶段只交付四份文档,不承担Git操作;STEP-112任务书第十一节要求RDC独立进行仅文档scoped提交、推送和远端回读。Git最终状态以RDC收尾补记为准。

View File

@@ -0,0 +1,78 @@
# STEP-114 Fast Path 安全准入与闭环判定 v1
日期:2026-10-11(JST)。结论:**PARTIAL;安全子集源码与独立最小场景已交付,真实 Fast 尚未验收。**
## 1. 基线、证据等级与阅读
Mac / Darwin 24.6.0 / x86_64,`/Users/tei/dev/MapLibreParticleLab`,`particle/main@b02e54de3d45fcf612126ae8f037cba204f590ea`。源码版本 `13.6.1-particle.23`,没有发行或改 Source Lock。实际模型 GPT-6.1-Sol / high 已从当前本地 rollout 的 `turn_context` 核实。
已读根 AGENTS、PARTICLE_BASELINE、项目规范全部八份、项目总则 README 与四份有效总则、本任务、STEP-112 调查/完成报告、STEP-113 完成报告/Step(含两轮 RDC 追加)、实际 Shared Core 和 Test App 源码。Integration 只读 AGENTS、README、WORKFLOW、TEAM_MODEL、C-001/C-002、Contract 版本规则、RFC-001/002/004、ADR-001、COORD-005 决议;不写跨项目文件,不改 RN/JS API/ABI。
SOURCE-PROVEN 表示本轮源码控制流;RETAINED-EVIDENCE 表示本机保留的旧 RDC JSON;IMPLEMENTED 表示已改源码;NOT RUN 表示未编译/运行。旧设备证据不是本轮改动的设备验收。
## 2. R 的真实 SourceType 与初建失败点
不能从 `unknown-dependency` 推断 source 一定是 CustomMVTVector:它还覆盖未知依赖、缺 revision 登记等情况。
本轮只读 `artifacts/maplibre-particle/android-validation/step113/source-change-after-full.json`:Native source 表为 mapple/raster、navsea_delivery/vector、org.maplibre.annotations/annotations、rn-weather-fields/raster-array、step113-source-probe/geojson。R0 `[0,63)` 的依赖为 mapple、navsea_delivery、**tide**;第2层 `tide-spots` 是 `symbol`、sourceId=`tide`,source 表却没有 tide。R0 的 sourceRevision=0、sourceReady=false、Build=0、fallback=unknown-dependency。
源码 `style/conversion/source.cpp::convertVectorSource` 创建 `VectorSource`;其构造函数指定 `SourceType::Vector`;`RenderSource::create` 创建 `RenderVectorSource`。因此该 Test App 的正式 PBF source **不是 CustomMVTVector**。任务书列出的 CustomMVTVector 限制是独立安全约束,本轮完整保留。没有改旧报告,也没有补造 tide 数据、删原正式 PBF 层或修改服务 Style。
`retainedSegmentSourceState` 原先先读取 revisionFor,缺少 tide 的登记会返回 nullopt,从而提前返回默认 unknown-dependency。现在在读取 revision 前发现缺失 RenderSource 时明确返回 `missing-source`;本例的ready/stable=false、revision=0 不变。有旧tombstone记录的缺源也提前返回revision=0,而不暴露旧登记hash;它仍不得Build/Reuse,RevisionBook登记机制不变。这是诊断修正,**不改变缓存准入**。source 存在但无 revision、CustomMVTVector、未知 layer dependency 仍 fail-closed。
后续即使 Host 合法补齐 tide,R 仍包含大量 Symbol,Placement 首建阻塞不会自动消失。缺源与 Placement 是不同失败点。
## 3. 已支持的本地 GeoJSON/circle 子集
FAST_SMOKE 选择本地 GeoJSON 非空点与 circle R,不新增 source 类型授权。其现有追踪链:
| 输入或事件 | 源码链与保护 |
|---|---|
| GeoJSON setGeoJSON | `GeoJSONSource::setGeoJSONData` 替换 immutable Impl 并通知 SourceChanged;Fast non-camera signature 比较 sources;Full `diffSources` 推进全局 content 和本 source revision |
| Source 增删/同ID重建 | `createRenderTree` 的 sourceDiff added/removed/changed 都 advanceSource;RevisionBook 保留 tombstone;PreparedTree 在 Full 更新对象前释放 |
| 异步 GeoJSON 数据/布局 | `RenderGeoJSONSource::update` → GeoJSONTile::updateData;capturedData 和 weakPtr 防陈旧/销毁回调;GeometryTile correlationID 保护 pending;`onLayout` 每次更换 layoutResult 都 onTileChanged;RenderSource → orchestrator 推进 source-local revision 和全局 content,再 invalidate |
| Tile 未就绪/淡入 | `isLoaded`、hasFadingTiles、source assessment、#17 与 R 稳定性检查继续保留 |
| Layer paint/layout/顺序 | immutable layers signature、per-R content fingerprint、resolved plan/topology;有 transition/repaint 仍阻止 |
| Camera/viewport/pixelRatio | #6/#7/#8、RetainedUnderlayKey 与 revision state;旧 texture 不能按新矩阵复用 |
| Style reload | styleLoaded/signature、配置/层序/Source 新身份、PreparedTree 生命周期保护;trace 在实际看到 loaded→false 时重置 |
| Image/Sprite | imageDiff、spriteLoaded、imageManager readiness、missing/unused image callbacks 推进全局 revision;#8 先捕获新 images 输入 |
| Glyph/fontFaces/URL | non-camera signature、glyph URL/font identity 追踪、onGlyphsLoaded 推进全局 revision;不删除资源门禁 |
| Annotation | #24 pending 检查、source/tile/图片变化与层表;实际 Symbol 继续纳入全场结构检查 |
| context/reset/destroy | markContextLost、Renderer.clearData 清 PreparedTree/trace,orchestrator 推进 revision;析构 detach observer 前排空 source/tile 工作;Test App 取消 callback、dispose runtime、删除自有 marker,MapView 原生命周期仍由 Activity 执行 |
这一闭环是现有实现的源码依据,**不是本轮已跑出的 Source mutation/GPU PASS**。异步过渡中 Full 也可能继续展示尚未更新的旧 bucket;要求 Fast 与同一时刻合法 Full 等价,而非承诺提交 API 后同步完成画面更新。成功布局后必须失效并正确重建;不可用两张稳定末态截图代替事件顺序证据。
R source policy、RevisionBook、GPU Build/Reuse 算法本轮零语义修改。新增 C++ 用例验证既有 revision/cache 状态机组合;unit 中显式推进 epoch/标记构建只用于状态机测试,不是实际 Native 帧或 GPU 成功的证据。
## 4. C、Annotation 与 Scene 聚合
`continuousRangeSupportsParticleFastPath` 要求每个 C layer 都 startsRetainedDynamicRange,并至少有粒子动画层。WeatherColor 与 RasterParticle 符合;普通 circle、Symbol、内建 Annotation Symbol 不符合。`particlePreparedSceneSupported = retainedSupported && fastContinuousSupported`,并要求单一 C。#22/#23 可以共享 C 不支持根因。
本轮没有修改 #22/#23、C GPU 复合流程、层序或 AnnotationManager。不能证明非粒子 C 与既有 Fast prepare/postRender 等价,故不放行。
独立 FAST_SMOKE 在确认 `map.annotations.isEmpty()` 后,仅从本场景 Style 删除自动生成且空的 `org.maplibre.annotations.points` layer,保留内建 source;若缺层或 remove 失败则 BLOCKED。Native annotation 更新规则未变,正式 PBF 五场景不删除该层。baseline 层序应为 background、R circle、weather-color、raster-particle,实际 JSON 仍需 RDC 核对。
ANNOTATION_BLOCKED 重新加载一个正常 Style,保留自动内建层并用现有 `map.addMarker` 加真实 marker;可单次修改其 position。内建 Symbol 位于 C,#22/#23 必须继续阻止 Fast。从该场景切回 baseline 时只删除本场景拥有的 marker,不能清除其他 Annotation。若存在其他 Annotation,baseline 拒绝启动。该场景转换含合法 Style reload,不是生产 Annotation 零重载支持的证明。
## 5. Placement:安全局部修复与 BLOCKED
正常 Continuous Full:placementIsRecent=false 则 placeLayers/commit,placementChanged=true;recent=true 则 setPlacementStale,controller.hasTransitions=true;关闭 transitions 时正常 Full changed=true、fade=1。含结构性 Symbol 的 R 无可证明的首次 Build 稳定窗口。
本轮保持 Symbol R 的三项 gate、scene placementStable、全图碰撞、fade/transition 和跨 source 规则。完整 GPU symbol 输入、bucket/atlas/opacity/duplicate/anchor 的结果证书仍不存在;建立它需要独立结果签名或更大生命周期设计。**Symbol R 准入修复 BLOCKED,不能删除 changed/stale/fade 条件。**
唯一行为修复限定于**完整非空层表没有任何结构性 Symbol**:此时 controller 的 stale/transition 没有 Symbol 像素消费者,单独阻止 #13 不代表画面可能变化。`particleFastPathHasPlacementTransition` 按完整 Style 表检查;隐藏/空数据的 Symbol 与 Annotation Symbol 一律保守保护。production 还核对层表数量与 renderLayers 数量,不一致保持原 transition;空表也保守保留。
这是 Shared Core 内部实现优化,无 Android/iOS 分叉,不新增控制 API、不禁止正常 Full Placement、不修改原始 controller。其余23项 Gate、source/cache/scene 条件全部继续生效。新增 Symbol 尚未进入旧表时,immutable layers 的 non-camera signature 不相等,且 Annotation pending 单独阻止 Fast;Full 更新后表重新包含 Symbol。未取得新层表前不得只凭这个 helper 放行。
反例边界:同场只要存在一个隐藏 Symbol、空 Symbol、C Annotation Symbol,仍使用原 controller transition;缺层表、source mutation、未完成 tile layout、image/glyph 更新、camera/viewport、style reload、debug/context 问题都不能被本修复抵消。未来若引入新的 Placement-capable layer 类型,应先扩展结构检测及测试;当前源码仅 Symbol 的 LayerTypeInfo 使用 CrossTileIndex::Required。
## 6. 实施结果、验证方案和限制
- IMPLEMENTED:缺源明确诊断、无 Symbol 场景的 #13 限定检查;3个非 Jest C++ 用例源码;独立4场景及 UI/Intent、真实 Native 快照与操作前后自动采样;中文文档和追加记录。
- 保留 fail-closed:CustomMVTVector、未知 revision、缺源;非粒子 C;所有 Symbol R Build/Placement 保护。
- NOT RUN:Native/Kotlin 编译、C++ 测试、Android build/设备/GPU/Fast 命中、像素对照、性能、iOS Metal 设备验证。
- 保留:正式 STEP-113 五个 PBF 场景实现原样、STEP-102 fixture 原样、STEP-110/111 未跟踪文件原样。新增菜单明确标识独立 smoke,不冒充63/113层等价。
验收步骤和命令见 `docs/60_测试/STEP-114_独立FastSmoke场景与RDC验收_v1.md`;Native 定向命令也直接写在本轮完成报告。baseline 必须取得真实 Native Full>0、Fast 增长、CacheBuild>0、Reuse 增长、completed fast-rendered、24 PASS/knownMask 完整/decisionMismatch=0;否则维持 PARTIAL,不能标 Fast Path 已修复。
Source/Image/Camera/缓存禁用/Reload 的反向证据需保存事件前/提交后/100/500/1500ms 快照,必要时以更早的精确帧复核;未捕获瞬间 gate 不能用计数猜测。后台/恢复/Teardown、Symbol/Annotation 与原 PBF 回归、双平台共享源码独立检查仍为 RDC/用户待执行项。

View File

@@ -0,0 +1,73 @@
# STEP-116 STEP-113 原 PBF Fast Path 阻塞安全修复审查 v1
日期:2026-10-11(JST)。结论:**部分实现(PARTIAL);空 Annotation 生命周期受控修复已落源码,缺源和 Symbol R 准入继续 BLOCKED。没有新的设备 Fast 证据。**
## 1. 当前事实与范围
仓库 `/Users/tei/dev/MapLibreParticleLab`,`particle/main@e2d202b560ad0165101546597c43f2ef0ccf4542`,Darwin 24.6.0 / x86_64。起点 tracked clean,只有 STEP-110/111 和本次 STEP-116 任务书未跟踪。版本仍 `13.6.1-particle.23`,不能用版本号相同推断 APK 源码相同。Integration main `80667d6c343411d03a760425438e3d292f452118` 为用户提供的 RDC 核验事实,本轮只读相关规范,没有 fetch 或跨仓写入。
已读取 AGENTS、PARTICLE_BASELINE、项目规范全部八文件、总则 README 和四份有效总则、STEP-116 任务及 STEP-112/113/114/115 当前记录/报告和相关源码;Integration AGENTS、README、WORKFLOW、TEAM_MODEL、contracts、rfc、adr、instructions、status、compatibility 只读。本轮不使用子代理,不修改公开头文件、公开 API/ABI、Host/RN/JNI 控制协议、NavSea 或公网 Style。
## 2. 缺源 R:证据成立,空依赖准入不实施
保留文件 `artifacts/maplibre-particle/android-validation/step113/source-change-after-full.json`,SHA-256:
`24a06fa1cb072a628c650723c58a880895ec656f4972fa55d3cb064bfb2aabda`
Native 原层 index=2、ID=`tide-spots`、type=`symbol`、source=`tide`。R[0,63) 依赖 `mapple/navsea_delivery/tide`。Native 实际 Source 表是 `mapple:raster`、`navsea_delivery:vector`、`org.maplibre.annotations:annotations`、`rn-weather-fields:raster-array`、`step113-source-probe:geojson`;没有 tide。该文件属于旧 SOURCE_CHANGE 完整窗口,不冒充本次 STATIC_RC 实测。原记录中 fallback=unknown-dependency;当前 STEP-114 代码已提前报 missing-source。
`RenderOrchestrator::retainedSegmentSourceState` 在查 revision 前先检查缺源及 CustomMVTVector;普通 Vector 不走 CustomMVTVector 拒绝分支。`renderSegmentDependencies` 不因隐藏或当前桶为空删依赖。`RenderSegmentRevisionBook::revisionFor` 对未登记来源返回空值,保留删除墓碑用于同名重建;墓碑存在不能代替当前 Source 的 readiness。
源码链路审查:
| 链路 | 当前实现与限制 |
|---|---|
| 零贡献候选 | createRenderTree 的 source/layer 匹配循环不会给缺源层生成 LayerRenderItem,updateList 默认 false;这支持“本轮没有对应新 RenderItem”,不等于全部旧 GPU 对象已完成禁用的证书 |
| 旧 GPU 生命周期 | RenderLayer::markLayerRenderable 调用 activateLayerGroup 提交 ChangeRequest;Symbol 还处理 collisionTileLayerGroup,layerRemoved 停用两类 group。CPU 状态、待处理 requests、旧 buckets 与实际执行时点需要一起核验;无 source 的循环边界也不能只靠 bool 推断 |
| Source 创建/删除/同名恢复 | createRenderTree 的 sourceDiff 递增全局内容及对应 source revision;prepare 输入的 immutable sources 身份变化使 NonCameraSignature 不匹配,必须先 Full |
| 同名更新/异步完成 | changed sourceDiff、onTileChanged → TileMutation → 内容/source revision + observer invalidate;feature-state 同样走 source 变更,不能按 HTTP 下载或 loaded 时间代替 |
| 全局输入 | layerDiff / 层序、imageDiff、Glyph URL/fontFaces、onGlyphsLoaded、Sprite/ImageMissing、context/style cleanup 保留原失效链 |
因此本轮**没有把 missing-source 当 empty**,没有登记虚构 tide revision,没有补假源或删除 tide-spots。后续需要“该 R 在成功 Full 中实际参与 GPU 绘制的 group/碰撞 group 已排除、无待应用资源变化”的有界证据,以及 source 出现/恢复后下一次尝试必定 Full 的回归。当前仅 Source 表缺失不足以承担这项新准入契约。服务器正式 Style 缺源仍是 BLOCKED。
本轮只在现有诊断 JSON 的每段增加可选 `missingSourceIds`,由同次 snapshot 的依赖和 Source 表计算,包含缺失来源的具体 ID。查询不写 revision、不构造缓存、不修改首错和 24 Gate。
## 3. 空 Annotation C:局部修复与保护
旧 STATIC_RC Native 66 层:原63层 + weather-color + raster-particle + 自动 points Symbol。C 从 rn-weather-color 开始,普通 Symbol 不支持 particle dynamic range,阻挡 #22,并影响聚合 #23。SOURCE_CHANGE 的额外 circle 也必须保留。
仅在 Step113PbfGateScene 的 STATIC_RC 第二次 Style 加载完成时执行一次空层调整:
1. 生产基础 ID 不含 points,生产Sources不得占用org.maplibre.annotations;SDK `map.annotations` 必须为空;实际层必须是 SymbolLayer、sourceId 为 org.maplibre.annotations,内部 Source 必须存在。来源由本次原始JSON无内部ID、Native加载路径与实际层共同核对,避免把生产同名Source误当空SDK来源。
2. 使用 `Style.removeLayer(Layer)` 对象重载,保留可重加的 wrapper;Source 保留,原63层定义/顺序及生产 Sources 原样。
3. 记录 `emptyBuiltinAnnotationLayerRemoved`、实例数0、Source保留、移除层ID/作用范围,保存 productionSources;poll/export 继续使用真实 Native 计数。静态预期正常 STATIC_RC 65 层,不能当实际计数。
4. 不周期删除任何层。后续普通 Style.addLayer 保持原行为,native immutable layer/signature/topology 变化继续 Full;Style 更换后不对新 Style 再做移除。
5. stop 在相同 Style 且该 ID 仍缺失时重加原 wrapper,已有同名层不覆盖。Style 身份改变或 Annotation/points 重新出现时,3秒 poll 撤销场景验收条件并报 BLOCKED;poll 是报告保护,不承担恢复可见内容的时序保证。
关键修复位于内部 `src/mln/annotation/annotation_manager.cpp/.hpp`:原 updateStyle 只在 Source 不存在时创建 points,Source 保留而层被删除会让后续 Marker 无绘制层。提取私有 ensurePointLayer,独立检查 Source 和层;首次添加 SymbolAnnotation、更新已有 SymbolAnnotation、Style loaded 三个入口修复缺失层。更新没有 geometry/icon 变化时也把 dirty 置真。已有同名 Layer 不被覆盖。
时序:Map::addAnnotation / updateAnnotation → AnnotationManager 修复 Style layer vector → Map::Impl::onUpdate 捕获新的 immutable layers → Fast NonCameraSignature/annotationMutation 拒绝旧树 → Full 执行正常 Annotation 渲染。恢复不依赖3秒poll,也没有在 renderer thread 的 updateData 内重写 Style。Shape Annotation 原接口/图层仍按原逻辑处理,碰撞、Fade、Glyph/Image 和 C 判断完全保留。
风险:这是 Shared Core 内部生命周期修复,所有后端共用;需 RDC 编译并跑真实图像用例,确认无锁/重入问题及旧 Annotation 行为回归。用户主动提供同名层时尊重其语义;若其配置本身无法绘制,该情况不被当作 Test App 空层安全条件。恢复失败在日志保留,不写成功。
## 4. Symbol R:首次 Build 仍 BLOCKED
已读 PlacementController、Placement::placeLayers/commit/symbolFadeChange、RenderTreeImpl::prepare、SymbolBucket::updateVertices、renderer Full 准入和 scene 聚合。
| Continuous Full 分支 | 当前 Symbol R gate |
|---|---|
| placement 不 recent | placementChanged=true;placeLayers 后 commit、setPlacement,changed 本身阻断 |
| placement recent | setPlacementStale;hasTransitions 返回 true,阻断 |
| transitions 关闭 | placementIsRecent=false,Full changed=true;symbolFadeChange 返回1,不是 settled=0 |
RenderTreeImpl::prepare 在 bucket updateVertices 中更新 opacity/dynamic attributes,并置 uploaded/dynamicUploaded/placementChangesUploaded 等状态。Placement commit 只提交 CPU placement/opacity 结果,不证明当前 R 的最终 GPU 输入一致。per-R `renderSegmentPlacementGatePassed` 和 `retainedSegmentIsStable`,全局 retainedPlacementStable / retainedSupported / preparedSceneSupported 均保留。隐藏/缺源/当前 placementData 为空的 Symbol 仍结构上具备 Placement 能力。
**需要的额外设计证据**:成功 Full 对应的 GPU 上传/绘制完成代次、R 使用的 bucket/tiles/跨瓦片ID与排序、跨Source碰撞与所有参与碰撞层的 Placement结果、text/icon opacity和dynamic vertices、atlas/glyph/image代次、Style/Source/layer identities与revision、camera/projection/viewport/pixelRatio、uniform fade及debug/3D条件、待处理requests/异步变更纪元。证书必须绑定一次成功 Cache Build,变化即 invalid/Full,并对 GL/Metal 验证等价及 shadow。现有代码没有完整证书,本轮不做跨层重写,不用毫秒阈值、loaded、无手势或counter装配代替。
## 5. 测试源码与结论边界
新增四个 `Step116Safety.*` C++ 用例:缺源/Source加入未loaded/异步revision/删除墓碑和恢复;JSON缺源审计与Placement并存、查询无副作用;Symbol两个Continuous分支及关闭transition保护、无变化/Source与全局资源revision;Annotation/层序/disable/reload多失败 shadow 全集。revision用例验证的是账本契约,不伪造实际 Glyph/Tile 回调或 GPU完成。
新增三个 `Annotations.Step116*` 用例:空points移除后真实添加在render前恢复并比对既有point_annotation图像;已有Marker no-op update恢复且出图;已有同名层不覆盖、reload恢复且出图。这些是测试源码,全部 NOT RUN;设备的annotation/Style、async mutation、GL/Metal回退还须独立验收。
本轮实现后预期消除原 STATIC_RC 的空内部 C Symbol;无法承诺 #23 清除,也无法承诺 R Build/Fast>0。旧快照、AST或diff检查均不构成新制品安全、像素或性能 PASS。

View File

@@ -0,0 +1,137 @@
# STEP-117 PBF R 首次缓存 Build 安全证据 v1
日期:2026-10-11(JST)。结论:**安全准入 BLOCKED;本轮保持 missing-source 与 Symbol placement fail-closed,交付七项非 Jest C++ 回归源码。没有实现 R 零贡献准入或 Symbol 结果证书,没有新的 Native/Android PASS。**
## 1. 工作区与证据等级
Mac.lan / Darwin 24.6.0 / x86_64,仓库 `/Users/tei/dev/MapLibreParticleLab`,`particle/main@43e5f5b91598b9c3d3f1c788052d4bbd2d616668`。起点 tracked clean;STEP110/111 与 STEP117 任务书未跟踪。实际模型 GPT-6.1-Sol(gpt-6.1-sol)/ high,由当前 rollout `01a12912-9a0c-7712-9624-793f04b3f681` 的 `turn_context.model/effort` 核实。Integration 本地只读 HEAD 为 `80667d6c343411d03a760425438e3d292f452118`,没有跨仓写入或远端操作。Android VERSION 仍 `13.6.1-particle.23`。
已读根 AGENTS、PARTICLE_BASELINE、全部项目规范、总则 README 与四份有效总则、STEP117 任务、STEP112–116 关键任务/调查/Step/完成报告/验收文件;Integration AGENTS、instructions、README、WORKFLOW、TEAM_MODEL、contracts/rfc/adr/status/compatibility。本报告用以下等级区分事实:
- **源码事实**:当前 HEAD 的控制流和所有权;不等同运行安全证明。
- **保留实测**:STEP116 原设备 JSON 与报告;不是 STEP117 新二进制。
- **测试源码**:本轮已写、全部未编译和未运行。
- **待证明设计**:尚未实现的证书、影子比较与平台验证。
## 2. 原 120 帧重新核对
原文件:`artifacts/maplibre-particle/android-validation/step113-retest-20261011_121308/static_rc/native-full.json`;274,693 字节;SHA-256:`cd491d2acca97119f1d4f2d6f77eb7b229c17b90d4be1181a59202308429b219`。本轮只读解析完整 JSON,逐帧复算 failMask,不修改或纳入 Git。
实测源码 `c48a72a9d492ea0d05e4d58464f9643453158d50`,APK SHA `a8b7f4e0f8e026bde8835e18a04876062be84cce3a3749a511cb08605d9f8a35`,设备 FZG1-2 / `192.168.200.139:5555`。STEP116 报告记录安装 SHA 相同、90.008 秒 FastDelta=0 / FullDelta=920。本轮未接设备。
原正式 63 基础层保留;Native 65 层,R[0,63)、C[63,65);C 仅 `rn-weather-color`、`rn-weather-particles`。正式 Style fingerprint 为 `e0f9c2d1df38ae5d5953b561c52efe2f8590336de80b988e1d2935580d30b996`。生产 sources JSON 只有 mapple/raster 与 navsea_delivery/vector;Native 另有 org.maplibre.annotations/annotations、rn-weather-fields/raster-array。没有 tide。R index=2 的 `tide-spots` 为可见 Symbol,source/source-layer 均为 tide。
| 实测项 | 复核结果 |
|---|---|
| 120 帧范围 | sequence 5631–5750;119 completed Full / 0 Fast / 1 pending |
| 24 Gate | 120/120 knownMask=0xffffff、decisionConsistent=true;decisionMismatch=0 |
| #3 / #4 / #23 | 各失败120帧 |
| #12 / #13 | 各失败105帧 |
| #22 | 最近窗口失败0;生命周期累计6,不称启动全程零失败 |
| R cache | Build=0 / Reuse=0;textureCreate=0 / compositeCreate=0;fallback=missing-source |
| R dependency | mapple、navsea_delivery、tide;missingSourceIds=[tide];placementGatePassed=false |
| pre Placement 状态 | 105帧 false/非零/true;15帧 true/0/false(changed/fade/transition) |
| post Placement 状态 | 104帧 false/非零/true;15帧 true/0/false;pending 的 post=null |
| fade 范围 | pre/post 均为0至0.9914085268974304 |
这解释了一个容易误判的现象:15帧的 #12/#13 通过,并不建立 R cache;这些帧仍有 #3/#4/#23,且上一完成树的 changed=true。Fast #13 使用当前 controller transition,R 首建使用当前 Full 的三条件,二者取样点与职责不同。不得拼接 pre 与另一帧 post 来造出通过状态。
## 3. 缺 tide:新 RenderItem 为零与 GPU 零贡献的区别
### 3.1 Style 与 Source 创建
`src/mln/style/style_impl.cpp::parse/addSource/addLayer` 先加入 parser.sources,再加入 layers。addLayer 在 source 存在时检查兼容性;source 不存在时仍保留原层(源码 TODO verify source)。这正是原 Style 能保留 tide-spots 而没有 tide 的路径。removeSource 会拒绝仍被 Layer 引用的 source;同名恢复不能依靠违法删除调用,合法整份 Style 更换及后续创建也必须覆盖。
`RenderSource::create` 根据真实 SourceType 创建 RenderVectorSource、RenderRasterSource、RenderGeoJSONSource 等。原 navsea_delivery 是普通 Vector,不能将它归因为 CustomMVTVector;后者的未知 revision 准入限制继续保留。
### 3.2 Full 的注册、停用和绘制时序
| 环节 | 当前源码事实 | 能支持的结论 / 不能支持的结论 |
|---|---|---|
| createRenderTree source/layer 匹配 | `render_orchestrator.cpp` 在已有 sourceImpls 的外循环中收集 LayerRenderItem;缺源层不匹配,updateList 初始false | 本轮缺源层无新 RenderItem;不能单凭此证明旧 group 已移出 registry |
| renderability 更新 | 每次 source 更新后比较 isLayerRenderable 与 updateList,调用 markLayerRenderable | 有 source 的正常 Full 能排队停用;所有 sources 为空时此循环不执行,不能推广为任意状态的清理保证 |
| RenderLayer::markLayerRenderable | 更新 CPU bool;activateLayerGroup 创建 Add/RemoveLayerGroupRequest | bool=false 不是 request 已执行的证明;该方法没有清除 drawables、renderTilesOwner、placementData |
| RenderSymbolLayer 停用 | 同时为普通 group 与 collisionTileLayerGroup 排队停用 | 需覆盖两类 group;不能仅数普通 RenderItems |
| layerRemoved | RenderLayer 调用虚方法 removeAllDrawables;Symbol 的实现清除两类 group 的 drawable,然后停用 | 删除 Layer 与缺源导致不 renderable 的生命周期不同,不能互相替代 |
| processChanges | 移动 pendingChanges 后逐个 execute,RemoveLayerGroupRequest 删除对应 registry entry | 删除 registry 不等于销毁 Layer 所持 group/bucket/atlas;指针仍可能持有旧数据 |
| Renderer Full | tree.prepare → source/layer upload → updateLayers → processChanges → cache评估 → tweakers/group upload → Build/live draw → present/endFrame | Cache 评估位于处理 changes 之后,但此前没有生成覆盖旧数据与成功提交的零贡献证书 |
| Cache Build | R 使用 orchestrator 的 layer group range 双 pass;没有只遍历新 LayerRenderItem | 缺源层无 RenderItem不等于range里无旧group;需要独立registry与层身份审计 |
本轮实际读取 `render_layer.hpp/.cpp`、`render_symbol_layer.hpp/.cpp`、`change_request.cpp`、`render_orchestrator.hpp/.cpp`、`render_tree.hpp`、`renderer.cpp`、`renderer_impl.cpp`、`retained_underlay_cache.hpp/.cpp`。当前 src/include 中未找到名为 ResourceManager 的类;实际资源所有者是 gfx Context/backend、RetainedUnderlayCache 的 offscreen/composite、LayerGroup/Drawable 与 RenderTile bucket/atlas,以及 DynamicTextureAtlas/ImageManager/GlyphManager。GL 的 `Texture2DPool`(`src/mln/gl/resource_pool.hpp`)管理纹理复用,不提供“某个缺源层本帧零绘制”的语义证书。
**限定的正向事实**:新加载且从未获得 source 的 tide-spots,没有匹配 RenderItem;新 RenderLayer 默认 isRenderable=false。**仍缺的事实**:原设备 JSON 没有 per-layer registry identity、两类 group 的已执行停用、旧 drawables/tiles/placementData 清空和成功 Full 提交代次。不能从其他 sources 已 Loaded、cache textureCreate=0 或 pending-changes Gate=PASS 推出这些条件全部成立。尤其 task 要求“无旧 Renderable 数据”,单纯停用并不满足它。
### 3.3 失效链已存在,但不存在缺源的受控依赖状态
`retainedSegmentSourceState` 在读 revision 前要求每个 sourceId 存在;缺失即返回 ready=false/stable=false/revision=0/reason=missing-source。renderSegmentDependencies 从完整层表收集 source 和 Symbol 全局前提,不因隐藏/空桶删依赖。RevisionBook 没有为从未出现的 tide 登记;删除墓碑可保留,但不代表当前 source 存在或 loaded。
`sourceDiff.added/removed/changed` 增加 content revision 与对应 source revision,先删/建 RenderSource,再 update。同名同类型但 immutable Impl 改变属于 changed。Fast 的 non-camera signature 比较 immutable sources 身份,下一帧在 createRenderTree 之前拒绝旧输入;已有 PreparedTree 在 Full 创建前释放。异步 onTileChanged 增加全局 content 和局部 source revision并 invalidate。Image/Glyph/Style/Context 的全局 epoch、层身份 fingerprint、camera/viewport key也各自保留。
因此“同名 source 出现后可拒绝旧 Fast”的机制已有源码依据;**不能由此反推不存在的 source 可合法登记成 loaded/stable/零 revision**。新的缺源状态需要绑定 source absent identity、完整层集合、应用后 registry/旧对象状态和成功绘制代次;source 出现、替换、异步 layout、context改变必须撤销它。当前没有这些存储和核验,不实施空依赖准入。
## 4. Symbol 首次 Build 的安全根因
### 4.1 两个 Full 分支和第二重 scene 锁
`PlacementController::placementIsRecent` 按 commitTime+updatePeriod 判断;transitions关闭则总返回false。Continuous `createRenderTree`:
1. 不 recent:placementChanged=true;Placement::create → placeLayers → commit;setPlacement 清 stale,CrossTileSymbolIndex prune 与 source fading 更新。
2. recent:placementChanged=false;setPlacementStale;controller.hasTransitions 在 enabled 时优先因 stale 返回true。
`renderSegmentPlacementGatePassed` 对结构性 Symbol 要求 `!changed && fade==0 && !controllerTransition`。每个分支至少违反一项。关闭 transitions 时 Full 为 true/1/false;duration=0但enable=true时fade=1,recent分支仍stale。等待更久、改300/30ms阈值或关闭fade无法提供证书。
`renderer_impl.cpp` 还有独立保护:retainedPlacementStable、retainedStaticStable、retainedSupported,以及 `particlePreparedSceneSupported=retainedSupported && fastContinuousSupported`。只放宽 per-R Gate不会使 #23 安全通过;只放宽 #13也不能自举 Build。Fast 成功后 prepareParticleFastFrame 才重置树的 changed/fade;调用它来准备第一次 Build会循环假定尚不存在的安全证据。
### 4.2 CPU 结果不等于最终 GPU 输入
读 `placement.cpp::commit/updateLayerBuckets/updateBucketOpacities/updateBucketDynamicAttributeData`、CrossTileSymbolIndex、SymbolBucket::updateVertices、SymbolLayerTweaker 与 DrawableAtlasesTweaker,以及 GL/Metal symbol shader:
- commit 以每个 crossTileID 的 text/icon placed 状态更新 opacity;显隐未变可继承旧 fadeStartTime。controller transition=false不等于所有桶在GPU中已上传或与旧帧等价。
- RenderTreeImpl::prepare 更新 bucket opacity/dynamic data;SymbolBucket 将 uploaded、placementChangesUploaded、dynamicUploaded 标记为false。SymbolBucket::upload 的标记不是成功GPU Drawn R的证书。
- opacity处理 duplicate、默认allow-overlap、justReloaded、variable anchors、orientation、text/icon配对;沿线重投影与camera相关。碰撞为全局 Placement,并可能跨 source/段产生依赖。
- shader实际使用 `clamp(baseOpacity ± symbolFadeChange,0,1)`,再乘paint opacity。fade=0既可能为全透明新符号,也可能为已显示符号;fade非零也可能因clamp不再改变像素。用==0或>=1替代逐输入等价均不足。
- LayerTweaker 还使用tile/label/coord矩阵、size binders、paint插值、纹理尺寸与shader变体;DrawableAtlasesTweaker绑定glyph/icon atlas和sampler。
- retainedRangeContentFingerprint 只散列 layer.baseImpl 指针与index;它不包含 placement结果、opacity buffer、bucket身份、atlas纹理、uniform最终值。Source revision也不替代这些内容。
- Build成功计数来自双pass编码无捕获异常、finishBuild与markBuildSucceeded;随后主buffer present提交。现有实现没有完整的 per-R GPU输入证书或设备completion fence记录。将编码成功说成GPU执行完毕属于证据越界。
### 4.3 可继续的设计边界与反例
| 候选条件 | 反例 / 缺少的证明 |
|---|---|
| source loaded、无手势、静止90秒 | changed/stale循环继续;异步glyph/tile/image可改变结果 |
| Placement placed布尔相同 | opacity方向/基值、duplicate、anchor/orientation或dynamic vertices可不同 |
| fade=0且controller=false | 实测15帧changed=true,Build仍拒绝;刚出现符号可能base opacity=0 |
| 当前 RenderItems 为空 | 旧group可能仍在registry或旧数据留在RenderLayer;pending请求可能未执行 |
| layer fingerprint/source revision相同 | 不能区分同桶的dynamic opacity/atlas/uniform变化 |
| 两张末态截图看起来相同 | 未捕获变更瞬间、碰撞受害段及GPU提交代次,不能建立持续复用不变量 |
待证明证书至少应覆盖:R层身份/层序和drawable集合;bucket/tiles/crossTileID及跨段碰撞结果;text/icon显隐、opacity方向/基值、dynamic vertices、沿线/variable placement;paint/layout revision;glyph/image/atlas/shader/uniform版本;projection/camera/viewport/pixelRatio;context generation;已应用ChangeRequests及异步content epoch。只能在正常Full完成prepare/upload/tweakers、真实Build成功提交且资源一致时绑定,任何不一致先Full+invalidate/rebuild。不能只加入一个“结果稳定”bool。
这涉及 Placement→Bucket→Drawable/Atlas→Renderer提交生命周期的跨层实现,当前没有足够设备/图像证据保证安全。本轮停止该准入实现,不删或强制置假 placementChanged、symbolFadeChange、controllerTransition,不改24Gate。
## 5. 七项测试源码及真实覆盖限制
两个既有 test/CMakeLists.txt 已登记的测试文件内新增 `Step117Safety.*`,无需改构建表,全部 NOT RUN:
| 用例 | 实际测试目标 | 不覆盖的事实 |
|---|---|---|
| RealPlacementControllerHasNoFirstSymbolBuildWindow | 用实际Controller/Placement::create/placeLayers/commit执行120个逻辑时刻;两个分支均fallback,Build/Reuse为0 | 空桶生命周期最小复现,无原PBF碰撞/GPU |
| RealPlacementDisabledOrZeroDurationIsNotAResultCertificate | 实际enable=false与duration=0/enable=true,fade=1、stale行为继续保护 | 没有禁用生产fade |
| MissingAndSameNameRestorationCannotReuseAnOldCache | 缺源→创建→状态机构建/复用→删除墓碑→同名恢复必须新Build | 模拟status与markBuildSucceeded仅为cache状态机,无GPU成功 |
| CameraStyleAnnotationAndContextRequireRebuildBeforeReuse | camera/viewport/content/topology/config变化经过实际revision tracker和cache,使旧cache无效且#3/#4失败 | epoch输入是模拟,未触发真实Annotation/camera设备事件 |
| RealMissingSourceTreeHasNoRenderItemButStillFailsClosed | 真实createRenderTree:缺源Symbol零RenderItem/零group仍missing-source;实际sourceDiff创建、同名替换、移除;layer fingerprint保持不变而revision变化 | 使用step117-source无数据GeoJSON测试对象,未改生产tide;无旧GPU对象场景 |
| QueuedGroupRemovalIsNotAppliedUntilProcessChanges | 两个测试group实际进入registry,真实Remove请求入队前后遍历差异与pending变化 | 测试group代表两类生命周期,不是实际Symbol collision group或GPU Draw |
| RealAsyncCallbacksAndContextStyleCleanupAdvanceContentEpoch | 实际Tile/Glyph/Image observer分派、markContextLost/clearData推进content epoch;旧content输入单独命中#5 | 事件在测试线程注入,无实际网络并发/完整Renderer/设备 |
未新增Jest,未编译或运行这些测试。生产 src/include、所有Gate/计数、Android场景和63层零修改。既有Annotations.Step116图像用例保留;新测试不代替glyph/tile并发、跨段collision、old GPU drawable清理或Full/Fast像素验证。
## 6. 具体阻塞与协调需求
| 编号 | 无法继续的状态 | 应由谁处理 / 需要的产出 |
|---|---|---|
| B1 缺源零贡献 | 当前只有absent Source与新RenderItem缺席;无应用后双group/旧drawable数据/提交世代证书;revision=0不能登记为有效 | Shared Core制定absent依赖内部状态及销毁/禁用证书;Android/Metal分别验证初始缺源、曾有旧数据、合法Style替换、同名恢复 |
| B2 Symbol首次Build | 重算changed、recent stale;所有可达Full状态都不满足现有准入;无最终GPU结果等价/时间不变证书 | Shared Core提供跨层结果证书设计与影子比较实现任务;先影子不放行,证明后才讨论受控准入;粒子负责人组织双平台审查 |
| B3 正式tide语义 | 生产Style宣告tide-spots但sources不含tide;本仓库不能自行补业务数据、改服务器或删层 | Integration协调NavSea/Style所有者明确是否属于合法宿主动态注入、何时提供真实source及身份;改变公共缺源语义才走RFC/ADR,纯内部优化不强制新Contract |
| B4 独立验证 | 本轮没有Native编译、测试或设备新证据;STEP116历史Mac配置缺libuv | RDC复核当前环境、执行定向测试,首错保留;用户明确授权后由RDC/普通Terminal集中Android验收 |
B1/B2应形成具体设计/实现任务,不通过业务开关、假tide、删Symbol、改24Gate或STEP114烟测绕过。Integration是协调入口,本轮不向他人发消息、不写跨仓文件。当前代码阶段止于测试和证据交付;详见STEP117验收计划。原目标 Build>0、Reuse>0、FastDelta>0仍未完成。

View File

@@ -0,0 +1,77 @@
# STEP-119 Symbol结果证书与R缓存Build实现依据 v1
日期:2026-10-11(JST)。状态:IMPLEMENTED_WAITING_ANDROID;Shared Core已有实质实现,尚无本轮编译、GPU、Android或视觉验收结论。
## 1. 基线与原证据
仓库 `/Users/tei/dev/MapLibreParticleLab`,Mac.lan / Darwin24.6.0 / x86_64,particle/main@869930046b11dbeb3b147e3be952a2e847316292。Native版本13.6.1-particle.23。Integration本地只读HEAD=80667d6c343411d03a760425438e3d292f452118;已读取AGENTS、instructions、README、WORKFLOW、TEAM_MODEL和contracts/rfc/adr/status/compatibility,未写跨仓文件。纯Renderer内部优化,不新增公共接口、所有权或错误协议;C-001/C-002和Core-owned timeline/playback保持。
STEP117揭示:Continuous重新计算令placementChanged=true;recent复用设置stale,hasTransitions=true。旧Symbol gate要求changed=false、fade=0、transition=false,两个分支都不能自举。global retainedPlacementStable、retainedStaticStable、retainedSupported、preparedSceneSupported还有独立阻断。
STEP118保留原63基础层manifest,仅测试副本排除tide-spots:62基础层+2天气层,Native64,R[0,62)、C[62,64),missingSourceIds=[],static-placement、Build0/Reuse0。旧设备90.00806697399821秒FastDelta0/FullDelta942、decisionMismatch0,不是本轮结果。
旧APK源码3051196054c7b69815c298040dae61d26499d9f2,SHA=44afcfabe0cf4f1ec93f35899241bd73093aa6db4b5cb82ca2120dd531b8f3e9。旧result.json SHA=d3d20fc35bbd17e9f0ea0dd014ca4af8e0c5c5284d9f0c009bc20981d876ff10;screen.png SHA=c8b57a8432626c9209ca9a614a747728d4256bd09273b28c26a793a75a6d44d4。本轮只读解析/核对,文件未修改。
## 2. 实际渲染生命周期
1. 正常Full先createRenderTree:Source/style差分、跨瓦片ID匹配、全场Placement碰撞与commit继续使用原实现,没有按R段拆碰撞或冻结新结果。
2. RenderTreeImpl::prepare调用Placement::updateLayerBuckets,继续处理text/icon、重复符号、variable anchors、方向、opacity、动态顶点、沿线投影和排序。
3. Renderer依次上传source/layer/atlas,updateLayers创建或更新Drawable,processChanges实际应用注册/移除队列。
4. 所有正常Full LayerGroup先runTweakers并upload。SymbolLayerTweaker记录**同一份传给GPU的**evaluated props、drawable/tile UBO字段;记录frameCount,并确认evaluated/drawable/tile UBO存在和consolidated容量,未执行、出错、缺buffer或错误帧不能补证。
5. 此后采集每R的原生Symbol绘制输入,与上一成功提交Full的证据精确比较。符合就准入首次Build,不依赖已有缓存。
6. Build保持原双pass和原始全局ordinal;finishBuild后再检查输入未改变。资源/绘制错误走Fallback;实际主缓冲按原R/C层序composite或live绘制。
7. present和endFrame成功返回之后再次检查输入、epoch及错误代次,才记录完成Full/Build证书。它证明成功编码和提交,不宣称GPU fence完成或像素等价已经测量。
8. 后续一次正常Full Reuse成功提交后,才允许Fast。Fast使用相同证书版本、content/topology/config、相机、backend viewport、pixelRatio和crossSourceCollisions,并继续原24项Gate。
普通Full替换旧prepared tree时只销毁引用式RenderItem;证据拥有复制的字节和必要资源引用,跨Full保留。style/context/显式prepared-tree reset仍清证书。symbolCompletedSignature独立保存上一Full的非相机输入,防止glyphURL/fontFaces/placement设置等在旧树销毁后丢失保护。
## 3. 结果证据包含什么
| 实际输入 | 采集与保护 |
|---|---|
| Symbol uniforms | 从SymbolLayerTweaker实际提交的CPU参数复制;包含paint颜色/透明度/halo、tile/label/coord矩阵、尺寸/插值、gamma、text/icon/halo标志;忽略SymbolDrawableUBO末尾12字节非shader padding |
| 顶点与实例属性 | 对实际Drawable绑定的VertexAttributeArray/InstanceAttributeArray记录属性索引、类型、stride、offset、vertexOffset和共享原始字节;包含静态布局、动态位置/角度、opacity、data-driven paint、排序实例;共享属性必须已有GPU背板,索引必须已上传且非dirty |
| 已释放的不可变buffer | 只在producer已release且存在GPU backing时用保持存活的对象/背板身份;不能读取的可变输入拒绝。不是把任意空数据当相等 |
| text/icon/索引与排序 | 原生SymbolBucket对应text/icon/sdfIcon buffer、segment范围、instance/vertex/index长度、sortKey;两条路径均记录实际三角索引字节,release后必须保持不可变索引对象与GPU背板身份;instancing固定quad和segment仍核对 |
| fade | 检查每个实际opacity顶点的packed值及当帧fade;必须在放置目标方向达到shader clamp终点,并且真实Placement fade已结束;非有限值、负值、非整数或越界packed值拒绝 |
| atlas/texture/sampler | 保持atlas、glyph/icon及实际绑定纹理/原生atlas tweaker身份,核对尺寸/format、上传状态和实际绑定一致。原生tweaker的sampler策略由固定bucket/layout和相同camera/无手势状态决定;glyph/icon像素原地更新由既有全局epoch保护 |
| shader | 独立provenance记录只在实际GL/Metal编译源码与原生Symbol源码完全相符、额外宏属于原生uniform白名单时登记;保留shader对象身份。名称相同的未知/替换shader不借用fade契约 |
| LayerGroup/层序 | processChanges之后验证Symbol组确实注册,隐藏层无旧注册组,collision额外组或陌生组拒绝;记录全局group总数、原始ordinal、组身份/层索引/数量/enable,以及Drawable实际混合方程/因子/颜色mask、剔除/深度/3D状态。全局depth range与跨段排序因此参与证据 |
| 版本与生命周期 | 精确结果revision、成功Build revision、成功Reuse状态;绑定content/topology/config、camera矩阵、viewport、pixelRatio、crossSourceCollisions;原update signature/SourceRevisionBook/Annotation Gate继续使用 |
每个结果字节缓冲上限16MiB,超限拒绝证书;没有提高公共容量或假装完整证明。资源引用避免地址ABA;比较对象是字节序列而不是散列。既有segment cache revision组合仍保留,但Fast还直接核对证书状态,不能靠组合散列碰撞取得证书;Full Reuse也要求candidateHasBuild精确版本,缺匹配Build时显式invalidate旧Cache,再走Build。
## 4. Placement与fade语义
没有修改placementIsRecent、setPlacementStale、原hasTransitions、commit/fadeStartTime、updatePeriod或placement计算频率。原hasTransitions仍用于正常renderer请求刷新。新增内部hasActiveSymbolFade仅表示实际fade;retainedRangeIsStable、retainedSegmentIsStable、retainedPlacementTransitionActive和Fast动态输入改用它。
GL三个原生Symbol vertex shader和Metal symbol.hpp三类shader均使用unpack后的base opacity加/减fadeChange并clamp到[0,1]。>=1在合法packed值上足以达到fade终点,但不能证明顶点、uniform、atlas或层序相同。因此没有单独用>=1放行,而是逐opacity检查+完整结果比较+提交代次。
重算仍令placementChanged=true,诊断继续如实记录;它不再被当成最终GPU输入改变。recent/stale仍请求刷新,不能单独代表正在fade。Fast rebase保留被证书确认的Full fade值,不把它清为0来取得准入。
## 5. 完整准入与失效
首次Build路径:已完成Full证据 → 下一Full同绑定同输入且已settled → Build编码成功 → present/endFrame及复核 → Build证书。Reuse/Fast路径:同版本缓存 → 正常Full Reuse成功提交 → 原24Gate全部通过 → Fast。preparedSceneSupported与global/perSegment稳定性均使用同一结果证明,不存在只改perSegment条件的旁路。
以下继续fail-closed:missing Source、CustomMVTVector未知revision、tile fading、未loaded style/sprite/image manager、paint/light transition、非粒子repaint、3D/debug/非continuous、不支持C、Annotation pending、相机/viewport/pixelRatio/非相机参数、异步content或pending changes、资源/编译/上传/绘制错误、未知placement-capable类型或shader(Symbol原生typeInfo指针必须匹配,名称相同不足以强制转换)、证据超限/不可读取。
Glyph requested/error/loaded、Tile error/changed、Symbol error新增或保持epoch推进;Image missing/remove/update、Source差分、Context reset、Style clear沿用原保护。捕获到的renderer/registry错误推进错误代次与全局content epoch;逃出上传/编码/present的异常由Renderer外围捕获,清prepared/证书、失效所有旧Cache后原样抛出;证书不能使用失败更新的旧Drawables。Build前和成功提交后都有epoch复核,证据观察期间的变化拒绝完成证书。
现有回调/registry操作运行在renderer调度线程;content revision是原有atomic。此优化不建立后台并发访问Drawable的通道,不改变callback线程合同。真正跨线程竞态仍需RDC平台实测。
## 6. 反例与保守限制
- 同placed布尔但variable anchor/角度/opacity不同:实际属性字节变化,revision递增,重新Full取证。
- controller不fade但尚未上传/陌生shader/atlas未绑定:complete=false,拒绝证书。
- glyph/icon纹理指针不变但内容更新:content epoch变化,旧Build/ReUse证书失配。
- Source同名替换、原63层缺tide:SourceRevisionBook/loaded/缺源保护仍拒绝,不能借空RenderItem作零贡献证明。
- 下方段删除group导致上方R ordinal/depth改变:全局group数量/ordinal证据变化,上方R不得复用旧结果。
- Build存在但没有成功Full Reuse:canFast=false;失败/未完成的Full不能刷新成功提交证据。
- Vulkan/WebGPU目前没有本轮原生shader provenance登记,Symbol结果证书保守拒绝;它们的普通Full渲染不变。本轮不宣称跨后端运行通过,也不以GL代替Metal验证。
- 16MiB上限、非常大的PBF、持续Glyph/Tile更新、不同native实例buffer生命周期均可能继续阻止Build;须保留真实结果修复,不能提高测试阈值制造PASS。
## 7. 测试与待证事实
新增13项Step119SymbolResults,位于已登记的两个test/renderer文件。覆盖真实Controller lifecycle+SymbolBucket opacity/dynamic buffer、packed clamp反例、variable位置/角度、camera/viewport/pixelRatio、CollisionGroups、真实uniform/wrapped tile编码、未知shader、失败提交、Annotation与24Gate、真实Tile/Glyph/Image/Context/Style回调、Source同名替换/缺失和跨段registry移除。
所有新增测试NOT COMPILED/NOT RUN。部分Build标记和字节payload是状态机fixture,绝不称真实GPU等价;现有CrossTileSymbolLayerIndex和Annotations图像测试必须由RDC一并验证。原PBF跨段collision、实际Glyph/Atlas延迟、Full/Fast图像、真实Build/Reuse/Fast和性能仍全部待独立验收。源码去除逻辑自举死锁,不承诺本机实机目标已实现。

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,161 @@
# STEP-110 R0 Placement 跨段归因与缓存安全边界只读调查 — Codex 任务 v1
## 一、任务性质与核心问题
**性质:Particle Native / Shared Core 专项,只读调查;不实现、不修复、不编译、不上机。**
用户要求粒子项目独立回答:
> Native 能否区分“R0 自己的 Placement 变化”和“其他 RenderSegment 引起的 Placement 变化”?如果能区分,是否足以在**不破坏 Symbol 碰撞、文字/图标位置及图层顺序**的条件下让未变化的 R0 复用缓存?
必须把以下不同概念分开审查:
1. **事件来源**:哪个 source/layer/segment 引发了 placement/repaint/revision;
2. **实际影响范围**:全局碰撞计算是否使 R0 的 Symbol 位置、可见性、fade 或绘制结果改变;
3. **缓存安全判据**:能否证明 R0 内容/Placement 仍与已缓存画面等价;
4. **当前实现**:上述信息目前是否已有、是否按 Segment 使用、还是仅采用全局保守 Gate。
禁止预设“全局 Placement 变化一定是 Bug”,也禁止以“R0 自己没有提交数据”直接推导“R0 视觉必然没有变化”。
## 二、仓库、版本与事实优先级
- 项目:`/Users/tei/dev/MapLibreParticleLab`,正式分支 `particle/main`、远端 `origin/particle/main`。
- 创建任务时只读确认:`particle/main` HEAD `2df504cdc0403bdad85d3f31f16fd54a8744abac`、干净工作树。执行时必须**重新读取 Git 实际状态**,不得把此 HEAD 当作永恒基线。
- NavSea 产品实机所用 APK 内 `assets/component-manifest.json` 记录:RN / Android Native AAR `13.6.1-particle.23`。V23 正式来源对应 `refs/tags/maplibre-particle/rn/13.6.1-particle.23`、GSRL Git commit `87f3d1c59a52581fb8339f0ea7d106b9f4b94fef`。
- **必须分别标明 V23 发布源码与当前 `particle/main` 的事实**。如当前 main 有未发行后续改动,不能以 main 代替 V23,不能以 V23 APK 推断未发行修复已经生效。
- 跨项目事实遵循 `D:\React\navsea-particle-integration\instructions\NAVSEA_PROJECT_INSTRUCTIONS.md` 及最新 COORD / RFC / ADR / Contract / Status;当前既有外部契约 C-001。此任务不新增/更改公共 API 或契约。
- 粒子项目根 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、`docs/00_项目总则/README.md` 与四份有效总则必须先读,按当前项目事实处理过时的历史环境/发布坐标。
- 必读既有证据:STEP-088、089、090、092、094、099、100、102、106、108、109;重点对比 STEP-090 早期缓存/快路成功条件、STEP-102 placement-free 分段隔离范围、STEP-106 T1/T2 Symbol 与 Annotation 边界的真实验收。
## 三、来自 NavSea 的问题证据(仅作为输入,不由本任务修改 NavSea)
FZG1-2 / Android 13 / V23 当前 APK,NavSea 采用**两段 R-C**:
- R0:RETAINED,31 层,`background` 至 `place-labels-land-ja`,包含有 Placement 能力的 Symbol;
- C1:CONTINUOUS,82 层,`rn-weather-color` 至 `chartDangerScanCenterLineLayer`,包含天气/粒子和其他业务层。
固定同一风场、同一 map 生命周期,连续约 15.24 秒,`mapReactTag=864`、`generation=4`、`configRevision=1`、`topologyRevision=8`、113 层均不变:
- 风场期间进程 CPU 样本均值约 98.6% / 99.1%,关闭天气后约 4%~8%;
- `fastPathHit Δ0`、`fastPathMiss Δ452`、`fullCpuFrame Δ453`;
- R0 `build Δ0`、`reuse Δ0`、`invalidation Δ453`、`fallbackCount Δ453`、texture/composite create `Δ0`;
- 两端 `valid=true`、`fallback=false`、`sourceReady=true`、`sourceStable=true`、`placementChanged=true`、`placementGatePassed=false`、`cache.supported=false`、`cacheState=fallback`、`lastInvalidationReason=static-placement`;
- `mainBufferExecutionMode=mixed`、`globalLiveFallbackReason=none`、`fastPathFallbackReason=cache-invalid`。
**这些是症状,不代表已证明 C1 导致 R0 Placement 改变。** 此外,历史较早的 V23 短时快照曾观察到 `placementChanged=false` 但 R0 记录仍为 `static-placement`,需要区分“当前门槛值”和“上次失效原因/诊断时序”,避免误读。
Mac 粒子团队如无法访问 Windows 原始快照,可依据上述冻结事实做 Native 只读审计;缺失证据须列为“需要后续验证”,不得自行编造 NavSea 图层或运行结果。
## 四、必须回答的调查问题
### Q1:Placement 的产生与传播
从真实 V23 Native 代码绘制完整的因果链:
- 触发 RenderTree / Placement 计算的入口与时机是什么?
- `placementChanged`、`symbolFadeChange`、`placementTransitionActive`、`crossSourceCollisions` 分别表示**当前帧/上帧/全局/局部**的什么事实?何时清零或稳定?
- Source/Layer 的 layout、tile、visibility、camera、animation 等变化,分别可能影响哪些 Placement 状态?
- R0 以外的 C1 Symbol/Annotation/业务层变化,**在哪些条件下**可能影响 R0 的符号碰撞与最终视觉?
- 如果 RenderTree 每次都重新算 Placement 但画面没有变化,如何区分“执行过计算”和“R0 最终结果发生变化”?
### Q2:现有 R0 Gate 的作用域
调查 V23 实际调用链及数据结构,至少解释:
- R0 结构性 `placementCapable` 如何判定,与动态 `needsPlacement()` 有何区别;
- R0 `placementGatePassed` 如何使用全局 `placementChanged`、fade、transition;
- R0 `cache.supported`、`static-placement`、`actualExecution=live` 的完整因果关系;
- R0 的 source-local mutation、segment fingerprint、retained revision 与 placement gate 是否真正各自独立;
- STEP-102 对 **placement-free** R 的隔离已解决什么、**placement-bearing** R 仍未解决什么;
- `lastInvalidationReason` 与当前失败条件是否可能属于不同采样/缓存生命周期。
### Q3:能否证明“R0 不受 C1 变化影响”
针对以下反事实场景逐项作出有证据的判断:
1. R0 含静态地名,C1 只有普通非 Symbol 动画/颜色/粒子更新;
2. R0 含静态地名,C1 的 Symbol 内容变化,但不同 Source 且跨源碰撞关闭;
3. R0 含静态地名,C1 的 Symbol 内容变化且跨源碰撞开启;
4. R0 Symbol 自身 layout/tile/visibility/zoom 发生变化;
5. Camera/pan/zoom/rotation/pitch 变化;
6. Symbol Fade/Placement Transition 活跃或刚结束;
7. C1 有内部自动插入的 Annotation Symbol(参照 STEP-106)。
每行必须分别给出:
- “谁触发 Placement”能否识别;
- “R0 最终 Placement/可见性是否改变”能否独立证明;
- **允许 R0 Reuse 的必要安全条件**;
- 当前 V23 是否具备这种判定能力及证据来源;
- 无法证明时为什么必须继续 fail-closed。
严禁把“不同 Segment”简单等同于“碰撞和 Placement 相互独立”。
### Q4:对 Fast Path 的影响(仅限前置依赖)
解释“R0 缓存有效”是否为 Fast Path 的必要条件但**非充分条件**。只读标出 R0 Gate 与单 C / unsupported continuous layer / revision / pending changes 等后续 Gate 的先后关系。不要扩大成 C1 全图层性能调查,不实施多 C 优化。
## 五、只读检查范围与方法
允许读取:
- 当前 Particle Core 的 Renderer、RenderOrchestrator、PlacementController、RenderTree、Symbol/Collision、Retained Cache、RenderSegment Plan、Diagnostics 与 Fast Path 相关源码;
- V23 tag 与当前 `particle/main` 的只读 `git show`、`git diff`、`git log`;
- STEP-090/102/106 等历史设计、报告、设备验证结果;
- 现有 C++ 定向测试源码与 Validation 场景源码(只读,不执行、不修改)。
建议从当前实际路径 `src/mln/renderer/` 与相关 Shared Core 源码定位,**不得沿用旧 STEP 中过时的路径假设**。
每一条重要结论必须给出:**文件路径 + 类/函数或结构名称 + 适当行范围或 commit + 相关调用顺序**。核心论断不得只引用历史文档或本任务描述。
允许纯只读 Git/文本搜索,不要求也不允许额外建立实验分支、修改 Native/JS/Kotlin/测试代码、插桩、添加日志、运行 Jest、运行测试、Gradle、CMake/Ninja/Bazel、ADB 或重新安装 App。不得改 NavSea、Integration 或正式 release/tag。
## 六、最终必须提供的结论矩阵
**每个问题必须选择明确判定**,而不是只罗列建议:
| 维度 | 可选结论 |
|---|---|
| 能否定位 Placement 事件来源 | 现有数据可区分 / 现有数据不足 / 条件性可区分 |
| 能否证明 R0 的实际视觉结果未变化 | 已能证明 / 需扩展内部安全证据 / 不能按段安全证明 |
| 现有 Gate 是什么性质 | 正确的设计保守限制 / 明确可证的过度阻塞 Bug / 混合 / 证据不足 |
| 缓存失效的主要责任边界 | Native Placement 数据模型 / Native Gate 判定 / 当前跨段碰撞语义 / Host Style 组合 / 多因素 / 未确定 |
| 可选后续方向 | 无需代码调整 / Native 内部诊断补强 / Native 内部安全判据设计 / 宿主层序方案需协调 / 需真机对照实验 |
如果判断是 Bug,必须指出**违背了哪项既有语义/可复现的状态不变量**;如果判断是安全限制,必须指出**缺少何种证明才不能放宽**。如有多个阻塞项,按必要性和因果顺序排序,避免“修第一个之后才继续猜下一个”。
## 七、报告与交接产物
只允许新建/更新以下本 Step 文档(不改现有源码):
1. `docs/20_调查/R0_Placement跨段归因与缓存安全边界调查_v1.md`(主报告);
2. `docs/50_Step记录/STEP-110_R0_Placement跨段归因只读调查_v1.md`(状态及完成项);
3. `docs/报告/STEP-110_R0_Placement跨段归因只读调查_Codex完成报告_v1.md`(执行报告);
4. `粒子开发记录.md`(简短追加式记录)。
调查主报告必备章节:
- V23 tag / 当前 main 基线对照,说明是否有相关行为变化;
- Q1~Q4 的逐条答案及代码证据;
- **数据流/状态流**:Placement 事件 → 全局/局部状态 → R0 安全判定 → Cache Build/Reuse/Fallback → Fast Path;
- 七类反事实场景矩阵,明确跨源碰撞、Fade、Annotation 的影响;
- 已确定事实、假设、证据不足之处三栏;
- 结论矩阵、根因优先级、最小下一步验证建议;
- 若需调整公共 Contract / API,则仅提出 Integration 阻塞,不自行实施。
完成报告必须逐项按“**已完成 / 未完成 / 无法验证**”回答任务项并说明原因;记录实际检查范围、当前 branch/HEAD、Git 提交及推送信息。不得用“源码已读”代替代码证据。
**状态判定**:只读因果审计证据完整才可标为 `调查 PASS(不等于修复 PASS)`;否则 `PARTIAL/BLOCKED` 并列出缺失项。严禁声称“Native 已修复”、“R0 已缓存”或“CPU 已下降”。
## 八、Git 收尾与执行界限
- 本任务**只负责调查**。Codex/粒子项目未经用户后续明确授权,不得实施任何缓存、Placement、Fast Path 逻辑修改。
- 完成相关文档后,仅提交本 Step 新增报告、Step 记录及追加的开发记录;保留所有无关 dirty/untracked 文件,不得强制清理。
- 将本任务相关文档 scoped commit 到正式主线 `particle/main` 并 push 到公开远端 `origin/particle/main`。禁止 force push、改写历史、推送 upstream 或增加无关文件。
- 完成报告必须给出**实际 commit hash、commit message、push 结果、远端分支**;push 失败不得宣称完成,应明确报告阻塞。
- 本 MD 是交给粒子项目执行的**待办合同**。写入任务书本身不代表调查已执行、已验证或已得出结论。
- 本任务不包含 Jest,不涉及原生编译、测试执行或 Android 设备操作。
## 九、停止条件
遇到以下情形须停留在调查层,提交 PARTIAL/BLOCKED 事实,不得扩大实现:
1. V23 tag 与当前代码差异导致无法确认实际执行版本;
2. Placement 影响关系必须依赖运行时数据,静态源码无法证明;
3. 证明缓存安全需新增 per-segment collision/placement fingerprint 或改写 Core 算法;
4. 需要改变对外 Native/RN 公共接口或跨项目责任;
5. 需要 NavSea 配合修改 Style、增加调试或重新编译/测试;
6. 所需证据缺失或与 STEP-090/102/106 的历史结论相冲突。
出现以上情况,在报告中给出**最小下一步任务建议**,等待用户/Integration 决定;不得在本任务中直接实施。

View File

@@ -0,0 +1,41 @@
# STEP-111 R 段 Placement 结果等价与独立刷新可行性只读调查 — Codex 任务 v1
## 一、用户目标
用户希望 Native 对 R-C-R-C-R-C(简称 RCRCRC)中的每一个 RETAINED R 段单独决定是否更新。若该段本身最终视觉输出未变化,不应仅因另一段 Symbol/Placement/Fade 全局状态更新而被动重建;确实受跨段碰撞影响的 R 段必须正确更新,不能破坏文字、图标或层序。此任务是下一步架构/可实现性调查,不是授权实施优化。
**核心区别**:A. 哪个 Layer/Source/Segment 触发重新计算;B. 全图 Placement 后每一个 R 段的最终 Symbol 视觉结果是否改变;C. Retained R 段缓存能否安全复用;D. 是否可进一步省略 R 段 prepare/placement 计算。A≠B、B≠C、C≠D。不要把各 R 分段直接当作 Collision Domain,也不要承诺每段无需参与全图 Placement 计算。
## 二、正式边界与必读
- Mac /Users/tei/dev/MapLibreParticleLab,particle/main,origin/particle/main;执行时重新记录 HEAD、branch、dirty、远端基线;保留未跟踪 STEP-110 任务书。
- 先读仓库 AGENTS.md、PARTICLE_BASELINE.md、docs/00_项目规范/、docs/00_项目总则/README.md 和全部现行总则;读 STEP-110 主报告和 Codex 完成报告,以及 STEP-102、106、109、Fast Path 相关历史证据;以当前源码事实为准。
- 先检查 /Users/tei/dev/navsea-particle-integration 的 main 与 origin/main 同步状态,读取其 README、WORKFLOW、TEAM_MODEL、contracts、rfc、adr、status、compatibility;已有跨项目结论优先。本 Step 只讨论 Shared Core Native,不直接修改 Integration/NavSea/Android/iOS。
- STEP-110 已证明:PlacementController 仍是整图语义;R 的 placementCapable=true 时 Gate 消费 global placementChanged、symbolFadeChange、placementTransitionActive;跨 Source Collision 可能影响 R0,source≠segment。STEP-102/106 只验收过 placement-free R 的安全复用。本 Step 不能简单重复这些事实,重点给出**新增的判等/判无变化能力**具体可行性。
## 三、只读调查工作项
1. **从源码定位可实现性**:追踪 CrossTileSymbolIndex、Placement::placeLayers/commit、CollisionIndex/CollisionGroups、symbol buckets、placed icon/text、opacity/fade、RenderTree、RenderLayer、RenderSegment/Retained Cache 的真实控制与数据所有权。指出计算结束何处可以读取每一个 R 的最终 Placement 输出,是否已有稳定 ID、可见性/位置/opacity;写明路径、函数、行号和生命周期。辨别层间/Source 间/段间影响。
2. **定义每段结果等价契约候选**:提出具体 per-R PlacementResultKey/Signature/Revision 候选字段(Symbol 实例稳定 ID、tile/source/bucket generation、placement result、collision visible/hidden、text/icon anchor/offset、paint opacity/fade/transition、zoom/camera、glyph/image atlas、style/layer order、render target boundary 等)。逐项解释哪些是 Placement 专属,哪些已有其他 cache gate 覆盖,哪些必须动态比较,哪些不需要加入,避免对每个 Symbol 每帧做过度 O(N) 或漏比较;比较发生于何时、如何处理首次帧、transition、reset、missing data。
3. **RCRCRC 示例与判定表**:至少 R0/C0/R1/C1/R2/C2,其中 R0/R1/R2 都有 Symbol。分别分析:
- C1 仅粒子/颜色动画,R 均无可见变化;
- C1 Symbol 变动且只影响 R2;
- C1 Symbol 变动,碰撞也影响 R0/R1;
- R1 自己改变但 R0/R2 不变;
- 同一 Source 跨段、不同 Source 且 crossSourceCollisions=true/false;
- Annotation Symbol 自动插入,layer 顺序、camera/zoom/pitch、tile/glyph fade/opacity transition;
- 不能证明者 fail-closed。
结论表需区分“依然执行全局 Placement”、“该段仅避免 cache rebuild/composite 是否可复用”、“能否避免 render/prepare”、“Fast Path 仍被哪些其它 Gate 阻断”。
4. **方案对比与难度**:对 A 只加 tracing/per-R 指标(不改变判定)、B 新增 per-R result fingerprint 但 shadow compare(仅记录真假)、C 利用已证明等价替换 global-only Symbol Gate 允许单段 Reuse、D 深改 Placement/Collision 成分段增量计算,逐一评估前提、复杂度(1-5)、改动范围、CPU/GPU/内存开销、风险、能否只改 Shared Core、对 Android OpenGL/iOS Metal 的影响。不能因为理论上可 hash 就声称 hash 一定安全。
5. **执行与验收计划**:给出最小分阶段实现建议和真实可验证指标,包括 per-R signature stable/change、false invalidation、false reuse(严格 0)、build/reuse/fallback、视觉 A/B、Fade 长尾、CPU/frame、same frame draw ordering;指出先做诊断或原型是否需要新 Native 内部接口,哪些行为必须同时覆盖 Android/iOS。分别评估改善 retained cache 命中与改善整帧 CPU/Fast Path 的边界。
6. **明确推荐结论**:回答每个 R 段可否“自身未变就不刷新”、可做到的最小粒度、不能保证的情形、最佳第一阶段(原型)与中长期方案,给出具体源码位置和工程量区间;区分源码已证实 vs 推断 vs 必须运行时验证。
## 四、禁止项
调查只允许读取 Native/历史 Git/本机历史实验文件以及新增本 Step 文档。禁止修改任何 C++/JS/Kotlin/Swift/编译脚本/测试代码;禁止执行编译、链接、测试、Jest、Gradle、ADB、Bazel、CMake、设备操作;禁止更改 NavSea、Integration、公用 Contract/API、GSRL、release/Source Lock、发行 Tag;禁止放宽当前 fail-closed Gate。禁止重置/清除现有 dirty、禁止 force push。除非明确用户授权,不执行后续实施。
## 五、必做输出(实际生成)
1. docs/20_调查/STEP-111_R段Placement结果等价与独立刷新可行性调查_v1.md — 主报告:代码路径/行号、RCRCRC 场景矩阵、Signature 草案字段逐项依据与生命周期、可行方案 A-D 对比、复杂度与工程量估计、风险、可执行的后续 Step 拆分、源码已证/待验证标识。
2. docs/50_Step记录/STEP-111_R段Placement结果等价调查_v1.md — 简短中文 Step 记录,状态 PASS(调查完整)/PARTIAL/BLOCKED,记录未验证项与下一步。
3. docs/报告/STEP-111_R段Placement结果等价调查_Codex完成报告_v1.md — 完成报告:实际模型与 reasoning effort(仅凭运行会话证据)、读取资料、逐任务结果、调查起/终 HEAD、branch、dirty、文件修改、未运行构建和测试、检查命令、限制、Git/Push 状态如实记录。
4. 粒子开发记录.md — 本阶段简短追加(不要重写历史)。
Codex 仅负责内容调查和上述报告落盘,Git scoped commit/push/远端验证由 GPT 指挥 RDC 独立负责,完成报告不得预报未执行 Git 结果。原有未跟踪 STEP-110 任务文件不纳入提交。本任务书自身保持未跟踪,避免误提交。
## 六、验收与停止
只在调查结果有对应源码位置/函数/调用顺序,明确安全条件、未证实风险、工程阶段与复杂度对比时,记“调查 PASS”;如生产实际视觉等价无法仅通过源码证明,可记“调查 PASS / 运行验证未执行”,不得声称优化已通过。必要的实机状态/插桩超出只读范围时明确给出下一阶段最小需求,不得扩大当前范围。不得把历史 STEP-110 的 PARTIAL 当成本次已经实现结果。任务退出前向 GPT 汇报生成文件、最后状态和未完事项。

View File

@@ -0,0 +1,138 @@
# STEP-112:V23 粒子 Full Render 循环与 Symbol 缓存准入全面源码调查 — Codex 任务 v1
**状态:待执行|类型:全面只读源码调查|编写日期:2026-10-10**
## 一、用户目标与必须回答的问题
NavSea 的 FZG1 连续风场场景中,粒子约 30 FPS,Native Particle Fast Path 持续 0 hit,约 1,787~1,796 次 Full CPU Frame/分钟;粒子关闭对照约 83 次 Full CPU Frame/分钟。含 Symbol 的 R2 缓存长期失效,理由为 static-placement。用户要求由粒子系统全面分析 Native 源码,找出导致“粒子 Tick → Fast Path 失败 → Full Render → R2 仍无法建立缓存 → 下一次继续失败”的真实原因,并提出安全修复方向。本任务**不授权修复**。
必须明确回答:
1. 谁请求每帧重绘?粒子是否只是要求下一帧,而非要求 Full Render?什么时候 MapLibre 能进入 Idle?
2. Fast Path 全部准入条件的实际来源与执行顺序是什么?为什么 FZG1 的首个拒绝原因是 cache-invalid?
3. R2 为什么 Build=0,而不是建好后偶尔失效?static-placement 实际检查了什么?
4. placementChanged、symbolFadeChange、placementTransitionActive 是否在正常连续运行下形成难以通过的组合?原因来自时间机制、Transition 配置、Source,还是其它条件?
5. Full Frame 已经正确绘制 Symbol 时,什么条件下可以安全地建立缓存供后续 Particle Tick 复用?修复 R2 后还会遇到哪些独立阻塞?
**假设不是结论**:此前讨论认为 symbolFadeChange == 0 可能被误用为“Fade 已稳定”,这只是必须严谨验证或推翻的假设;不得直接断言为 Bug。
## 二、环境、权威文档和既有事实
- Particle 源码:Mac /Users/tei/dev/MapLibreParticleLab;执行时记录实际 branch/HEAD/dirty。编写时 branch=particle/main,HEAD=50ae48ad70,不能把该 HEAD 当执行时已固定版本。
- 按 AGENTS.md 顺序先读 PARTICLE_BASELINE.md、docs/00_项目规范/、docs/00_项目总则/README.md 及现行总则、本任务、STEP-102/106/109/110/111 相关调查与报告;以执行时源码事实为优先。
- 跨项目事实依照 /Users/tei/dev/navsea-particle-integration 的最新 COORD/RFC/ADR/Contract/Status。公共 API、Host/Native 职责改变只写提案,跨项目协调另立事项。
- 现有未跟踪 STEP-110 与 STEP-111 任务书不得移动、覆盖、代为提交。现有 STEP-110/111 调查属于 PARTIAL,不表示 per-R 安全复用已经实现。
- 执行模型建议 GPT-6 Sol Medium,实际完整模型与 reasoning effort 在开始前核实并报告;MD 不负责自动切换。提高推理档位须先停止说明理由、由用户确认。
### FZG1 现场证据(不等于逐帧因果证明)
| 指标(约 60 秒连续风场) | RC1 | RCRR_PREFIX | RC2 |
|---|---:|---:|---:|
| Full CPU Frame 增量 | 1792 | 1787 | 1796 |
| Fast Path Hit | 0 | 0 | 0 |
| R0 Reuse | 0 | 1787 | 0 |
| R2 Build/Reuse | — | 0/0 | — |
| R2 Invalidation/Fallback | — | 1787/1787 | — |
| R3 Reuse | — | 1755 | — |
| Fast 首个拒绝 | cache-invalid | cache-invalid | cache-invalid |
RCRR_PREFIX 分段:[0,13) R0;[13,33) C1;[33,107) R2;[107,113) R3。C1 **含普通 Symbol/Line,以及 index31 WeatherColor 与 index32 Particle**,并非纯粒子段。R2 含大量 Symbol。FZG1 末态记录 placementChanged=true、symbolFadeChange=0、placementTransitionActive=false,**只是快照不是每帧时间序列**。此前也观察到导航 Source Mutation 计数增长,不能未经证据归咎于它。Full CPU Frame 不等于所有 Layer 重绘,不等于 Placement 实际重算次数。
## 三、必须完成的源码分析 A:从粒子请求到 Full Render 的调度链
逐级核查:
- RenderRasterParticleLayer::isParticleAnimationActive / needsRepaint,SourceTimeline / V4Cadence / cadenceDue,粒子启动、暂停、无数据、隐藏时的返回条件。
- RenderOrchestrator::hasTransitions,RenderTreeParameters::needsRepaint,Map::Impl::onDidFinishRenderingFrame / onUpdate,RendererObserver、RendererFrontend/Backend、Renderer::render / tryRenderParticleFastFrame。
- 明确粒子 Tick、下一帧调度、Native Full 准备、GPU 粒子更新及 MapLibre Idle 的不同概念。检查 30 FPS 来自哪个实际 cadence,以及是否存在独立的 Placement 定时器强制 Full。
- 输出**可核查调用图**,带文件路径、函数、行号及关键条件;区别“必然调用”“在特定配置调用”和“只是假设”。
## 四、必须完成的源码分析 B:Fast Path 全条件审计
检查 src/mln/renderer/particle_fast_path.hpp 的 evaluateParticleFastPath 与 renderer_impl.cpp 的 tryRenderParticleFastFrame:
- Prepared RenderTree、Retained enabled、**所有 R 段** Cache valid、Build/Static Revision、Content Revision、Camera/Viewport、non-camera update signature、Style、Boundary/Topology/RangeConfig。
- staticStable、Placement transition、pending changes、non-particle transition/repaint、fading tiles、ParticleNeedsRepaint、still image、MapMode、Debug、Continuous 支持层、scene support、annotation mutation。
- **必须产生全 Gate 矩阵**:条件/来源函数与行号/通过条件/失败原因/是否被粒子 Tick 本身改变/是否在一次新 Full 后可能恢复/FZG1 已证实或未知。
- 确认短路返回首个错误是否掩盖 unsupported-continuous-layer、unsupported-scene 等后续失败;不得因只看见 cache-invalid 就宣布其他条件已通过。
- 比较 Full 与 Fast 路径的 CPU Prepare、RenderTree、GPU 绘制工作,避免将 Fast 解释成零 GPU render。
## 五、必须完成的源码分析 C:R2 缓存 Build/Invalidate 状态机
从图层结构到 GPU 缓存逐级核查:
- render_segment.hpp 的 renderSegmentHasPlacementCapableLayer / renderSegmentPlacementGatePassed;隐藏 Symbol 是否同样触发结构级保护。
- render_orchestrator.cpp 的 retainedRangeUsesPlacement / retainedRangeIsStable、Source/Tile/Glyph/Sprite/Annotation 的有效性及跨段 Collision 依赖。
- renderer_impl.cpp 的 retainedPlacementStable 全局 Scene Gate、每段 segmentPlacementGatePassed、segmentSupported、Segment fingerprint/source revision、segmentFallbackReason、缓存 evaluate 与合成控制。
- retained_underlay_cache.cpp 的 RetainedUnderlayCache::evaluate / invalidate / BuildPending / markBuildSucceeded / markBuildFailed / releaseResources;区分“首次未建立”“有效缓存因 Gate 被主动 invalidated”“底层资源损坏”“revision 变化”。
- 以 FZG1 的 R2 Build=0、Reuse=0、Fallback/Invalidation≈1787 为约束,写出完整状态转换和所有可支持或反驳的来源;解释为什么 Full 的正常绘制不自动等于缓存离屏纹理 Build 成功。
- 对 placementChanged=true 分清“发生 Placement 计算”与“R2 Symbol 最终可见性/位置/像素变化”。还须分析跨 Source/跨段 Collision、Fade、Shader opacity、Camera、异步资源所需安全边界。
## 六、必须完成的源码分析 D:Placement 和 Fade 真正语义
逐函数追踪 PlacementController::placementIsRecent / setPlacementStale / hasTransitions;Placement::getUpdatePeriod / commit / symbolFadeChange / hasTransitions;Style TransitionOptions;Symbol 在 OpenGL/Metal Shader 中如何消费 symbol_fade_change、opacity。
**必须分别分析两种运行配置**:
1. enablePlacementTransitions=true:默认 300ms 更新间隔、Symbol Bucket Added 可能导致 30ms override、Zoom 修正、Fade 从 0 到终态的数值变化、hasTransitions 与 setPlacementStale 的交互。
2. enablePlacementTransitions=false:placementIsRecent() 为什么可能持续返回 false、Placement 是否每次 Full 都重算、symbolFadeChange 实际取值、为何末态可能为 true/0/false。
针对 Gate **!placementChanged && symbolFadeChange == 0 && !placementTransitionActive**,逐行给出真值表与相应真实可达时刻:
- Placement 刚计算后;
- Placement 未重算但仍有淡入淡出;
- Fade 完全结束后;
- 关闭 Transitions;
- Symbol Bucket 增减、跨段碰撞等变化;
- Camera/Style/Source 不变、只有粒子的稳态。
回答:在正常运行下是否存在可用于 Build 的安全窗口?如果不存在,是哪一条造成的?全局 Scene Gate 与每段 Placement Gate 是否构成第二重锁死?FZG1 末态数据不能被机械套进默认 300ms 时间表。**绝不通过直接删除 Gate 或仅更改 ==0 比较符来宣布安全修复。**
## 七、必须完成的源码分析 E:反事实场景与因果闭环
对下列情况分别输出逐帧状态推演与 Fast Gate 判定(明确“已证明/条件推断/需设备验证”):
- **S1 纯粒子稳态**:Camera 不动、所有 Source/Tile/Style/Glyph/Sprite ready 且不变、无外部 Full 指令、粒子 30 FPS。能否从第一次正常 Full 构建 R2 缓存并进入 Fast?何时会卡死?
- **S2 粒子关闭**:没有粒子连续重绘,仅偶尔正常地图刷新。分析为何 Full CPU Frame 频率低,不能误判 Placement 从不执行。
- **S3 真实 Host Source Mutation**:导航相关 Source/Annotation 更新与 Tile 异步输入同时存在,量化其对 Revision、Cache Build、Full Frame 的理论影响;不能假定所有变化都来自粒子。
- **S4 不同布局**:原 RC/RCRR、实验 RCRR_PREFIX、理想保留图层顺序的 [0,13) R / [13,32) R / [32,33) C / [33,107) R / [107,113) R。检查 C 范围有普通 Symbol/Line 是否会触发 unsupported-continuous-layer;不能将最后一个仅为反事实的配置写成已实现。
- **S5 动态安全回退**:相机缩放/旋转、Tile/Glyph/Sprite 完成加载、Opacity Fade、跨段 Collision、Style/Layer 改变时,哪些 R 必须重建,哪些状态不能盲目复用。
务必清楚界定:“粒子导致下一帧”与“粒子本身让静态 R 内容变了”不等价;“需要 Full 准备”与“所有图层从零画一次”也不等价。
## 八、方案对比与建议(仅提交提案)
至少比较四类选项并给优先级:
- **A 全 Gate 与 R2 状态诊断**:一帧内采集所有 Gate 状态,而不是仅返回第一个 Miss;记录 Placement 配置、计算原因、Fade 值、各 R 的 Build/Reuse/Fallback/Invalidate,控制 tracing CPU 开销。
- **B Full Frame 后安全建立 Symbol R 缓存**:寻找 Placement、Opacity、GPU Shader、资源与 RenderTree 都满足安全条件的 Build 时机,打破可能的“永远无法首次 Build”自举问题。
- **C 更细粒度 per-R 结果等价**:与 STEP-111 对照,是否有必要做 per-R Signature/Revision 或可采用更轻量的生命周期证据;跨段 Collision 不能被忽略。
- **D 外部 Full 更新与 Particle Tick 分流**:用户要求外部明确 Full 指令到来即完整更新,不因数据相同去重/延迟;无外部指令时粒子优先 Fast,仍保留 Native 必要安全 Full 回退。核对目前内部/公共接口是否支持;涉及责任或公共语义必须由 Integration 正式协调,本任务不得私建 API。
每项必须列:是否解决首次 Build、是否能解决全局 Scene Gate 和 C 段 Gate、数据正确性、GPU/CPU/内存代价、需改文件、风险、复杂度 1~5、Android OpenGL / iOS Metal 一致性、最小验证方案。
**性能目标是 30 FPS 粒子与低频外部 Full 更新各司其职;2 Full + 28 Fast 是理想例子,不是当前 V23 已实现或零安全回退的承诺。** 不以降低粒子 FPS、删除海图 Symbol、移动一批普通静态 Layer 到 C、放宽 fail-closed 或忽略外部指令作为优化手段。
## 九、本轮权限、限制与停止条件
**仅执行全面只读调查**,允许阅读本仓库源码、历史 Git、已有 FZG1 数据、现有调研报告;允许写入本任务要求的调查文档、Step 记录、Codex 完成报告与对粒子开发记录的短条追加。不得修改 Native、Android/iOS、Shader、JS/TS、构建脚本、测试源码、Host/NavSea、Integration 契约、Style、Renderer Gate 或公共 API。
不执行编译、链接、打包、设备操作、发布或新的性能测试。本轮如无法通过源码证实最终触发频率,必须给出下一阶段**最小诊断插桩提案**,记录字段、调用点、采样窗口、FZG1 验收场景与预期判断方法;不可未经授权直接实施插桩。严禁清理其他 dirty/untracked、变更上游、强推、改历史 Step。无法证明安全则维持 fail-closed。
需要涉及跨项目职责/API 调整的提案,先输出 Interface Blocker,返回 Integration 决策。遇到重大事实冲突时报告并停止冲突部分,不得自行扩大修改范围。
## 十、必做输出(调查完成时必须真实落盘)
1. **主报告**:docs/20_调查/STEP-112_V23粒子FullRender循环与Symbol缓存准入全面源码调查_v1.md。必须包含:
- 完整粒子调度→Fast→Full 调用图、源码路径/函数/行号;
- Fast Path **全部 Gate** 对照矩阵(含短路掩盖条件);
- R2 Cache 的首次 Build、Invalidate、Fallback 状态机与实际决策链;
- enablePlacementTransitions 开/关及 Fade 时间/数值状态表;
- 原始、PREFIX 和理想单粒子 C 的分段对照;
- S1~S5 反事实和证据强度;
- A~D 方案风险/成本/工程改动/优先级及推荐的下一步;
- 不同问题分别标注“**源码证明** / **FZG1 实测** / **条件推断** / **仍需设备验证**”。
2. **阶段记录**:docs/50_Step记录/STEP-112_V23粒子FullRender与Symbol缓存调查_v1.md。写状态、目标、完成项、未完成、阻塞与下一步。
3. **Codex 完成报告**:docs/报告/STEP-112_V23粒子FullRender与Symbol缓存调查_Codex完成报告_v1.md。逐项按“已完成 / 未完成 / 无法验证”报告,必须解释未完成原因;记录实际模型/推理档位、Git 起终 HEAD、branch、dirty、变更文件、检查结果、未做构建/运行的事实、Git/Push 实际结果。
4. **粒子开发记录.md**:本阶段追加简短事实记录,不能更改历史。
## 十一、验收与 Git 收尾
只有 A~E 的关键问题有源码级证据、全部 Gate 有出处、root-cause 假设被验证或明确证伪、方案风险和最小验证路径充分时,才能写“源码调查 PASS”;**运行时未验证、Native 未修复、Fast Hit 未改善**必须明确列出,不能写实现/性能 PASS。没有决定性证据,结论应 PARTIAL,明确下一轮需要的最小运行时信息。
本任务研究完成后,对**本任务范围的文档变更**进行 scoped Git 提交并 push 到 origin/particle/main,核对远端 HEAD;不得纳入现有未跟踪 STEP-110/STEP-111 任务书或无关修改。依 AGENTS.md 的 Codex→RDC 职责分工,Codex 交付调查结果,RDC/获授权操作方独立完成 Git/Push 与核验;Push 未确认不得冒称 Git 收尾完成。记录真实 commit hash、message、远端分支与 push 结果。禁止 force push、upstream push。
**本任务结束条件:调研证据、报告与 Git 收尾;不得继续直接实施 Native 修复。**

View File

@@ -0,0 +1,41 @@
# STEP-113 — NavSea FZG1 PBF/Layer 等价测试 App 与逐帧 Fast Gate 诊断
## 授权与范围
用户明确授权:在 Particle Android 测试 App 接入与 NavSea 相同的正式 PBF 与图层分段,找到每帧阻止 Particle Fast Path 的所有 Gate;RDC 可以编译 APK、使用已连接 FZG1-2 的 ADB 测试。使用 Codex **GPT-6.1-Sol High** 实施源码,RDC 独立编译/设备验收。不得把调研中的假设冒充真机结果。
## 项目与协作
- Particle repo: /Users/tei/dev/MapLibreParticleLab, `particle/main`; NavSea **只读**: /Users/tei/dev/NavSea-Build。Integration **只读**: /Users/tei/dev/navsea-particle-integration;已 fetch 核实 Integration main 80667d6c。
- 先完整阅读 Particle `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、`docs/00_项目总则/README.md` 和其四份有效总则,以及 STEP-112 源码调查、上一步报告。
- Native/Shared Core 仅增加诊断,Android Test App 增加测试 UI/场景。**禁止**改变现有 Fast Path Gate 准入逻辑、Placement/Fade、安全回退、公开 RN API、NavSea 源码、Cache 等价安全判定。Common Core 诊断数据不能含 Android 专有语义。
- 仓库中两个已有未跟踪 STEP-110/111 任务 MD 必须原样保留,不得 add/reset/clean;禁止新分支或工作树。
- Codex 不编译、不执行 Android Gradle/ADB、不执行 Jest(用户没有要求),不做正式发行、Git commit/push;由 RDC 负责这些步骤。
## 目标 A:准确复制生产 Style、场景与真实 R/C 分层
现有测试 App 已支持 `NAVSEA PBF`,使用生产 URL `https://pbf.jpcn.ccwu.cc/style/style.navsea-delivery-full-semantic-extentfix.json`,但仅有 **63** 个基础图层;它与 NavSea FZG1 实测 **113** 个最终渲染图层尚未等价。NavSea 源码 `src/config/builtinRuntimeUrlConfig.ts` 是 URL 事实源;`src/map/particle/RenderSegmentHost.tsx` 是宿主分段控制。STEP-112 用户 FZG1 的 RCRR_PREFIX 诊断:R0 [0,13), C1 [13,33), R2 [33,107), R3 [107,113)。注意 NavSea 当前源码 R/C 初始 plan 可能与运行诊断不一致,必须记录版本/选择器,不得当作同一事实。
- 基于现有 `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt`/fixture 增加独立 **STEP-113 FZG1-PBF Gate** 测试场景。真正加载生产 Style、Sprite、Glyph、VectorTile URI;原有场景全部保留,且不得改为假矢量地图。
- 与 NavSea 对齐层顺序、类型、Source ID 和显隐规则:优先从 NavSea 实际源码、已有 FZG1 快照/资料得到完整清单,至少能证明原始63层一致;对于 RN 额外动态层,若没有真实 Source/图层定义,不得将同名假图层宣称为“同等画面”,应分别标注原样/结构仿真/缺失。严格实现全113层和所述四分段如能力允许;若其余层无法准确复制,仍完成可运行的 **真实 PBF 63 层模式 + 明示的部分等价模式**,并记录阻塞原因,不要卡死整个任务。
- 每个场景导出/Logcat:实际 styleLayers (index/id/type/source/visibility)、总层数、每段 [begin,end)/类型与层名、分段配置接受/拒绝原因、当前 Style URL/版本/fingerprint、预报时间和 Particle Tick 状态;严格用实际 Layer ID 定义分段,不假定第13/33/107的 ID 永远不变。
- 支持至少静态30fps粒子且 camera/forecast fixed、粒子关闭、Source 变化、场景对照。静态场景不可每500ms自行修改 Symbol;原 STEP-102 的动态 fixture 保持原样。
## 目标 B:逐帧 24 Gate 完整诊断
- 阅读 `src/mln/renderer/particle_fast_path.hpp` 的 `ParticleFastPathGateInput` (24 条固定顺序) 与 `src/mln/renderer/renderer_impl.cpp::tryRenderParticleFastFrame`;每次真实 Fast 尝试,记录统一 frame/sequence、所有 24 条 Gate 的 **PASS/FAIL** 或明确不可观测的 UNKNOWN,并保存首个实际短路 Gate 与同帧失败全集。不得通过二次调用有副作用的函数来获取字段;优先使用现成的已计算 `ParticleFastPathGateInput` 在单次调用无副作用完成独立掩码计算,并确保首错与 `evaluateParticleFastPath` 决定一致。
- 对于 CacheInvalid 等早期阻断,也能看出后面的 `unsupported-continuous-layer` / `unsupported-scene` 是否同帧成立。记录 per-R cache state/build/reuse/invalidation/fallback, Placement changed/transition/fade、原因分类、FullFrame count。不要把 Full 绘制导致的 post frame Placement 数据冒充 pre-fast 原子时刻;必要时为 pre/post 两种 phase 分离记录。
- 诊断必须为有界环形缓冲/低开销低频导出(例如保留最近120或256帧,另聚合失败计数),不得每 33ms 从 JS/Android UI Logcat 输出大 JSON。沿用已存在的 Android `getRenderSegmentDiagnostics` Native JSON 出口,新增 `fastGateTrace` 可选诊断字段,避免新 RN API/ABI;必要时增加手动/低频截图/Logcat 导出入口。控制每分钟几条摘要而非每帧 log。
- 同一渲染生命周期 reset 要有清晰逻辑;不引入 data race,遵循现有 shared snapshot/atomic/锁语义,避免在 map UI 线程操作 Native mutable renderer state。
## 目标 C:可验收性
- Test App UI 能切换 STEP-113 场景、查看 Gate 首错/失败排名与各段状态;可用 Logcat 定向标签稳定抽取机器可读 JSON (包含 schema/version, style parity summary, frame window计数, gate-failure聚合, R状态摘要)。不要让 UI 实时多次 Native 查询产生额外帧,使用约2至5秒刷新。
- 提供实际验证步骤(Mac PowerShell不要,macOS shell可用),基于标准 `tools/particle_android_validation/build_validation_apk.sh` 构建,确认设备 ABI 与脚本默认 ABI一致后交 RDC 运行。ADB 必须明确 `-s 192.168.200.139:5555`(这个设备本轮是 FZG1-2),不可无参数误操作其他设备。
- 文字报告区分:SOURCE-PROVEN / TEST-APP-STRUCTURAL / DEVICE-MEASURED / NOT-VERIFIED。成功构建 APK 不表示真机性能验证通过。
## 必做输出与收尾
1. 所需 Shared Core / Android Test App 源码实现(尽量只局部变更)。
2. `docs/50_Step记录/STEP-113_FZG1_PBF_Layer与FastGate诊断_v1.md`:实际状态、设备验收填写栏、剩余风险。
3. `docs/报告/STEP-113_FZG1_PBF_Layer与FastGate诊断_Codex完成报告_v1.md`:改动文件列表、与 NavSea 样式/层等价度证据、24 Gate 定义、精确构建/ADB/场景命令、产物/日志预期、静态检查事实、未做项。
4. `粒子开发记录.md` 中文末尾追加少量本次进展(append-only)。
5. Codex 最终回报真实 model=GPT-6.1-Sol / high,以及剩余需要 RDC 独立验证事项。
6. 文件变更仅 scoped;`git diff --check`,不提交不 push;避免 Jest 和 Native/Android 长时编译。任何公共跨项目 API 变更先停、写明需要 Integration RFC。
## 优先次序
优先先完成 **B 24Gate真正可靠同帧采集**,再实现已有 PBF Mode 的专项布局与 UI;不允许因为“113层不能精确复刻”就省略实际可交付的完整 Gate 诊断。

View File

@@ -0,0 +1,41 @@
# STEP-114:Fast Path 安全准入受控修复与 Test App 闭环(Codex 任务 v1)
## 执行授权与基线
- 用户明确同意继续受控修复,不要求完全复刻 NavSea 113 层。
- Mac 仓库 /Users/tei/dev/MapLibreParticleLab,particle/main 起点 b02e54de3d45fcf612126ae8f037cba204f590ea。Integration 最新 main 80667d6c343411d03a760425438e3d292f452118 经 RDC 验证。
- Codex 固定 GPT-6.1-Sol High (gpt-6.1-sol / high)。先读 AGENTS.md、PARTICLE_BASELINE.md、docs/00_项目规范、docs/00_项目总则/README.md 及四份有效总则、STEP-112/113 报告、Integration 的正式 Contracts/RFC/ADR,严格遵守项目边界。
- 本轮 Codex 只改代码与文档;不能运行 Android Gradle、ADB、Jest、CMake/Ninja/Bazel 大编译或正式打包;Native 定向验证由 RDC 另行执行,Android 编译由用户在授权条件下手工统一验收。
- 不碰原有两份未跟踪 STEP-110/111 文件、无关文件、NavSea 源码和 Integration;禁止 git add -A / reset --hard / clean;不得变更 RN/JS 公共 ABI/API、发行版本或 Source Lock。
## 根因事实
- STEP-113 STATIC_RC 66 Native layers,R[0,63) Build=0、unknown-dependency;C[63,66) 含天气色场、粒子、内部 Annotation Symbol。Fast=0。
- 119 个最新 completed Full 帧:24 Gate shadow mismatch=0;#3 cache invalid、#4 revision mismatch、#22 unsupported continuous layer、#23 unsupported scene 全部失败;#12/#13 placement 105 帧失败。
- RenderOrchestrator::retainedSegmentSourceState 对 CustomMVTVector 明确 fail-closed,已有源码注释说明无 retained cache revision contract;不得删掉这个限制来伪造 Fast。
- continuousRangeSupportsParticleFastPath 要求 C 全层以 Particle dynamic API 标记;内部 Annotation Symbol 不满足,且 #23 聚合依赖部分与 #22 重合。
- STEP-112 证明含结构性 Symbol 的 R 在旧 Placement stale/changed/fade 时可能根本无法进入首次 Cache Build;禁止只删条件或假设视觉没变。
## 实施步骤(依次、受控)
1. 做当前源码静态闭环:明确 Test App 对应 SourceType、Source/Tile/Style/Glyph/Image/Annotation mutation 追踪、R 的初建失败点、C 准入及 Scene 复合条件、Placement 控制流。
2. R 缓存:只对确有完整 Source revision 与异步失效追踪的类型建立安全准入;若 CustomMVTVector 无法严格证明,保留其 fail-closed。优先为已支持的保守 source 子集提供可用的独立 R-cache 正向测试。保留 Source mutation、Tile update、camera、style reload、image/glyph、destroy/reset 的反向用例。
3. C 非粒子层:只有证明原有 GPU 复合内容/Source revision/Placement/Annotation 在静止场景与 Full 等价、变化时 Full 回退,才能受限放行。否则不可移除 Gate #22:可通过单独 Test App 最小场景或安全 Layer 拓扑证明能进入 Fast。不要丢失原始正式 PBF 5 场景。
4. Symbol R Placement:基于已提交的真实 Placement/资源 revision 与 transition/fade 审核是否有安全稳定窗口;如须新增独立结果签名或其他跨平台设计,停止该项并在报告中写 BLOCKED,不做危险简化。
5. 优先实现独立 Test App FAST_SMOKE 场景(如安全可行),按 UI 菜单明确区分正式 PBF 与独立最小场景。使用 Native 真实 Full/Fast/CacheBuild/Reuse 计数和24 Gate JSON,测试稳定、Source mutation、Annotation、Pan/Zoom、Reload、前后台/Teardown;不许伪造 Frame 结果或直接注入 candidate=true。
6. 所有公共 Shared Core 代码仍跨 Android/iOS 共用,不得建立平台分叉。任何需要改公共语义/API的部分暂停,留给 Integration RFC。
## 测试/运行要求
- 可改/新增现有非 Jest C++ 相关测试源,但 Codex 不执行测试/编译。
- 必须写出 RDC 可以执行的精确定向 Mac Native 检查命令;后续 Android 编译和设备 Gate 集中一次进行。
- 只证明能证明的部分;可产出 PARTIAL / BLOCKED 与可用局部修复,不允许“关掉Gate提升Fast数字”。
## 必做输出
1. 本次实际修改的 C++、Android Test App 和非 Jest 测试源(不能安全改的部分写明零修改)。
2. docs/20_调查/STEP-114_FastPath安全准入与闭环判定_v1.md:R/C/Placement 证据、可能的反例、实现范围、安全前提、实际测试方案。
3. docs/50_Step记录/STEP-114_FastPath安全准入与TestApp闭环_v1.md:当轮修改、状态、验证事实及后续步骤。
4. docs/报告/STEP-114_FastPath安全准入与TestApp闭环_Codex完成报告_v1.md:模型全名档位、改动文件、影响/兼容、RDC 具体命令、已做/未做的验收、Git check。
5. 追加中文 粒子开发记录.md:当前改动、当前状态、下一步。
6. 新增 Test App 入口时更新相关场景说明,清楚区分真实 PBF 与独立 smoke。
7. 文档必须实际写入并保留,不能任务后要求用户补交。
## Git 收尾
- Codex 只能执行无构建型静态检查与 git diff --check,回报 git status 和必要的验证命令;禁止将未运行的测试写成 PASS。
- Codex 完成后先停下由 RDC 独立审计/运行定向检查;阶段结果达到合理门槛后由 RDC scoped commit 到 particle/main 并 push origin,核验远端 SHA。若未达真实 Fast,不得标示 Fast Path 已修复。正式发行独立处理。

View File

@@ -0,0 +1,26 @@
# STEP-116:STEP-113 原 PBF Fast Path 实际阻塞修复
## 授权、当前基线与配置
用户要求继续修复,并以原 STEP-113 专用 Android APP 的 STATIC_RC 真正进入 Fast 为目标,而非 STEP-114 简化 FAST_SMOKE。当前仓库 Mac /Users/tei/dev/MapLibreParticleLab,particle/main HEAD e2d202b560ad0165101546597c43f2ef0ccf4542。Integration 已由RDC fetch 并核实 main 80667d6c343411d03a760425438e3d292f452118。仅内部 Shared Core 安全修复与 Android 专用 Test App;不改跨平台公开 API/ABI/协议,不改 NavSea、不发行。
Codex 模型必须为 GPT-6.1-Sol High (gpt-6.1-sol / model_reasoning_effort=high)。先读根AGENTS.md、PARTICLE_BASELINE.md、docs/00_项目规范、当前 docs/00_项目总则 README及四份有效总则,读 STEP-112/113/114/115、Integration instructions/README/WORKFLOW/TEAM_MODEL/contracts/rfc/adr/status/compatibility,按事实和边界执行。Codex只编辑、静态检查、中文报告;不可执行Native/Gradle大编译、ADB、安装、Jest、发版、commit/push。Native独立审计由RDC;Android编译安装由用户手工集中一次完成。
## 已确定的真正阻塞
STEP-113 STATIC_RC:正式63个PBF基础层,Test App Native实际66层;R[0,63) Build0、Fast0、Source fallback unknown-dependency,C[63,66)中有自动添加的 org.maplibre.annotations.points Symbol,#22/#23持续失败。后续 STEP-114 源码/快照发现R依赖mapple、navsea_delivery和tide,Native source表实际没有tide,tide-spots Symbol层引用它;navsea_delivery实际是Vector,不是CustomMVT。即使R的missing-source得到解决,Symbol R首次cache Build仍被Placement的changed/stale/fade保护阻挡。不可把去掉一个Gate误写为全套修好。
## 工作:真正解决而非假通
1. 通过静态源码和保留的 STEP-113完整JSON逐环确认R缺源的原层及fallback、真正的 SourceType、source/Style变化导致的revision与GPU生命周期。
2. R的缺失源:只有能证明缺失源永远贡献零像素,且后续Source创建/恢复/同名更新强制缓存失效/Full重新处理时,才允许把它作为有界的empty依赖;否则保留fail-closed并明确报告服务器正式Style存在缺源而无法安全缓存。不能偷偷创建假的tide Source或删除原始tide-spots层。
3. C的内部Annotation Symbol:可以在专用 Test App 确认地图无任何Annotation实例时,参照 STEP-114 仅移除 SDK 自动创建的空内部 Annotation 层,保留原正式63层定义、Source/Layer顺序不变;必须有代码防止遗留Annotation/Style添加时漏内容,并在日志、manifest明确记录原STYLE层原样、仅内部辅助层调整。其他已有原场景继续原语义,不为了Fast删除SOURCE_CHANGE中的测试circle。未经证明不能改Core C准入逻辑。
4. Symbol R初建:必须读PlacementController、onRender、commit/fade、renderSegmentPlacementGatePassed、retainedRangeIsStable和full-frame时的状态。真正放宽必须有结果等价的Placement/atlas/glyph/tile/style/source/camera/opacity证据,并在变更时立即invalid/full;不能仅凭stable多少毫秒、场景没有手动操作、tile加载完成来放行。若现有Core没有可核实的已提交GPU结果签名,禁止危险跨层重写,列为BLOCKED并说明需要的精确额外数据/设计。
5. 要保留24Gate shadow mismatch=0与实际Cache Build/Reuse/Full/Fast原生计数。可添加同帧诊断或快速失败细项,便于用户一次Android build后通过原STATIC_RC脚本迅速定位仍失败的Gate。保留PBF63、PARTICLE_OFF、PARTIAL_RCRR、SOURCE_CHANGE五场景、STEP114独立Smoke和STEP102原场景;不要改变公网PBF Style或NavSea,也不要求精确113层。
6. 增加与安全修复对应的非Jest C++源码测试,含缺源/源加入/async mutation、Annotation、Symbol R /全局变化/无变化、Style/Source/Tile/Glyph/Image以及Layer顺序/disable/reload;不要假造rendered Fast、不要把不运行测试记PASS。
## 交付文件,全部本轮必须实际生成
- 本轮实际源码和适当非Jest测试源码;修改Test App后更新必要README。
- docs/20_调查/STEP-116_STEP113原PBF_FastPath阻塞安全修复审查_v1.md:每条阻塞证据/解决方案/风险/未解项。
- docs/50_Step记录/STEP-116_STEP113原PBF_FastPath实际修复_v1.md:真实完成度及未运行项目。
- docs/60_测试/STEP-116_STEP113原PBF专用APP验收_v1.md:严格按最新HEAD一次用户手工Android build/install→STEP115 preflight→原STEP113 STATIC_RC 75s以上→完整120帧/24Gate/源码/设备SHA/用户画面;失败后如何判断R/C/Symbol,不得依赖旧APK。
- docs/报告/STEP-116_STEP113原PBF_FastPath实际修复_Codex完成报告_v1.md:实际模型全名档位、改动清单、公共API(必须不变)、逐项Implemented/Blocked/Not Run、Git/源码证据、RDC具体命令及用户构建/设备步骤。
- 粒子开发记录.md追加中文结果与下一步;必要README与相关已有文档说明保持一致。
- Codex运行git diff --check、静态核查;不运行Native编译、Android编译、Jest、ADB、安装、Git commit/push;本轮用户原有两个未跟踪STEP-110/111 MD一律保护。禁止git add -A/.、reset --hard、clean。完成后停止由RDC独立审计并scoped提交推送;若Fast尚未实测只能PARTIAL归档,不写Closed/PASS。

View File

@@ -0,0 +1,26 @@
# STEP-117 原STEP-113 PBF的R缓存首次Build与Fast安全准入
## 用户目标、代码基线和约束
2026-10-11 用户要求继续 STEP-116 实机之后的实际修复:原专用Android Test App STEP-113 STATIC_RC,保留所有正式PBF 63基础层,不代换成STEP114简化FAST_SMOKE。真正验收须Native Cache Build>0、Reuse>0、FastDelta>0,同时画面正确、24Gate mismatch=0、异步更新fail-closed。若现有证据不能安全准入,必须保留真实阻塞并如实标 PARTIAL/BLOCKED,禁止伪造Fast或PASS。
Mac repo /Users/tei/dev/MapLibreParticleLab 当前particle/main起点43e5f5b91598b9c3d3f1c788052d4bbd2d616668。Integration /Users/tei/dev/navsea-particle-integration origin/main同步至80667d6c343411d03a760425438e3d292f452118。模型固定 GPT-6.1-Sol High (gpt-6.1-sol, model_reasoning_effort=high),不得升级。先读AGENTS、PARTICLE_BASELINE、docs/00_项目规范、docs/00_项目总则README与全部4份当前总则、Integration instructions/README/WORKFLOW/TEAM_MODEL和contracts/rfc/adr/status/compatibility。阅读STEP112-116全部关键任务记录和实际源码。Shared Core代码仍同时服务Android/iOS,本次限平台中性的内实现与Test App诊断,不改跨平台公开API/ABI/RN/JNI合同、不改NavSea/iOS/外部PBF服务器。若改动需要新公共contract或跨项目决定必须停止该部分,并在报告中指出应由Integration协调RFC/ADR,不自行做新平台业务语义。
## 已测事实,一定先读取实机JSON和报告
docs/60_测试/STEP-116_FZG1_原PBF实机验收结果_20261011.md。原完整JSON:artifacts/maplibre-particle/android-validation/step113-retest-20261011_121308/static_rc/native-full.json,SHA=cd491d2acca97119f1d4f2d6f77eb7b229c17b90d4be1181a59202308429b219。
STEP116新APK源码c48a72a9,设备192.168.200.139:5555:原STATIC_RC已65层,原正式63层保留,C[63,65)仅WeatherColor与RasterParticle,#22最近120帧失败0。R[0,63)缺Source=tide,原style tide-spots Symbol引用tide;真实sources只有mapple:raster、navsea_delivery:vector、org.maplibre.annotations、rn-weather-fields。R Cache Build0 Reuse0、fallback=missing-source、placementGatePassed=false;24Gate最近120帧 #3/#4/#23失败120、#12/#13失败105、mismatch0,119Full/0Fast/1pending。90秒FastDelta=0、FullDelta=920。STEP116修好的空SDK Annotation条件和原63层定义不许回退。
## 必做实现/根因研究
1. 读 Style/Source creation、RenderOrchestrator::createRenderTree及LayerRenderItem、RenderLayer group disable/activate/processChanges、ResourceManager、sourceDiff、retainedSegmentSourceState、RenderSegmentRevisionBook、contentFingerprint、GPU提交。缺tide只可在具有可检查的零实际GPU绘制/无旧Renderable数据且同名Source添加/重建时完整Full+invalidate证书时作为缓存零贡献依赖;不得补假tide、删tide-spots、通过忽略revision/ready来放宽。无法证明则保持missing-source fail-closed,报告具体证据。
2. 读PlacementController::setPlacement/placementIsRecent/hasTransitions、Placement::placeLayers/commit/fade/opacities、RenderSymbolLayer/SymbolBucket::updateVertices、RenderTree::prepare、Renderer::render Full与Cache Build及particleFast path判定。当前全帧重算时placementChanged=true、不重算时stale=true,fade不断非零;使首Build一直0。若能建立与成功提交的GPU Drawn R精确等价的结果证书,才受控放宽首次缓存,包括crossTile collision/placement、glyph/image/atlas、dynamic opacity、tile/source/layout revision、projection/camera/viewport、GPU context、异步ChangeRequests;任何变更必须Full/rebuild。不能将任何placementChanged、symbolFadeChange或controllerTransition强制false或删除24Gate。若需跨层较大重写无法保证安全则保留Gate报告BLOCKED。
3. 原STEP113的正式63基础层完全保留,C空Annotation受控修复保留;旧五场景及SOURCE_CHANGE的负例保留。必要最少的Native诊断/Android测试入口增强可以做,但不可改变真实Fast计数/24Gate判定或把FAST_SMOKE当PASS。
4. 以一次未来Android用户授权的RDC标准完整build/install测试为目标批量实现可证明安全的改动;提供非Jest C++测试源码,覆盖缺源零绘制/新增同名Source后失效、Symbol placement/symbol fade、异步glyph/tile/style/annotation/camera/context改变时fallback。不能将没有运行的测试标PASS。Codex不要运行大编译/Gradle/ADB/CMake/Ninja/Bazel/Jest、安装、commit/push,由RDC独立执行。
## 必做中文交付文件
- 真正实施的源文件、非Jest测试源码;必要Test App README更新。
- docs/20_调查/STEP-117_PBF_R首次缓存Build安全证据_v1.md:真实源码/原设备JSON,GPU生命周期与Source缺失和Symbol placement安全边界、各项状态、设计/反例。
- docs/50_Step记录/STEP-117_原PBF_Fast缓存准入_v1.md:改变、阶段验收、未执行与下一步。
- docs/60_测试/STEP-117_FZG1_原PBF真实Fast验收计划_v1.md:严格原STEP113 STATIC_RC >=90秒、新APK源码SHA/设备SHA、Cache Build/Reuse/Fast及120帧24Gate、原63层与C、Source/Style/CAMERA mutation和用户画面、失败停止。
- docs/报告/STEP-117_原PBF_Fast缓存准入_Codex完成报告_v1.md:模型全名档位、源码文件/公共ABI不变、证据、静态检查、当前BLOCKED和RDC实际测试步骤,commit/hash留RDC回填。
- 粒子开发记录.md 末尾中文追加,必要README/说明文档修正。
- git diff --check、静态API一致性与中文交付核查。禁止git add -A/git add ./git reset --hard/git clean,不触碰用户两份保护未跟踪文件 docs/40_任务/STEP-110_...和STEP-111_...,保留原Git忽略设备JSON。未经RDC工具真实返回,不得自称Native/Android PASS。最后停止交RDC进行独立代码审计、scoped commit/push/remote verification及一次集中Android build/installation/test。

View File

@@ -0,0 +1,33 @@
# STEP-119 Symbol稳定结果证书与R首次Build/Fast安全准入
日期2026-10-11。用户批准立即继续实施上一轮讨论的Shared Core方案,不是只读调查。
项目 /Users/tei/dev/MapLibreParticleLab,particle/main基线869930046b11dbeb3b147e3be952a2e847316292;Integration /Users/tei/dev/navsea-particle-integration已同步origin/main=80667d6c343411d03a760425438e3d292f452118。实际Codex须GPT-6.1-Sol High(gpt-6.1-sol,model_reasoning_effort=high)。
Codex负责开发、定向非Jest测试源码、中文文档,不执行Gradle/ADB/Native大编译/Bazel/Jest/commit/push。RDC独立核验、限定Git收尾并按用户授权集中执行一次APK build、install和真实设备验证。
## 优先读取
根AGENTS.md、PARTICLE_BASELINE.md、docs/00_项目规范、docs/00_项目总则README和4份当前总则;Integration的instructions/PARTICLE_PROJECT_INSTRUCTIONS.md、README.md、WORKFLOW.md、coordination/TEAM_MODEL.md、contracts/、rfc/、adr/、status/、compatibility/。STEP117根因调查 docs/20_调查/STEP-117_PBF_R首次缓存Build安全证据_v1.md 与STEP118真机报告 docs/60_测试/STEP-118_潮汐清理新APK设备实测结果_20261011.md及STEP112-116关键报告。必须逐段核对src/mln/text/placement.cpp、render_orchestrator.cpp、renderer_impl.cpp、render_tree.cpp、retained_underlay_cache.cpp、include/mln/renderer/render_segment.hpp、RenderSymbolLayer/SymbolBucket/atlas/shader、24FastGate/RevisionBook及现有测试。
## 已证实
STEP118原生产63基础PBF图层测试副本只排除一条失效tide-spots(用户允许),保留其它62基础层、天气2层=Native64;R[0,62)、C[62,64),missingSourceIds=[]、R fallback=static-placement、R Cache Build0/Reuse0;90.008s FastDelta0 FullDelta942;PlacementGatePassed=false,24Gate mismatch0。当前源码perSegment Gate对Symbol要求 !placementChanged && symbolFadeChange==0 && !placementTransitionActive;Full新排布置changed=true,recent复用设stale->hasTransitions=true,而Fade完全稳定时通常>=1而非0。独立global retainedPlacementStable/retainedStaticStable/retainedSupported/particlePreparedSceneSupported又阻断。首次Build与Fast复用需要分开。
旧设备真实证据在Git忽略artifacts/maplibre-particle/android-validation/step113-retest-20261011_140448/static_rc_no_tide/result.json和screen.png。新测试APK源commit=3051196054c7b69815c298040dae61d26499d9f2、SHA=44afcfabe0cf4f1ec93f35899241bd73093aa6db4b5cb82ca2120dd531b8f3e9。现HEAD较新只包含报告。
## 本次必须优先真正实施的内容
1. 在平台无关的Shared Core内部区分Placement计算调用与最终Renderable Symbol结果变化,建立可检验稳定状态、revisions/epoch/证书。不能用单一布尔值代替真正GPU输入一致性,不能把placed结果布尔不变当成vertices/opacity/atlas全部一致。保留跨瓦片collision、text/icon、variable anchors/方向、fade、glyph/icon/atlas、shader uniform/tweaker及绘制层序语义。必须检查GL和Metal两侧shader的symbolFadeChange clamp行为。普通Full应完成placement、updateBuckets/vertices、processChanges、tweakers/uploads,才决定可安全建立R缓存(同次或下一帧)。Build/reuse需要匹配版本,任何可影响像素的异步状态立即invalid并走Full。
2. 解决Placement::placementIsRecent / setPlacementStale / hasTransitions语义和symbolFadeChange的>=1状态对首次Build的逻辑死锁。重新计算 != 真正GPU结果变化;stale不是强制正在fade。fade未完成仍必须阻断,所有真实动态Symbol/tiles/glyphs/Source/style/camera/viewport/pixelRatio/context/Annotation更新都fail-closed。不得简单常置false或单改renderSegmentPlacementGatePassed、删除24Gate、修改计数或强行把其它Symbol放进C。
3. 必须同时审查并修复global/perSegment稳定性、revision/state跟踪和preparedSceneSupported,让首次R在安全的Full后建立Build,Reuse后才能Fast。缺失证明的Symbol子类严格拒绝,不能用概括性稳定状态假设可缓存。尽可能真正做到原62基础层静止时Build>0、Reuse>0、FastDelta>0;若架构上无法证明,明示BLOCKED,安全停止危险路径,但应尽量完成有证明的实现。
4. 非Jest C++定向测试(已登记test/renderer文件),覆盖真实PlacementController生命周期、fresh->settled->build->reuse、fade过程拒绝、相机/缩放/旋转、纹理字形异步、新Source/style、Annotation、Context、tile/wrap/crossTile collisions、跨段LayerGroup安全重建;不能把无GPU predicate当真正GPU等价。
5. 不改RN/JNI/ObjC/Java/Kotlin公共API与ABI、正式PBF/Source/MapLibre外部URL或NavSea/iOS业务、不变更Core-owned timeline/playback、不能改天气层语义。跨项目公共接口新语义须Integration RFC/ADR授权,必要则只报告并保留旧合同。
6. 保持原STEP113 63 PBF和新STEP118 STATIC_RC_NO_TIDE派生测试、原24Gate、现有真实version/SHA的严格核对。不能新增虚构tide数据。
7. 不触碰用户两个未跟踪 docs/40_任务/STEP-110_...与STEP-111_...,不git add . / add -A / reset --hard / clean;不修改Git忽略旧真机证据。Codex不运行Gradle/ADB/Jest/Native大编译、也不commit/push,最后交RDC。
## 必做中文输出文件(缺一不得完成)
- 实际Core实现+必要非Jest C++测试源码及直接相关诊断/README改动。
- docs/20_调查/STEP-119_Symbol结果证书与R缓存Build实现依据_v1.md:真实渲染生命周期、核心不变量、结果证书如何绑定真实GPU绘制、反例、风险。
- docs/50_Step记录/STEP-119_Symbol缓存实际实现_v1.md:状态、变更、验证、下一步。
- docs/60_测试/STEP-119_原PBF_FAST实机联合验收计划_v1.md:一次build/install,新STATIC_RC_NO_TIDE >=90秒真实Build/Reuse/Fast+24Gate mismatch0,原63负对照及source/style/camera/glyph/annotation/context负例,视觉核验与fail closed。
- docs/报告/STEP-119_Symbol缓存_Codex完成报告_v1.md:真实模型档位、文件、已实施/未实施、清楚BLOCKED或IMPLEMENTED_WAITING_ANDROID、静态检查、RDC命令、git commit由RDC负责。
- 粒子开发记录.md在末尾中文追加。
Codex只跑轻量静态范围和git diff --check;不可宣称Native/Android/iOS/用户视觉PASS。
## RDC严格实机目标
有实质Shared Core行为修改且独立审计通过后,Mac标准 ./tools/particle_android_validation/build_validation_apk.sh 一次Android OpenGL x86_64 APK;设备FZG1-2 ADB serial 192.168.200.139:5555且全部命令显式-s;运行 python3 -B tools/particle_android_validation/verify_step113_fast_path.py --scenario STATIC_RC_NO_TIDE --seconds 90。必须真实R Build>0、Reuse>0、FastDelta>0、Symbol画面/24Gate一致、更新时缓存失效与Full正确。失败最多5轮当前步骤修复,不能跳过并声称通过。无行为改动则不重编毫无意义APK。

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,31 @@
# STEP-108 V23 同源统一正式发行 v1
- 日期:2026-10-09(JST)
- 状态:**CLOSED / GSRL PASS / RELEASED(设备验收独立进行)**
- 冻结源码提交:`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:release.sh 23 finalize exit 0;正式发行 Tag 已成功 push;远端 main 和 tag 回读验证通过。
## 未验收及禁区
- STEP-107 XCTest 与 iPad 直接点击崩溃场景尚未运行;不得宣称已实机修复。
- V23 Android 实机、React Native 宿主整体集成、iOS Metal 真机 NOT_TESTED。
- 原 V22 Tag/Source Lock 与发行制品不覆盖;禁止新建 worktree、擅自纳入无关 dirty 文件。
## 最终发行验收证据
- 实际构建源提交:87f3d1c59a52581fb8339f0ea7d106b9f4b94fef。
- 发行清单提交:a0a4787b4379ab2819943d20ce05a7ae760a88c3,Tag maplibre-particle/rn/13.6.1-particle.23 指向该 Commit。
- SwiftPM Tag 6.29.0-particle.23 公网可读,二进制 checksum 匹配。
- 六个制品再次经 curl 公网下载,全部 HTTP 200,长度、SHA-256 与 GSRL Source Lock 全部一致,TASK_PASS_V23_GSRL_FINAL_VERIFY。
- 最初独立 Python urllib 请求遇到 HTTP 403,改用正式发布脚本使用的 curl 请求后六项全部 PASS,文件没有被重新上传或修改。
- V23 GSRL 发行 CLOSED / PASS,STEP-107 XCTest、iPad 事故重放、Android/RN/iOS 设备测试仍 NOT_TESTED。
- 本补充属于发行 Tag 之后的审计提交,不更改发行 Tag、SOURCE_LOCK.json 或公开二进制。

View File

@@ -0,0 +1,35 @@
# STEP-109 V23 CPU FieldSnapshot 深比较快路 v1
日期:2026-10-10(JST)
状态:进行中(源码已改;Mac Native 编译及设备 CPU 对照待验)
## 基线
- 设备:Mac.lan,仓库 /Users/tei/dev/MapLibreParticleLab。
- 分支:particle/main;起点 HEAD:5b8bdfbfbb4263fe12a060e77bc310cf77f27d61;起点工作区干净。
- 已发布基线:Android / RN 13.6.1-particle.23,iOS 6.29.0-particle.23;正式 V23 Tag、Source Lock 及制品均保持不变。
- V21 FZG1-2 曾有天气持续运行高 CPU 与疑似浮点数组比较样本;不是 V23 的测量结果。
## 调查依据
- render_particle_validation_layer.cpp 稳定每帧通过 sameFieldGpuEndpoint() 比较 T0/T1,比较包含整个 vector 与 valid。
- render_weather_color_layer.cpp 稳定每帧通过 sameFieldGpuContent() 比较完整 scalar/vector/valid。
- V23 源码中已保留 STEP-090 Fast Path;STEP-102/106 已处理部分 RenderSegment Placement 语义,不对 V23 再次假设 cache-invalid。
## 本次非正式修改
- [x] ParticleGpuResources 记录与真实 GPU T0/T1 槽位相应的 shared_ptr<const ParticleFieldSnapshot>。
- [x] WeatherColor Resources 同样记录两帧快照身份。
- [x] 相同不可变 shared_ptr 且资源代际未变时,跳过 O(N) 深比较。
- [x] 新快照对象、窗口更换、旧资源不存在时保留严格内容/端点对比。
- [x] 只有真实纹理安装成功或严格等价比较后更新资源绑定身份;clear、context loss 与资源重建自动丢弃旧身份。
- [x] 保留 T0/T1、MIX、时间跳转、窗口重用、编码/内容身份、双平台 Renderer 语义。
- [x] 未改公共 API、RN、JS/TS、RenderSegment 安全 Gate、任何发行制品。
## 构建与验收
- [x] git diff --check PASS;只修改 2 个 C++ 文件。
- [ ] Mac Metal Native Core 编译:**NOT COMPLETED / 用户验收前主动中止**。`bazel build -c opt --//:renderer=metal //:mbgl-core` 因触发 649 个通用 Core actions 的大量重编译,尚未走到构建成功/失败结论;已中断 Bazel 客户端并核对其 PID 退出。日志 /tmp/maplibre_step109_cpu_field_identity_bazel_20261010.log。此项不能写为 PASS。
- [ ] C++ directed regressions(snapshot identity、new-object differing payload、window shift、clear/reload)。
- [ ] Android OpenGL / FZG1-2 同场景 V23 原版与修改版 CPU A/B:尚未执行。
- [ ] iOS Metal iPad 实际天气场/时间窗回归:尚未执行。
- [ ] 真实性能目标:至少比较相同场数据、帧率、图层与设备条件下的平均进程、RenderThread、JS CPU;同时检查 FieldTextureUpload / SimulationReset 计数无异常增加。
## 交付边界
本 Step 不创建新发行版本,不覆盖不可变 V23 产物。开发态代码已通过 scoped commit `2aae38f4023f2ef682c9039c93dfa1e96b0e6e1b` 并 push 到 origin/particle/main(远端 SHA 回读 PASS);这不是 Native 编译、性能或正式版本验收。正式使用仍须完成编译/设备测试、另立版本并执行 GSRL Source Lock、SHA-256、接收验收。若编译失败,则先记录错误并修复,不得宣称优化 PASS。

View File

@@ -0,0 +1,31 @@
# STEP-110 R0 Placement 跨段归因只读调查 v1
- 日期:2026-10-10
- 状态:**PARTIAL(调查性质;非修复/性能 PASS)**
- 仓库:/Users/tei/dev/MapLibreParticleLab;分支 particle/main
- 调查起点 HEAD:2df504cdc0403bdad85d3f31f16fd54a8744abac
- V23 Source Lock:87f3d1c59a52581fb8339f0ea7d106b9f4b94fef;V23 tag peeled commit:a0a4787b4379ab2819943d20ce05a7ae760a88c3
- Codex 实际模型:gpt-6-astra / high(CLI 启动日志确认),CLI 0.161.0;Codex 调查完成,文档交付会话多次卡住,之后由 RDC 整理、落盘并独立审查关键源码。
- 主报告:docs/20_调查/R0_Placement跨段归因与缓存安全边界调查_v1.md
## 已完成
1. 读取 STEP-110 任务书、根 AGENTS/基线、项目总则,以及 STEP-088/089/090/092/094/099/100/102/106/108/109。
2. 比较 V23 GSRL build Commit、Tag commit 和 main;核心六份 Renderer/Placement/Cache/Fast Path Git Blob 完全一致。
3. 只读追踪 Renderer::update、RenderOrchestrator::createRenderTree、CrossTileSymbolIndex、PlacementController、RenderTree、per-segment Placement Gate、Retained Cache、Fast Path。
4. 读取 STEP-106 本机既有 T1/T2 设备日志,确认 placement-free Retained 段复用,不推定含 Symbol R0 可复用。
5. 回答 Q1–Q4、七个反事实场景、结论矩阵与风险优先级,并明示 cannot prove 的范围。
6. 不涉及 Native/JS/Kotlin/测试代码修改;未运行测试、Jest、构建、Gradle、CMake、Bazel、ADB 或设备验证。
## PARTIAL 原因
- NavSea 生产 R0 逐 Symbol placement/fade/visibility 等价证据缺失;只读源码不足以证明 C1 的 Placement 变化完全不影响 R0。
- 本次不允许插桩、修改 Core 算法、重测 NavSea 或新建安全机制。
- 需要另行授权 Native 内部诊断/安全判据,再进行跨 Source 碰撞与真实 CPU/视觉 A-B。
- 本地报告写入由 RDC 完成,不能误称 Codex 已直接交付文档。
## 结论及停手范围
R0 含地名 Symbol,V23 全局 Placement/Fade Gate 的保守失效与现有设计相容;**当前没有证据判定为既有不变量遭破坏的 Bug**。对 placement-free R 的 STEP-102 隔离已通过历史测试,不能直接解除 placement-bearing R0 的 Gate。
Git 收尾证据以 STEP-110 完成报告和最终 RDC Git 验收为准;禁止提交原来未跟踪的 STEP-110 任务书或其他无关文件。本任务无正式 Binary/Source Lock 发行。

View File

@@ -0,0 +1,21 @@
# STEP-111 R 段 Placement 结果等价调查 v1
- 日期:2026-10-10
- 状态:**PARTIAL;调查未完成,Native 未实施**
- 仓库:Mac `/Users/tei/dev/MapLibreParticleLab`,`particle/main`
- 任务起点 HEAD:`7ec9ac8c1635c7a3b607abc2ed4be4fd764e7b6d`
- 模型:Codex CLI 0.161.0,实际可见 TUI `GPT-6 Astra high`。
- 执行:Codex 两轮源码探索后停止新增事件;尝试同会话缩窄为直接出报告,仍无报告生成。会话已人工中断并退出,未取得可靠退出码,不能认定正常完成。
- GPT/RDC 随后只读核验 `render_segment.hpp:85-90`、`render_orchestrator.cpp:775-810`、`renderer_impl.cpp:638-686` 与 STEP-110,并编写 PARTIAL 评估;不是 Codex 完整调查报告。
## 有证据的阶段结论
- Retained Cache 已逐 R 管理,但含 Symbol 的 R 使用全局 Placement/Fade Gate,未依据该 R 的最终 Symbol 结果判等。
- 可以把“全图 Placement 后按 R 判断纹理是否复用”作为候选优化路线,不把 Segment 当作独立 Collision Domain。
- 难度初估:诊断 2–3/5、shadow 比较 3/5、安全独立 Reuse 4/5、分段增量 Collision/Placement 5/5;数字仅为工程评估,未通过真实实现检验。
## 未完成、禁止夸大
- 未形成稳定/完整的 per-R 视觉等价签名设计或生命周期证明;无新 Native 插桩、运行数据、编译、测试、Jest 或设备 A/B。
- Codex 未生成预定三份交付文件;由 GPT/RDC 记录 PARTIAL,不写“Codex PASS”。
- 原有未跟踪 STEP-110 任务书和本次 STEP-111 任务书均保留,不参与文档 Git 提交。
- 主报告:`docs/20_调查/STEP-111_R段Placement结果等价与独立刷新可行性调查_v1.md`。
- 后续先审查稳定 ID、Fade 和 per-R 最终数据所有权,再考虑 shadow-mode 与缓存 Gate 改造。

View File

@@ -0,0 +1,56 @@
# STEP-112 V23 粒子 Full Render 与 Symbol 缓存调查 v1
日期:2026-10-11(JST)。状态:**完成(本轮只读调查交付);综合判定 PARTIAL(生产逐帧归因、安全修复与性能未验证)**。
## 目标与基线
只读完成任务A—E、全Gate矩阵、R2状态机、Placement/Fade两种配置、S1—S5及A—D提案;不实施Native修复。
- Mac.lan / Darwin 24.6.0 / x86_64;仓库 `/Users/tei/dev/MapLibreParticleLab`。
- 起点及结束基线:`particle/main@41b416c261fa72bd21171328045036a124694f47`。
- 模型已从本会话rollout确认 `gpt-6-astra/high`;无切换、无子代理。
- Integration HEAD `80667d6c` 采用用户提供的RDC核实事实,只读相关契约/决议。
- V23 Android/RN `13.6.1-particle.23`、iOS `6.29.0-particle.23`。对V23构建源码 `87f3d1c…` 的相关路径diff只有STEP-109两Layer快比较;本调查核心Gate保持一致。
## 必做项与结果
- [x] 按序阅读AGENTS、基线、8份规范、总则README和四份有效总则、当前任务与STEP-102/106/109/110/111。
- [x] A:Core/Android/iOS请求→Fast/Full→GPU→Observer/Idle调用图,区分forecast与粒子cadence。
- [x] B:24项Fast Gate,逐项来源、通过条件、首错、Tick影响、Full恢复与现场证据强度。
- [x] C:R2结构能力、Source/local/global revision、evaluate/BuildPending/资源/成功/失败/合成状态机。
- [x] D:transitions开关、300ms/30ms/zoom、fade公式、8组合真值表、GL/Metal opacity链。
- [x] E:S1—S5逐帧/事件推演和布局对照;原RCRR完整边界缺失明确标未知。
- [x] A—D提案:优先级、首建/scene/C覆盖、正确性、资源代价、候选文件、1—5复杂度、双平台最小验证;D列Interface Blocker。
- [x] 先创建框架、分阶段追加证据,最终三份交付文档和开发记录追加已落盘。
- [ ] FZG1同帧全Gate/配置/CPU归因、生产Symbol结果等价、GL/Metal运行验证:本轮禁止执行,留给另行授权阶段。
- [ ] Native修复、插桩、性能改善:本轮明确未授权,未执行。
- [x] 指定四文件scoped commit/push及远端HEAD:由RDC在Codex调查后执行,首次提交与Push已核验;实际记录见下文Git收尾补记。
## 主要源码结论
正常Continuous Full中,含结构性Symbol的R要求 `!changed && fade==0 && !transition`;重算分支changed=true,不重算分支setPlacementStale令transition=true,因此首次Build没有准入窗口。即使全体Source静态也成立。`fade==0`不是Fade终态;关闭transitions的正常Full为true/1/false,现场true/0/false与开启配置的稳定重算相容。
R2 invalid令下一次Fast在cache-invalid返回;另有全局prepared scene及PREFIX中普通Symbol/Line C的独立阻塞。Full可以复用R0/R3,不等于全图全部重画。上述不证明安全放宽Gate,也不证明现场每次Placement都改变R2像素。
## 允许范围与必做输出
仅四文件:
1. `docs/20_调查/STEP-112_V23粒子FullRender循环与Symbol缓存准入全面源码调查_v1.md`
2. 本阶段记录。
3. `docs/报告/STEP-112_V23粒子FullRender与Symbol缓存调查_Codex完成报告_v1.md`
4. `粒子开发记录.md` 仅追加。
两份未跟踪STEP-110/111任务书保持原样。未改源码、测试、Style、脚本、跨项目文件、版本或发行产物,未清理dirty或改变branch/worktree。
## 检查、阻塞与下一步
只执行只读源码/Git比较和文档范围、完整性、空白检查;实际结果详见完成报告。编译/链接/codegen/测试/Jest/Gradle/APK/ADB/设备操作全部未运行,无新制品。
剩余证据:FZG1实际Placement options和24项Gate逐帧序列、原始完整plan、Source/forecast/Annotation来源分摊、Symbol最终GPU输出安全等价。下一步仅建议授权A低开销同帧诊断与B生命周期安全证据设计;不得先删Gate。公共明确Full命令语义由Integration决定。Codex不负责Git收尾;STEP-112任务书要求RDC独立完成仅文档scoped提交、推送和远端验收,结果以Git收尾补记为准。
## RDC Git 收尾补记(2026-10-11)
- RDC独立检查 branch=`particle/main`、起点 HEAD=`41b416c261fa72bd21171328045036a124694f47`;仅stage任务规定的三份STEP-112文档及`粒子开发记录.md`,`git diff --cached --check`通过,恰好四文件,原两份未跟踪STEP-110/111任务书不触碰。
- 首次文档提交:`abc137ca9637abc18be8c64cd98f2c7f1ed68704`,message=`docs: investigate STEP-112 particle full render and symbol cache`;普通`git push origin particle/main`退出码0;`git ls-remote origin refs/heads/particle/main`返回相同SHA,首次远端验收PASS。
- 本段是RDC在Codex完成后补写的Git事实;补记自身形成单独文档提交,最终HEAD与远端一致性以RDC最终回读为准,不影响源码调查PARTIAL或禁止运行的设备/构建项目。

View File

@@ -0,0 +1,100 @@
# STEP-113 FZG1 PBF Layer 与 Fast Gate 诊断 v1
日期:2026-10-11(JST)。状态:**进行中;源码交付PARTIAL,等待RDC独立编译/设备验收,113层完整等价缺证据。**
## 目标、基线与边界
优先B:每次真实Fast尝试可靠保存24 Gate、首错及失败全集;再交付真PBF专项场景、低频UI和机器可读导出。
- Mac `particle/main@c6d13e1059d464d312ac33acac87967300fa5dc4`,仓库 `/Users/tei/dev/MapLibreParticleLab`。
- 已实际核实模型 `gpt-6.1-sol/high`;NavSea只读 `ad9aa043`,Integration本地 `80667d6c`。
- 当前源码版本 `13.6.1-particle.23` 未改;本次修改未发行。
- 只修改Shared Core诊断、Android Test App、相关C++测试源码、本Step/报告及开发记录追加。禁止改变Gate/Placement/Fade/Cache安全判定、公开RN方法、NavSea/Integration、构建基线、正式发行或Git提交推送。
- 既有STEP-110/111/113未跟踪任务书原样保留,无branch/worktree变更。
## 必做项与当前结果
- [x] 同一次已计算input保存24项状态掩码与生产首错,一致性计数明确,不重复副作用函数。
- [x] 120帧有界环形数据、生命周期及窗口聚合,所有Gate记录;pre/post明确分离。
- [x] per-R实际pre cache validity/revision和两phase的cache build/reuse/invalidation/fallback/原因/执行状态;每phase最多16R,超限显式。
- [x] 使用现有Native JSON出口追加可选fastGateTrace;共享锁快照,UI不访问Renderer mutable state。
- [x] 明确reset位置;普通Full miss不清历史。新增4个C++回归用例,尚未运行。
- [x] 正式URL专项PBF63/STATIC_RC/PARTICLE_OFF/PARTIAL_RCRR/SOURCE_CHANGE场景源码;保留原场景和STEP-102动态fixture。
- [x] 运行时原63层完整定义/顺序保存检查,真实Sprite/Glyph/Tile URI检查、Style fingerprint/版本/Layer表/plan接受及最终解析原因导出。
- [x] 固定camera/forecast请求、30fps目标;禁用手势;静态轮询不改Symbol/Source/forecast。
- [x] UI每3秒单次query、30秒摘要、手动最近snapshot导出;Logcat标签STEP113Gate,Base64分块防截断。
- [x] 中文完成报告、精确RDC命令、开发记录末尾追加与静态范围检查。
- [ ] 113层全量生产Layer/Source/显隐/业务数据与现场四段数字边界:缺同版本完整快照,未复制。
- [ ] Native/Kotlin编译、定向测试、APK、FZG1-2资源/层/逐帧/性能/开销验收:本轮Codex未执行。
## 证据等级与剩余风险
SOURCE-PROVEN:NavSea URL配置、当前源码RC选择器、shared天气片段ID/type/source。TEST-APP-STRUCTURAL:保序63层和部分RCRR检查代码、额外Source探针、既有canonical风场。DEVICE-MEASURED:本轮无结果。NOT-VERIFIED:正式服务回读(本机域名无法解析)、设备实际Layer表/113层全等价、构建与性能。
原始JSON63层不包含SDK可能追加的Annotation等内部层;必须使用实际Native styleLayers总数与Layer ID解析边界。部分RCRR锚点不同于现场[0,13)/[13,33)/[33,107)/[107,113),不能把结构测试说成同等画面。NavSea当前RenderSegmentHost源码RC与该历史现场RCRR_PREFIX分别记录。
pre里的changed/fade是上一完成RenderTree快照;当前Controller transition另存preFastPlacementTransition。Fast post不是新Placement结果。Debug simulationAdvance是进程级计数;实际forecast时间未新增Native回读接口,导出null并保存accepted请求。环形覆盖会丢弃最早详情但保留聚合;内存字节/CPU开销未实测。不能从零Fast提升或APK编译成功直接判性能PASS。
## 独立验收填写栏(由RDC追加真实结果)
| 项目 | 本轮状态 / RDC证据待填 |
|---|---|
| 源码diff/hash、分支/HEAD/dirty | Codex交付未提交;RDC填写制品对应源码 |
| 设备serial/主ABI/ABI list | 目标192.168.200.139:5555;未读取 |
| Native配置/编译/4个新用例与相关回归 | NOT RUN;填写命令/exit/耗时/日志/首错 |
| APK build-info/ZIP/ABI/字节/SHA/签名/安装 | NOT RUN;标准Validation脚本,禁止复用失败旧APK |
| PBF63完整资源、实际Layer manifest | NOT VERIFIED;填写SDK回读fingerprint和原/实际层数 |
| STATIC_RC首错/24Gate/window/mismatch | NOT VERIFIED;mismatch必须0,保存120帧详情 |
| PARTICLE_OFF/固定预报/camera | NOT VERIFIED;保存对照窗口和请求接受结果 |
| PARTIAL_RCRR实际四段/Layer ID/缓存状态 | NOT VERIFIED;明确部分等价,不能补写113层PASS |
| SOURCE_CHANGE基线/单次变化/pre-post | NOT VERIFIED;记录Source mutation和两次导出 |
| reset/前后台/退出/旧STEP-102回归 | NOT VERIFIED;不能以进程正常退出替代场景检查 |
| 内存/trace开销/CPU/FPS/截图/crash | NOT VERIFIED;不能用单制品构建成功替代 |
| 完整113层及原始现场selector | 缺证据;另需真实同版本快照 |
## 构建、检查与必做输出
Codex仅执行只读调查、文本一致性/范围检查、git diff --check与新增文件空白检查;无编译、C++运行、Jest、Gradle/APK、ADB、设备、发行或Git提交推送。
精确macOS Shell构建/ADB/场景/Logcat/私有JSON回读及Native定向命令见:`docs/报告/STEP-113_FZG1_PBF_Layer与FastGate诊断_Codex完成报告_v1.md` 第5节。Android ABI先匹配标准脚本x86_64,所有设备命令固定 `-s 192.168.200.139:5555`,由RDC执行。
交付源码/测试、本记录、上述完成报告、粒子开发记录追加已落盘。下一步仅由RDC独立验收;失败保留首个真实error及完整日志,再按本任务范围修复。不得借此放宽Fast/Placement/Cache安全判定或进入正式发行。
## RDC 独立验收补记(2026-10-11,设备证据)
以下是 Codex 退出后 RDC 新执行的证据,不修改上文 Codex 未构建/未实测的历史结论。本次阶段判定:**PARTIAL;Android 构建 PASS;五场景稳定基线采样 PASS;113 层等价与性能 NOT VERIFIED**。
- 环境:Mac、particle/main;构建源码基于 c6d13e1059d464d312ac33acac87967300fa5dc4 加 STEP-113 未提交改动,build-info 明确 git_dirty=true。此 APK 不是 Source Lock / 正式发行。
- 标准 build_validation_apk.sh 于 2026-10-11 08:35:08 JST 退出码 0;Gradle BUILD SUCCESSFUL in 19m 22s;Java 17.0.20.1;OpenGL/x86_64;ZIP/ABI/体积/SHA 自检通过。
- APK:artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk;66,587,454 bytes;libmaplibre.so 40,033,976 bytes;SHA-256 0054248bc3872e8670df4e9a10d11f1d65c0912a0482586cd7a247e880a6eaef。记录:同目录 build-info.txt、build.log。
- 设备:FZG1-2,ADB 192.168.200.139:5555,主 ABI x86_64;明确 -s 的 adb install -r 返回 Success,未卸载数据;设备安装后版本 13.6.1-particle.23。正式 PBF Style 模式运行并加载。启动前数秒 sceneReady=true 但 Native Layer=0 / plan not-configured 是加载暂态;只接受之后的 Native Layers 非零、planValid=true 稳定记录。
- 五个场景均由 ADB 明确 serial 启动;STEP113Gate Logcat Base64 summary 成功还原。每场景实际设备稳定状态的 decisionMismatch 都是 **0**。Full/Fast/Attempts 为生命周期累计,并非精确 60 秒窗口。
| 场景 | Native Layers | Attempts | Full | Fast | Retained 及关键证据 |
|---|---:|---:|---:|---:|---|
| PBF63 | 64(63+内部层) | 106 | 106 | 0 | R[0,64) Build0,unknown-dependency;无粒子持续驱动 |
| STATIC_RC | 66 | 876 | 875 | 0 | R[0,63) Build0,unknown-dependency;unsupported-continuous-layer、unsupported-scene 各876 |
| PARTICLE_OFF | 66 | 70 | 70 | 0 | no-particle-repaint=70;非相等时长性能对照 |
| PARTIAL_RCRR | 66 | 862 | 861 | 0 | R0[0,3) unknown-dependency;R2[39,64) 与 R3[64,66) Build0/static-placement;两个 unsupported Gate 各862 |
| SOURCE_CHANGE | 67 | 888 | 888 | 0 | 基线已记录;**没有执行菜单手动 Source mutation** |
- STATIC_RC 同帧失败全集汇总 CacheInvalid=870、revision-mismatch=871、placement-or-fade=780;PARTIAL_RCRR CacheInvalid=856。这些失败条件可能在**同一帧**并存,不能累加为帧数。即使修复缓存 Build,也还有 #22/#23 与其它独立 Gate。
- 设备摘要 JSON(不纳入 Git):artifacts/maplibre-particle/android-validation/step113/ 下的 pbf63-summary-ready.json、static-rc-summary.json、particle-off-summary.json、partial-rcrr-summary.json、source-change-baseline-summary.json。它们只是低频 summary,不是导出的完整 120 帧。
- 仍 NOT VERIFIED:原 NavSea 113 层完整 Layer/Source/显隐及真实四段边界;实际 PBF/Sprite/Glyph 画面截图;Source 单次变更后前后对照;120帧手动详情导出;C++ GTest 运行;reset/后台、旧 STEP-102 场景回归;CPU/GPU/诊断开销;生产性能提升、正式发行。不得把部分结构等价当成现场复测或将 STEP-113 判为 CLOSED/PASS。
- 下一阶段必须分别处理 retained first-build 的安全准入、unsupported continuous layer、unsupported scene;不能直接删除 Gate 放行。
## RDC 第二轮设备验收(2026-10-11:完整窗口与Source变更)
**本轮状态:设备逐帧诊断 PASS、单次Source变更链路 PASS,完整113层等价与生产优化仍 PARTIAL。** 本记录追加在原有RDC基线结果之后,旧内容中的“未执行单次Source mutation/120帧导出”是第一轮结束时的历史状态,不代表第二轮。
1. 在设备 FZG1-2、ADB 192.168.200.139:5555、版本 13.6.1-particle.23 上运行 SOURCE_CHANGE;通过屏幕中的 STEP 093 → STEP-113 → EXPORT LAST SNAPSHOT,使用明确serial的 adb exec-out run-as 回读 App 私有JSON;文件字节分别为变更前 277980、变更后约272KiB。精确位置:artifacts/maplibre-particle/android-validation/step113/source-change-before-full.json 和 source-change-after-full.json,均保留在 Mac 本机、未纳入Git。
2. 变更前 trace:lifecycle 1,attempts 13859,capacity/windowCount 120,最新119个完成的Full帧、0 Fast帧、最后1帧为pending、decisionMismatch=0;变更后 trace:同lifecycle,attempts 17476,windowCount=120,同样119个已完成Full、0 Fast、decisionMismatch=0。相同frame中前置状态 pre 的 placementBasis 标为上一完成RenderTree,post 完成Full才使用当前Full tree;knownMask覆盖24条Gate。
3. 在 SOURCE_CHANGE 场景操作一次 MUTATE PROBE SOURCE ONCE。Logcat解码的 source-command:mutation=1;变更后导出 commands.sourceMutationCount=1;Native lastMutation.sourceId=step113-source-probe,affectedSegmentIndices=[1],事件 TileMutation。变更前 lastMutation.sourceId=navsea_delivery,affectedSegmentIndices=[0]。累计 content-mutation Gate failure 从12变为14,但中间同时有正常瓦片更新,**不得把全部增量单独归因为这一次probe**。120帧环形窗口会覆盖发生瞬时变更时的精确帧,故本轮只确认命令/Native依赖定位,不声称捕获到单次Mutation帧的全部过渡时序。
4. 从变更后120帧中统计119个 completed 样本:首错 cache-invalid=119/119;同一帧 Gate #3 cache-invalid=119、#4 revision-mismatch=119、#12 static-unstable=105、#13 placement-or-fade=105、#22 unsupported-continuous-layer=119、#23 unsupported-scene=119。24Gate shadow 对应生产首错持续吻合,decisionMismatch=0。该统计是窗口而非全应用时间的FPS度量。
5. PBF同源核验:原始63层按SDK回读生产Style逐层定义保序、baseLayersPreserved=true;spec version=8,源Style名称 NavSea Delivery Full Semantic ExtentFix;productionFingerprintSha256=e0f9c2d1df38ae5d5953b561c52efe2f8590336de80b988e1d2935580d30b996(SDK回读序列化JSON,不是HTTP包字节)。SOURCE_CHANGE运行时67层,连续段[63,67)依次是 weather-color、raster-particle、测试circle、内建 org.maplibre.annotations.points symbol。STATIC_RC的连续段[63,66)同样包括内建annotation symbol。现有 continuousRangeSupportsParticleFastPath 要求其范围内**每一层**均支持 retained dynamic range;因此这些普通circle/symbol使Gate #22持续失败;这项结论为“源码判定+本机层清单”,未进行绕过Gate的运行实验。
6. Gate #23 supportedScene 源码逻辑与 retainedSupported、fastContinuousSupported 及单C配置有关,**#22与#23不是根因完全独立的两次失败**;二者虽为不同Gate,却可能共享同一个不支持的C层根因。Retained R[0,63)仍持续 Build0、unknown-dependency;部分RCRR其它R也有static-placement。不能只消除C阻断就承诺Fast会成功。
7. NavSea只读核对:当前src/map/particle/RenderSegmentHost.tsx 配置 R→C,从 rn-weather-color 开始;旧STEP-112的FZG1 113层RCRR_PREFIX属不同运行配置。同版本113个Layer的完整Source/ID/type/visibility/业务数据清单仍缺,不能把TEST-APP-STRUCTURAL的63/66/67层宣称为113层完整等价。FZG1-2存在debug NavSea安装,但**本轮未启动/修改NavSea用户数据**。
8. Android设备截图已采集:artifacts/maplibre-particle/android-validation/step113/step113-source-change-screen.png(1920×1200),仅归档,未进行NavSea同版本逐像素比对。Mac Native GTest CMake配置尝试退出码1:缺少libuv(记录/tmp/particle-step113-macos-cmake.log);未安装依赖,未进入GTest构建与运行,不记PASS。
9. 后续受保护顺序:先由跨项目负责人获取NavSea同版本113层完整只读snapshot及真实selector;然后在Test App精准构造Layer/Source/顺序并获取120帧一对一同条件比较;分别建立R Build准入、C范围支持、Scene聚合依赖的源码及像素等价证明;通过后才能在Shared Core按受控范围修改Gate逻辑。禁止在证据不足时关闭 fail-closed Gate。RDC未在本轮运行新Android编译、改变渲染逻辑或做正式发行。

View File

@@ -0,0 +1,77 @@
# STEP-114 Fast Path 安全准入与 Test App 闭环 v1
日期:2026-10-11(JST)。状态:**进行中;源码交付 PARTIAL,运行闭环待 RDC/用户,Symbol R 准入 BLOCKED。**
## 目标、基线与范围
- Mac / Darwin 24.6.0 / x86_64;`/Users/tei/dev/MapLibreParticleLab`;`particle/main@b02e54de3d45fcf612126ae8f037cba204f590ea`。
- 实际 GPT-6.1-Sol(gpt-6.1-sol)/ high 已从本会话执行记录核实。没有模型升级、子代理或另一 Codex 实例。
- Native/Test App 源码版本 `13.6.1-particle.23` 未改;Integration 契约仅只读,RN/JS公共API/ABI、Source Lock、正式发行不变。
- 只修改 Shared Core 内部安全子集判定/缺源诊断、现有非 Jest 测试、Android Test App、本Step中文文档和开发记录追加。
- 禁止 Codex 执行编译/测试/Gradle/ADB/Jest/CMake/Ninja/Bazel、commit/push、发行、跨项目写入或触碰原有 STEP-110/111 文件。
## 按顺序完成的源码工作
- [x] 静态闭环:Test App source类型、R初建失败、mutation/异步布局/资源/Annotation追踪、C/scene复合条件、Placement两分支。
- [x] 本机旧STEP-113完整JSON回读发现:navsea_delivery是Vector;R依赖tide但Native source表缺tide。缺revision默认unknown-dependency不能作为CustomMVT已出现的证据。
- [x] R缺失 RenderSource 提前返回missing-source,仍ready/stable=false、revision0;不补假数据,不放行CustomMVT/未知revision。
- [x] 保守GeoJSON/circle R正向独立场景;source admission/cache GPU算法不改,3个C++用例源码补充结构保护与revision/cache状态机覆盖。
- [x] 非粒子C继续fail-closed;独立scene只在确认没有Annotation实例时移除空内建层,正式PBF五场景原样保留。
- [x] 无Symbol全场的controller stale不影响Symbol像素消费者;限定#13只在完整层表有结构性Symbol时阻止。空表/数量不一致仍保守,隐藏或空Symbol不豁免。
- [x] Symbol R真稳定窗口审查:现有commit/fade证据不足,保留全部Build/scene条件;停止更大结果签名设计,明确BLOCKED。
- [x] 独立FAST_SMOKE、CIRCLE_C_BLOCKED、ANNOTATION_BLOCKED、SYMBOL_R_BLOCKED;菜单/Intent、低频真实Native UI、单次Source/Image/Camera/Annotation/Cache命令与操作前后文件、Reload/前后台/Teardown取证入口。
- [x] 调查、场景/定向验收说明、Step、中文完成报告、粒子开发记录实际落盘。
- [ ] Mac Native配置/编译和3个新用例及指定回归实际运行。
- [ ] Kotlin/Android一次手工编译、真实NativeFast>0、视觉与反向/生命周期设备闭环。
- [ ] Symbol R结果证书、CustomMVT完整revision契约、非粒子C复合等价证明:未实现,维持BLOCKED/fail-closed。
- [ ] iOS Metal实机/性能与生产Fast:NOT VERIFIED,不能由smoke替代。
## 当前结果与限制
没有新的编译、测试、APK、设备、GPU计数或性能结果。SOURCE-PROVEN/IMPLEMENTED与旧RETAINED-EVIDENCE分开;整体PARTIAL,不标Fast Path已修复。旧PBF缺tide诊断修正不代表该R可以Build;即使补源仍受SymbolPlacement保护。
预期FAST_SMOKE的真实Native层序4层、R[0,2)/C[2,4),必须取得Build/Reuse/Full/Fast与24Gate同帧证据。UIsceneReady只代表加载/命令成功,实际Native结构由导出JSON检查。三个负场景继续阻止Fast。Source等操作自动100/500/1500ms取证仍可能错过瞬态,缺证据如实记NOT VERIFIED。
## 已做检查与精确验收入口
Codex仅源码/文档阅读、已有JSON解析、Git只读状态、静态函数/范围/哈希核对、git diff --check和新增文件空白核对。未执行任何构建/测试/设备/Git写操作。
精确命令:`docs/60_测试/STEP-114_独立FastSmoke场景与RDC验收_v1.md` 第2节Mac Native配置/target/过滤器/日志/失败停止;第3—5节用户后续手工Android/ADB、JSON检查和反向/生命周期矩阵。完成报告也列出Native最小命令。配置失败保留现场,不能使用旧runner或自行安装依赖伪造通过。
## 独立验收待填写
| 项目 | 当前事实 / RDC或用户证据待填 |
|---|---|
| 源码baseline/diff/hash与制品版本 | 未提交工作区源码;用户构建前保存明确diff/hash与dirty状态 |
| Mac configure/build/新用例3个与回归 | NOT RUN;记录exit/耗时/真实测试数/skipped/日志/首错 |
| APK/ZIP/ABI/大小/SHA/签名 | NOT RUN;唯一现有Validation脚本由用户手工 |
| FAST_SMOKE真实层表/plan/cache/Fast | NOT RUN;4层R[0,2)/C[2,4),decisionMismatch=0,Fast/Reuse delta>0 |
| Source异步失效/Tile/Image/Camera/Cache恢复 | NOT RUN;保存五phase文件与精确变化帧,不从末态猜Gate |
| Circle C/Annotation/Symbol R负场景 | NOT RUN;保持阻止并具备可见内容与真实更新 |
| Reload/前后台/Teardown/重新进入 | NOT RUN;销毁前文件不是销毁完成证据 |
| 原STEP-113五场景与STEP-102回归 | NOT RUN;保留正式PBF全部定义,缺源改报missing-source |
| iOS Metal设备/像素/CPU/GPU性能 | NOT VERIFIED;不以Mac状态机测试替代 |
| Git提交/推送/远端HEAD | NOT RUN且本轮禁止;后续另行授权RDCscoped收尾 |
## 必做输出与下一步
调查:`docs/20_调查/STEP-114_FastPath安全准入与闭环判定_v1.md`。
验收:`docs/60_测试/STEP-114_独立FastSmoke场景与RDC验收_v1.md`。
报告:`docs/报告/STEP-114_FastPath安全准入与TestApp闭环_Codex完成报告_v1.md`。
以上及本记录、源文件、开发记录已由Codex实际生成。接下来由RDC独立审计并执行MacNative定向验证;用户一次手工Android/设备闭环。若缺libuv、编译失败或真实Fast仍为0,保留首错/24Gate再回交修复;不得继续扩大Symbol/CustomMVT/C放行或进入发行。
## RDC 独立审核追加(2026-10-11)
**结论:源码/差异/接口静态审核 PASS;Native 与 Android 编译、Fast 帧 NOT VERIFIED;整体 STEP-114 PARTIAL。**
- RDC 独立阅读本轮 Native diff、C++ 新用例、STEP-114 Android Test App 新场景与菜单、生命周期接线;git diff --check 退出码 0。本轮只有范围内的五个 tracked 改动和新增 Kotlin/中文文档;原 STEP-110/111 未跟踪任务书不修改、不暂存。
- 新增三项 Step114FastPathSafety 用例存在,覆盖无 Symbol、隐藏/Annotation Symbol 和 Source revision/cache 状态机,但**没有实际运行**。
- Step113PbfGateScene.kt SHA-256 为 6e7befeb90ae172aaeff4b636a7640274dfb1065d8c97744cbc6af8d22b4ccae,Step102PlacementFixture.kt SHA-256 为 e44912970dd6353787be7a3e5f9924fe1249e2305d495f1e25e707352c97d3c7,均未变化。
- Java SDK 只读核对实际提供 Style.removeLayer(String) 与 MapLibreMap.getAnnotations()。Kotlin/Gradle 编译未执行。
- Mac 预检:pkg-config --exists libuv 返回 1,与先前 macos-metal CMake required libuv 失败一致。独立 C++ header -fsyntax-only 试验退出码 1,首错为 Xcode AppleClang 找不到标准 C++ 头文件 array,实际在进入项目源码检查前终止。日志存于 Mac /tmp/step114-header-check.log。两项均是环境阻塞,不是本轮 Native 测试 PASS 或已证明代码错误;未安装/修改依赖。
- 实际 Fast Path 命中、正反 GPU 渲染、Android 设备、性能、iOS 均未验证。正式 PBF 仍有 tide 缺源、Symbol R Placement、内建 Annotation C 的阻断。STEP-114 独立最小场景属于待用户一次 Android 手工构建集中验收内容,不能用旧 APK。
- 后续 scoped Git 收尾若执行仅表示 PARTIAL 源码及测试入口已归档,**不代表正式发布或 Fast Path 修复完成**,不打 Tag/Source Lock。

View File

@@ -0,0 +1,22 @@
# STEP-115:STEP-113 原 PBF 专用 APP Fast Path 复测准备 v1
日期:2026-10-11(JST)。状态:TEST-PREPARATION COMPLETE / MANUAL ANDROID BUILD REQUIRED / FAST NOT VERIFIED。
## 任务意图
原 STEP-113 专用 Android Test App 的 PBF STATIC_RC 是 Fast Path 正式验证主体,不以 STEP-114 FAST_SMOKE 或删掉原正式图层的测试代替。STEP-113 的五个场景不修改,不要求 NavSea 113 层完整复刻,但每个结果必须基于真实 Native 的 Fast/Full、缓存 Build/Reuse 以及 Gate 记录。
## 本次已核实事实
1. Integration main 已 fetch 并确认 80667d6c343411d03a760425438e3d292f452118。Particle 当前 main 为 21a10816435a82de3471bdb55ed83223101f5689。
2. 现有 APK 的 build-info 显示构建源码 c6d13e1059d464d312ac33acac87967300fa5dc4;APK SHA-256 为 0054248bc3872e8670df4e9a10d11f1d65c0912a0482586cd7a247e880a6eaef,与设备 192.168.200.139:5555 的当前安装包 SHA 一致。这是 STEP-114 修改以前的旧 APK,不能拿来验收新修复。
3. STEP-114 未解决原 PBF 缺 tide Source、R Symbol Placement 首次 Build 及 C 内建 Annotation Symbol 拒绝,因此不能提前承诺原 STATIC_RC 会进入 Fast。
4. 新增工具 tools/particle_android_validation/verify_step113_fast_path.py;不修改原 STEP-113 Test App、Native 渲染逻辑、RN 公共 API。工具对版本/安装包 SHA fail-closed,按当前进程 PID 解码现有 STEP113Gate Base64 低频摘要并比较帧计数。
5. 使用 python3 -B 解码旧设备 Logcat 的 STEP113 SOURCE_CHANGE 摘要成功,历史累计 Fast=0、decisionMismatch=0。旧 APK 执行 --preflight-only 返回 STALE_APK,exit=3,属于**预期的旧包拒绝**,不是“修复后测试失败”。
6. 没有执行 Android 编译、安装、启动新 APK、完整 120 帧采集、真实 Fast/性能/像素验收。用户需手工完成最新源码的唯一 Validation APK Pipeline,并手工 install -r,之后才能进行新二进制测试。
## 通过准则与下一步
实际源提交与安装 APK 完全匹配、原 STATIC_RC Native Plan valid、场景 READY、同一进程和场景内 Full/Fast 计数真实变化,FastDelta > 0 方可称 Fast observed。需另外核对 cache Build/Reuse 和 decisionMismatch=0、24 Gate、影像等价及 Source mutation/Annotation fail-closed。若 FastDelta=0,记录具体 Gate/R fallback 后停止将其宣称为 PASS,回到源码定位。原五场景以及 STEP-102 维持保护,非正式发行,不改 Source Lock。
步骤见 tools/particle_android_validation/README.md。没有修复完成或新的 APK 时,保持 STEP-114 PARTIAL,本 STEP 仅测试工具准备完成。

View File

@@ -0,0 +1,57 @@
# STEP-116 STEP-113 原 PBF Fast Path 实际修复 v1
日期:2026-10-11(JST)。状态:**部分实现(PARTIAL),停止于源码交付,待 RDC 独立审计。**
## 当前基线
- Mac / Darwin 24.6.0 / x86_64;`/Users/tei/dev/MapLibreParticleLab`。
- branch `particle/main`,起点 HEAD `e2d202b560ad0165101546597c43f2ef0ccf4542`,本轮不提交,HEAD 不变。
- 实际模型 GPT-6.1-Sol(gpt-6.1-sol)/ high,从当前 rollout 的 turn_context 核实;没有切换或第二实例。
- Native Android 版本 `13.6.1-particle.23`,没有发版;Integration main 80667d6c 为用户给定 RDC 核验,相关文档只读。
## 交付与未完成
| 工作 | 状态 | 事实 |
|---|---|---|
| 强制前置/实际代码/旧完整JSON调查 | 已完成 | 原R缺 tide,navsea_delivery 为 Vector;GPU/source生命周期见审查文档 |
| 空Annotation生命周期 | 已实现,未验收 | Native add/update/style-loaded 恢复缺失points;Test App仅STATIC_RC一次移除空辅助层,保留Source,退出恢复,后续变更撤销验收条件 |
| 原63层/PBF资源/五场景 | 已保留源码语义 | 不改公网或tide-spots;SOURCE_CHANGE circle保留;STEP114/102源码冻结 |
| 缺源empty准入 | BLOCKED | 缺GPU零贡献证书;继续missing-source,不补假源 |
| Symbol首次Cache Build | BLOCKED | 无绑定成功Build的Placement/GPU最终输入证书,现有Gate全部保留 |
| 诊断与复测工具 | 已实现,未运行 | JSON缺源ID +独立Placement摘要;75秒实际等待、源码锁定/生命周期/blocked/24Gate拒绝条件 |
| 非Jest C++测试源码 | 已实现,NOT RUN | 四个Step116Safety、三个Annotations.Step116;真实图像检查交RDC |
| 四份中文文档/README/开发记录 | 已生成 | 具体路径见完成报告 |
| 真正Fast/像素/性能 | NOT VERIFIED | 没有新APK、设备计数、截图或FPS/CPU结论 |
## 检查与收尾
Codex仅执行阅读、Git只读状态/diff、旧JSON与冻结文件SHA、Python AST解析、git diff --check及文档静态检查。无 CMake/Ninja/Bazel/Native编译或测试、Gradle/ADB/安装/Jest、commit/push/发布;没有改构建流水线、公开头文件/API/ABI/Host控制协议。
保护 STEP-110/111 未跟踪任务书。下一步 RDC 按 `docs/60_测试/STEP-116_STEP113原PBF专用APP验收_v1.md` 独立审计并锁定提交后的源码,用户集中一次手工 build/install,再执行 STEP115 preflight、原 STATIC_RC ≥75秒、完整120帧和24Gate、SHA及画面。不得使用旧APK或旧测试runner给本轮记PASS。
## RDC 后续结果填写(本轮尚未执行)
- 审计后的源码HEAD/精确diff/SHA:待填写。
- Native配置/编译/定向测试及首错/日志:NOT RUN。
- 最新用户构建APK SHA、build-info、设备安装SHA:NOT RUN。
- STATIC_RC实际层数/空层调整/原63层manifest/Source:NOT VERIFIED。
- Cache Build/Reuse、Full/Fast增量、120帧/24Gate/mismatch:NOT VERIFIED。
- Annotation恢复、Source/async/Style/资源/层序/disable/reload回退及用户画面:NOT VERIFIED。
- 最终阶段结论:当前PARTIAL;缺源与Symbol仍BLOCKED。
## RDC 独立核验追加(2026-10-11)
**本轮结论:限定范围源码/接口及操作说明静态审查完成;Mac Native配置 BLOCKED;Android APK/设备Fast NOT RUN,整体PARTIAL。**
- 起点粒子仓库 particle/main=e2d202b560ad0165101546597c43f2ef0ccf4542;Integration origin/main已由RDC fetch并与本地main同步,HEAD=80667d6c343411d03a760425438e3d292f452118。未接触NavSea与Integration正式文件。
- Codex实际模型 gpt-6.1-sol/high;本轮实现仅原STEP-113 STATIC_RC 的 SDK 空Annotation辅助层、Native AnnotationManager缺层恢复、缺源只读JSON诊断和测试验证器,不改变正式63个PBF layer、原五场景其他分支、Fast Gate/caching的准入或公开控制面。新增7项非Jest C++用例源码,尚未执行。
- RDC直接核对 Android Style.java 对象removeLayer(Layer)文档明确注明被移除Layer可以重新添加;与STEP116在stop中恢复原wrapper的目的相符。Native AnnotationManager恢复与事件生命周期仍须编译、图像和设备验证,不凭静态阅读标PASS。
- RDC核对旧SOURCE_CHANGE实际完整JSON:Native FastGate gates每项有order,firstFrame的knownMask等字段存在;STEP116验收脚本的原JSON结构检查有数据来源,但并非本轮运行证据。
- Mac shell当前无rg可执行,RDC将STEP116手动导出命令中的rg替换为标准grep -E,保留同样的文件筛选表达式。Python验收器ast.parse通过,git diff --check通过。
- Mac Native按当前标准命令重新执行真正的cmake --preset macos-metal配置(使用/Users/tei/Library/Android/sdk/cmake/3.31.6/bin/cmake),退出码1:Required package libuv缺失,日志保存artifacts/maplibre-particle/step116-native/configure.log。严格串行停止,未执行构建目标、未运行7项C++测试。失败是环境依赖,不应改写成代码编译通过/失败或跳过继续。
- 未执行Android Gradle、APK安装、ADB启动STEP113、新Fast计数、逐帧画面、iOS Metal回归、发行/tag/Source Lock。正式PBF缺tide以及Symbol R Placement仍保留fail-closed;此任务只是安全部分实现,并不解决原STATIC_RC无法进入Fast的全部原因。
- 用户在本轮源码scoped Git锁定且push远端核验后,需手工一次集中Android编译与install -r,再执行STEP115同HEAD/安装SHA预检、原STATIC_RC 90秒、完整120帧+24Gate+截图与其它负场景验收。新APK前禁止把旧c6d13e1产物Fast0作为本轮修复验收。若仍Fast0,必须保留缺源、Placement和C实际结果并交跨平台Core负责人确定GPU结果证书。
## RDC实际Android APK/设备验收追记(2026-10-11)
用户明确授权本次由RDC完成Android编译、安装、测试。源码提交c48a72a9d492ea0d05e4d58464f9643453158d50,标准APK构建PASS,APK SHA=a8b7f4e0f8e026bde8835e18a04876062be84cce3a3749a511cb08605d9f8a35;ADB serial192.168.200.139:5555安装Success,设备SHA相同,预检PASS。原STEP113 STATIC_RC真实90秒FastDelta=0、FullDelta=920,脚本退出2 NO_FAST_OBSERVED。Native65层、C仅WeatherColor+Particle,原63层保留,#22在最后120帧失败数0,说明空Annotation修复局部有效。R[0,63)缺tide Source,Cache Build/Reuse=0、PlacementGate=false;120帧119Full/0Fast/1Pending,#3/#4/#23失败120帧、#12/#13失败105帧,decisionMismatch0。总体Fast目标FAIL,本Step继续PARTIAL。完整报告及证据路径参见docs/60_测试/STEP-116_FZG1_原PBF实机验收结果_20261011.md。

View File

@@ -0,0 +1,58 @@
# STEP-117 原 PBF Fast 缓存准入 v1
日期:2026-10-11(JST)。状态:**阻塞**。调查与七项非Jest C++回归源码已交付;安全准入、真实Build/Reuse/Fast仍未完成。
## 目标与基线
原STEP113 STATIC_RC完整63基础层保持不变,以成功提交的GPU R结果证据解决缺tide和Symbol首次Build,达到Build>0、Reuse>0、FastDelta>0;证据不足保持fail-closed。
Mac.lan,`/Users/tei/dev/MapLibreParticleLab`,`particle/main@43e5f5b91598b9c3d3f1c788052d4bbd2d616668`;实际GPT-6.1-Sol(gpt-6.1-sol)/high已核实。Integration只读HEAD `80667d6c343411d03a760425438e3d292f452118`。VERSION `13.6.1-particle.23`,未改发行坐标。起点tracked clean,STEP110/111及117任务书未跟踪。
## 必做项与结果
- [x] 读取规则、当前任务/基线、STEP112–116关键资料与相关实际依赖源码。
- [x] 回读原274,693字节JSON并验证SHA,逐帧复算120帧24Gate与Placement状态。
- [x] 审查sourceDiff/revision/fingerprint、双group注册/停用、prepare/upload/Build/提交生命周期。
- [x] 分别说明缺源零贡献与Symbol最终GPU结果证书的缺口、反例及协调责任。
- [x] 保留fail-closed、原63层、STEP116空Annotation修复、原五场景及SOURCE_CHANGE负例。
- [x] 新增七项Step117Safety C++测试源码,复用已有构建登记。
- [x] 落盘调查、验收计划、完成报告,末尾追加开发记录。
- [ ] 建立可核验的缺源零GPU贡献与无旧Renderable数据证书,准入缓存。
- [ ] 建立Symbol最终GPU结果等价证书,解决首次Build。
- [ ] RDC独立编译并运行定向C++测试。
- [ ] 原STATIC_RC新制品≥90秒,Build/Reuse/Fast增长、完整120帧、Mutation回退与用户画面验收。
## 允许与禁止范围
本轮实际只改两个既有C++测试文件,新增四份中文文件并追加开发记录。未改生产src/include、公开API/ABI/RN/JNI/合同、Shader、Android场景、Style/服务器、NavSea、iOS、版本/Source Lock;没有放宽Source readiness/revision、Placement或24Gate,没有造Fast计数。STEP110/111原未跟踪MD保留,不暂存;原设备JSON保持Git忽略。
按用户当前禁止项,没有运行Gradle/ADB/Native编译/CMake/Ninja/Bazel/Jest、安装、commit/push。未启动后台编译或第二Codex,没有子代理。
## 阶段验收与当前结果
保留实测(STEP116,非STEP117):原STATIC_RC 90秒FastDelta=0、FullDelta=920;Native65层R[0,63)、C[63,65);R缺tide、Build/Reuse=0;最近120帧119Full/0Fast/1pending,#3/#4/#23失败120、#12/#13失败105、#22失败0、mismatch0。
本轮复算pre为105帧false/非零/true、15帧true/0/false;post为104+15,末帧pending。源码证明:缺源没有新RenderItem不等于无旧GPU数据;停用request未执行前registry仍可遍历旧group;停用不清旧drawables。Symbol重算changed与recent stale使首建条件不可达;layer fingerprint不包含最终GPU opacity/atlas/vertices。
因此两项准入均BLOCKED。七项测试只是可运行源码:真实controller/sourceDiff/registry/observer路径与状态机组合;没有GPU像素等价证据,不能称Native或全任务PASS。静态检查的实际结果在完成报告中记录。
## 构建测试、输出与下一步
RDC命令及失败停止、源/APK/设备SHA、完整JSON/画面、负例矩阵详见 `docs/60_测试/STEP-117_FZG1_原PBF真实Fast验收计划_v1.md`。本轮仅测试代码,Android生产行为无变化,重复构建不能解决B1/B2;先进行独立审计与Shared Core证书设计协调,形成受控实现后再安排一次用户授权的集中Android闭环。
必做输出:`docs/20_调查/STEP-117_PBF_R首次缓存Build安全证据_v1.md`、本文件、验收计划、`docs/报告/STEP-117_原PBF_Fast缓存准入_Codex完成报告_v1.md`、开发记录末尾追加。Git提交/hash、远端HEAD、Native/设备验收均留RDC在后续授权下回填,本轮未执行。
## RDC独立核验附录(2026-10-11)
- RDC先核对Integration main=80667d6c343411d03a760425438e3d292f452118、Particle当前particle/main=43e5f5b91598b9c3d3f1c788052d4bbd2d616668;源码与git已跟踪改动起点为空,仅两个用户保护未跟踪任务书。
- 用户本轮要求继续真实STEP113 PBF Fast优化,Codex实际GPT-6.1-Sol High。调查原STEP116实机120帧JSON,缺tide Source以及Symbol placement重算/stale循环仍无法安全创建首次GPU缓存;已有证据只能证明无新的LayerRenderItem,不能证明旧GPU drawables/已经应用的双LayerGroup删除。保留既有24Gate/生产Core,安全准入BLOCKED。
- Codex新增7个Step117Safety C++测试源码,分布于两个既有登记的test/renderer文件;RDC独立对两个最终文件以当前Android OpenGL/x86_64的NDK clang++、真实compile_commands.json C++20 flags和googletest头文件进行-fsyntax-only。retained_underlay_cache.test.cpp exit0(6.3s),render_orchestrator_error.test.cpp exit0(5.3s),RDC检查结果PASS。语法检查日志存Git忽略artifacts/maplibre-particle/step117-native-syntax/*.log。
- RDC核查原生产src/include/Renderer/Placement/Fast Gate、Android STEP113/114及Source/style均无文件改动,git diff --check通过。故**没有新的运行时改动供Android APK验证**:重复编译/安装STEP116相同行为的APK不能使Build/Reuse/Fast改善,未触发第二次高成本Android周期。GTest链接运行、GPU像素等价、iOS/Metal、真实Fast目标仍NOT RUN/BLOCKED,不能将NDK语法退出0标为GTest PASS。
- Integration正式现有RFC/ADR/Contract/status/coordination未包含所需R Symbol已提交GPU结果证书。B1缺源零贡献、B2 Symbol跨层结果证书与影子验收、B3真实tide Source身份/宿主动态注入语义须由粒子负责人/Integration按职责协调设计;本执行项目不得自行改变Shared Core公共契约或正式Style。
- 七项测试当前仍是源码语法检查级别,不等于实测; 下一步应先由粒子负责人协调Shared Core结果证书及合法真实tide来源,受控实现后再使用原STEP113 STATIC_RC APK复测。任意无法安全证明的条件继续fail-closed。
### RDC 严格 -Werror 编译参数复核修正(2026-10-11)
第一次NDK -fsyntax-only按项目真实C++20参数编译两个测试文件均exit0,但测试辅助函数遗漏可选retainedUnderlayState字段初始化,产生一条 -Wmissing-designated-field-initializers 警告;第一次探测曾附加 -Wno-error,不能直接标作正式 -Werror 验收。RDC后续对 test/renderer/retained_underlay_cache.test.cpp 增加一行显式 retainedUnderlayState={},保留原逻辑,然后从临时核验脚本中移除 -Wno-error,严格复用Android NDK OpenGL/x86_64真实compile_commands.json的 -Werror 及C++20参数重新执行两文件 -fsyntax-only:retained_underlay_cache.test.cpp退出0/5.3秒,render_orchestrator_error.test.cpp退出0/5.2秒;对应Git忽略日志均无警告输出。**严格语法检查PASS;GTest链接运行仍NOT RUN,Symbol GPU证书及Fast仍BLOCKED。**修正仅测试辅助字段初始化,不涉及生产src/renderer、Android APK和正式Gate。

View File

@@ -0,0 +1,29 @@
# STEP-118 tide 失效引用受控排除与Fast真实验证
日期:2026-10-11 JST。用户授权:tide Source可以删除。最终状态依RDC的实际Android验收更新,不预先标PASS。
## 基线与数据事实
Integration最新已同步main=80667d6c343411d03a760425438e3d292f452118,Particle原main=fb8e78ac1ab65ccafa6fd2b181d038b90d9a00c3。STEP116旧实机原STATIC_RC:生产基础63层;Native65层、R[0,63)、C[63,65)、FastDelta0、FullDelta920,R Build/Reuse0、fallback=missing-source、missingSourceIds=[tide]、PlacementGate=false。STEP117生产Renderer/24Gate无改动,严格Android NDK C++20语法检查已通过。
完整旧设备JSON显示生产sources只有mapple和navsea_delivery,根本没有tide;63基础Layer中只有index2的tide-spots Symbol依赖tide。故删除不存在的Source本身不会改变Fast。
## 本轮受控实现
不修改远端正式PBF服务器Style、不改NavSea、Shared Core、ABI/公共API、iOS或Native Fast Gate。保留原五个STEP113场景及原63层原始定义和productionFingerprint。新增独立STATIC_RC_NO_TIDE:加载同样远端Style,断言生产无tide Source、唯一引用它的Layer必须是tide-spots Symbol;在仅供本机Test App测试的JSON副本中移除这一Layer,保留其他62个原定义/顺序、同样两天气层及相机/30fps/Pause和STEP116空Annotation保护。预期Native总64、R[0,62)、C[62,64)(尚须实际设备确认)。
Parity同时保留原production63清单/fingerprint与有效62清单/test fingerprint;明确baseLayersPreserved=false,删除唯一层id/source/count,禁止将修改后的场景宣称为原63场景。Python verifier新增场景,强制校验原63/有效62/Native64、tide-spots/tide唯一排除、原Source无tide、R[0,62)、空Annotation移除、24Gate和SHA;旧五场景仍严格要求basePreserved=true。
## 验收与风险
RDC先源码检查/scoped Git提交推送,再在用户既有授权下执行Mac标准OpenGL x86_64 Validation APK构建、设备192.168.200.139:5555安装、APK SHA与已安装base.apk SHA一致、preflight、派生场景连续90秒、真实Native Cache Build/Reuse、Fast/Full增量和24Gate及截图。无新设备结果前仅称IMPLEMENTED / WAITING_ANDROID_VALIDATION。
即使消除missing-source,R Symbol Placement changed/stale与#23可能继续阻塞。Fast0仍为目标FAIL;不得通过删其它Symbol、造Source、禁用fade、修改Gate或放宽24Gate得到假Fast。后续正式PBF Style是否永久删除tide-spots由Style/NavSea负责人按跨项目协调确定,本Test App派生结果不能直接替代正式Style发行验收。
## RDC Android 新APK实机验收追加(2026-10-11)
Mac标准Test App build脚本于源码HEAD 3051196054c7b69815c298040dae61d26499d9f2 实际返回BUILD SUCCESS,Kotlin/Native OpenGL x86_64编译与ZIP/strip/ABI校验通过。APK大小66603850字节,SHA-256 44afcfabe0cf4f1ec93f35899241bd73093aa6db4b5cb82ca2120dd531b8f3e9;RDC adb -s 192.168.200.139:5555 install -r最终Success,设备base.apk SHA完全相同,Python preflight PASS。安装耗时约267秒,没有重入安装。
原STEP113框架内独立STATIC_RC_NO_TIDE运行90.008秒,PID22825,实际2份同生命周期有效摘要:原正式基础63清单仍保留在manifest,仅测试副本移除唯一tide-spots引用,基础62、天气2、Native64,R[0,62)、planValid=true、decisionMismatch0。R missingSourceIds=[]且原缺源回退从missing-source变为static-placement,说明本项修复在真机真实生效。R Cache Build/Reuse仍为0/0,FastDelta=0、FullDelta=942;120帧窗口Fast0、Full119。PlacementGatePassed=false,24Gate的unsupported-scene等继续失败。验证脚本返回NO_FAST_OBSERVED/exit2;此为目标未达成的明确负结果而非运行器异常。
结论:**tide孤立引用造成的缺源阻塞PASS;原Fast性能目标FAIL;STEP-118 PARTIAL**。剩余独立根因是Symbol Placement的GPU结果安全证书;不能通过放宽gate将其当作成功。完整量化数据、APK绑定和设备截图SHA见docs/60_测试/STEP-118_潮汐清理新APK设备实测结果_20261011.md。本次纯测试记录Git commit若晚于APK HEAD,不得将新文档HEAD冒充APK二进制源码。

View File

@@ -0,0 +1,41 @@
# STEP-119 SymbolRetained 生命周期修复 v1
日期:2026-10-12(JST)。Step 状态:进行中。代码交付:**IMPLEMENTED / WAITING_RDC_VALIDATION**。这是此前中断任务的续作;不关闭整体 STEP-119,不构成编译、GTest、GPU 或设备验收 PASS。
## 目标与基线
按照 `/tmp/fzg1_astra_guidance_20261012.md` 修复内部 Symbol Drawable 的 RenderTile 同源绑定、旧 Drawable 更新及 Bucket/Atlas 更换生命周期;保留原缓存、证书和 Full → Build → 成功 Full Reuse → Fast 顺序。
- 机器:Mac.lan / Darwin 24.6.0 / x86_64;仓库:`/Users/tei/dev/MapLibreParticleLab`。
- 开工及交付分支:`particle/main`;HEAD:`badb515d590fbdf221751b6677de3da0a5650d7e`。未切换分支/worktree,未提交或操作远端。
- 接续已有四个源码修改;起点另有受保护 STEP-110/111 两份未跟踪任务书。
- 用户本轮明确指定模型 `GPT-6.1 Sol High`(`gpt-6.1-sol / high`)。当前工具未提供执行器模型元数据,且本机当日 sessions 目录不存在,无法独立核验实际运行参数;不宣称已从日志核实,没有主动切换模型或启动子代理。
- 当前发行锁为 Android/RN `13.6.1-particle.25`、iOS `6.29.0-particle.25`,构建冻结源 `f32f6f45fd359007e8b3c77911b66d9e7fc93848`;本修复不改 Source Lock/发行物,不声称已进入 v25 制品。
## 必做项
- [x] 读取 Astra 指导、AGENTS、PARTICLE_BASELINE、规范、四份当前总则、当前 STEP-119 任务/实现/测试资料,核对现有四文件 diff。
- [x] 同次 source prepare 获取 raw owner 和 Y 排序引用列表;仅比较地址完成同源校验,再复制当前 ID/atlas。
- [x] 同 Bucket/Atlas 的完整 tile/pass 组先检查,再全部重新绑定当前 owner/RenderTile;不逐帧重建 Drawable/tweaker。
- [x] Bucket/Atlas/组内成员失配或缺 atlas 时移除整组,走原创建路径;Text/IconRGBA/IconSDF/Halo 使用同一 atlas 与实际 tweaker 配对。
- [x] Retained 使用内部强引用 atlas 和当前地址映射,不沿旧 Drawable 裸指针读取 atlas/Tile;缺证立即 complete=false。
- [x] 保活 RenderTile → renderData → LayoutResult 的证据资源,不把每次 prepare 的 owner 地址写入比较字节。
- [x] 编写并静态核对 13 项 SymbolRetainedLifetime GTest 源码,修正 atlas-only TileRenderData 查询默认 assert 的测试替身缺口。
- [x] 中文 Step、完成报告及开发记录末尾追加;四源码路径 scoped diff 空白检查 exit0。
- [ ] RDC 实际编译/链接并发现、运行生命周期与 STEP-119 定向 GTest。
- [ ] Android OpenGL、Metal 实际路径验证与 Vulkan 编译兼容性证据。
- [ ] 新源码锁/APK/SO 身份闭环、FZ-G1 动态生命周期、原24Gate、Full/Fast 视觉及释放验收。
## 修改范围与当前结果
生产修改限 `src/mln/gfx/symbol_drawable_data.hpp`、`src/mln/renderer/layers/render_symbol_layer.cpp`、`src/mln/renderer/layers/render_symbol_layer.hpp`;测试限 `test/renderer/retained_underlay_cache.test.cpp`。沿用此前修改,续作只补未知 Symbol 类型拒绝、测试替身空查询覆盖、Text/Icon/SDF/Halo 连续 prepare 稳定性与证书资源释放检查。
未改 RN/JNI 公共 API、原24Gate/证书判据、Placement、缓存开关、发行物、Integration、旧报告或两份保护任务书。未运行 CMake、编译/链接/语法编译、GTest、Gradle/APK、ADB/安装、Jest;没有新制品或设备数据。
## 阻塞与下一步
实现已落盘,可编译性与运行正确性仍待 RDC。现 verifier 会拒绝 tracked dirty,后续 Git 来源锁须在独立审计和另行授权后处理;本轮禁止 Git 收尾,不能绕过 verifier 或借旧 APK。历史 Metal/libuv 缺口未重新验证,不安装升级依赖。
RenderTile 容器不拥有底层 `Tile&`;强引用 atlas/texture 也不能证明原地像素或槽位不变,继续依赖既有 content epoch 和真实绑定复核。原始寄存器 tombstone 缺失,不能断言 `0x8cdc7c` 唯一根因为本缺陷;保留原 SO Build ID `485459ba44f806ef0a0c08633c717bfd5d82ad8f` 与匹配符号交 RDC。
准确命令、测试覆盖限制、停止门槛和交接证据见 [本轮完成报告](../报告/STEP-119_SymbolRetained生命周期修复_Codex完成报告_v1.md)。Codex 到此交接,RDC 独立审计后逐条验证;失败保留首错与现场,不放宽条件。

View File

@@ -0,0 +1,85 @@
# STEP-119 Symbol缓存实际实现 v1
日期:2026-10-11(JST)。状态:进行中;代码阶段IMPLEMENTED_WAITING_ANDROID,独立验收未完成。
## 目标与基线
实现Shared Core内部最终Symbol结果证书,解决静止真实PBF派生R首次Build逻辑自举死锁,支持后续Reuse/Fast,同时保护所有真实渲染失效与原24Gate。
particle/main@869930046b11dbeb3b147e3be952a2e847316292;Mac.lan/Darwin24.6.0/x86_64。Native13.6.1-particle.23;Integration本地HEAD80667d6c343411d03a760425438e3d292f452118,只读。实际会话rollout 01a1299f-0c2b-78b0-8599-e50812c108dd的turn_context核实gpt-6.1-sol/high,未升级、未启动子代理。
## 必做项
- [x] 读取根规则、规范/总则、当前任务、Integration规则与STEP112–118关键证据,核对实际Renderer/Placement/Bucket/Atlas/GL/Metal。
- [x] 实现真实上传输入证据、revision/epoch、完成Full/Build/Reuse绑定,global/perSegment/preparedScene同时接线。
- [x] 分离stale刷新需求与真实fade;保留原Placement算法/原Style/Source/公开API/ABI。
- [x] 完成13项非Jest C++针对性测试源码,两个文件均已登记。
- [x] 中文调查、阶段记录、实机联合验收计划、完成报告与开发记录追加。
- [x] 轻量静态范围/原证据哈希/git diff --check检查。
- [ ] Native编译与13项GTest实际运行,GL/Metal真实代码路径独立验证。
- [ ] 同源新APK/设备SHA与>=90秒真实Build>0、Reuse>0、FastDelta>0、24Gate mismatch0。
- [ ] 原63缺tide负对照、全部失效矩阵、Full/Fast视觉与iOS/Metal独立验收。
## 允许与禁止范围
仅内部Shared Core/原生shader来源薄接入、已登记C++测试与本Step文档。未修改include公开头、RN/JNI/ObjC/Java/Kotlin、正式PBF/URL/Style、天气语义、Core-owned timeline/playback、发行版本或Integration协议。
没有Gradle/ADB/Native构建/Bazel/Jest/codegen/commit/push;没有更改STEP110/111未跟踪任务书或Git忽略原设备证据。没有修改长期项目总则、RDC Runner或后台执行器。
## 当前结果
两次成功Full取证的同一settled GPU输入版本可建立R Build,成功Full Reuse后才允许Fast。真实opacity clamp、动态/paint属性、UBO、索引/排序/tiles、atlas/shader身份、registry ordinal/depth与异步epoch共同参与;缺失证明仍Full。
普通Full销毁旧RenderTree时保留复制证据,真正reset时清理;否则两帧比较仍会自举失败。原24Gate evaluator/掩码/顺序/计数和公开诊断结构没有修改。STEP117历史保守helper与断言保留,生产接线改为最终结果证书。
源码没有运行验证,不宣称当前设备已从STEP118的Build0/Reuse0/Fast0改善。证书明确绑定编码/提交成功,GPU完成与像素仍需独立验收。Vulkan/WebGPU无本轮原生shader来源登记,Symbol证书继续fail-closed,普通Full不变。
## 阻塞与下一步
本工作区未锁定源码;既有验证器会拒绝tracked dirty,不能绕过。历史Mac缺libuv,本轮没有重跑配置。新的Native可编译性、真实PBF输入是否低于16MiB、实际GPU等价、异步竞态与性能均待证。
立即交RDC按本Step联合验收计划独立审计、在其授权范围完成源码锁/定向Native验证,并集中一次APK build/install/真实设备验收。失败保存证据,按固定Codex修复→RDC验证闭环;未通过不得标完成/冻结,不自行执行下一阶段。
## RDC独立编译检查记录(2026-10-11 JST)
用户明确要求编译然后测试,并要求不要因本次功能无新第三方依赖而破坏已存在原生环境。RDC先同步核实Integration origin/main=80667d6c343411d03a760425438e3d292f452118与Particle origin/particle/main=869930046b11dbeb3b147e3be952a2e847316292,且保护的两份STEP110/111未跟踪MD的SHA未变。
Mac Metal原生CMake配置首次因PATH找不到cmake而失败,改用既有/usr/local/opt/cmake/bin/cmake后,配置因早已缺少的libuv失败(不是本轮代码引入的依赖)。Homebrew建议连带升级CMake、Python和OpenSSL,RDC及时中断该安装并复核原CMake仍存在、无libuv新依赖安装。**Mac Metal CMake/GTest仍未运行,不能记PASS。**
改走原项目已存在 Android NDK OpenGL/x86_64 compile_commands.json,保持实际C++20警告/Werror、不加Wno-error、不切宏、不改源数据。编译实际Core生产7文件(placement.cpp、render_symbol_layer.cpp、symbol_layer_tweaker.cpp、render_orchestrator.cpp、renderer_impl.cpp、renderer.cpp、shader_program_gl.cpp)与两个非Jest已注册GTest源码,合计9个编译单元,经过本地定点修复后全部严格 -fsyntax-only exit0(每文件独立日志保存在Git忽略artifacts/maplibre-particle/step119-native-syntax/)。本轮修复3处小编译缺口:SymbolLayer及Retained test各一处toString补上util命名空间,Orchestrator error test将构造结果从const Mutable转换为Immutable,保留测试语义。该结果是**Android NDK语法/类型检查**,非真实链接、GTest运行或Metal构建。
下一关:RDC scoped Git锁定源码后,基于新源码一次集中标准Android Gradle/OpenGL x86_64 APK构建;FZG1-2安装和STATIC_RC_NO_TIDE 90秒验证。任何编译/设备错误继续按原步修复,不能绕过24Gate或声称Fast已通过。
## RDC Android原PBF实机正负对照(2026-10-11)
本轮源码锁 3d989a00a822989339a6d7cb4d23acc04396670e、APK SHA 57d68800770d431a2ce9247406f636e7a01cc19fccaf4c59f5eb3a7c99128135,Mac标准Android OpenGL x86_64 APK真编译/链接/打包PASS;FZG1-2安装SHA一致、preflight PASS。新STATIC_RC_NO_TIDE 90.010秒:Native64/R[0,62),missingSourceIds=[],Cache Build=1 / Reuse=3276(累计)、FastDelta=1819 / FullDelta=0、24Gate mismatch=0。原STATIC_RC 90.010秒:原Native65/R[0,63),missingSourceIds=[tide],Build=0、Reuse=0、FastDelta=0、FullDelta=305,fail-closed仍正常。新13项GTest运行/Metal/iOS/动态源变更与像素对比均未完成,保持ANDROID_FAST_CORE_PASS/OVERALL_PARTIAL。详细原始数据和sha见 docs/60_测试/STEP-119_FZG1_原PBF真实Fast验收结果_20261011.md。
- 2026-10-11 STEP119实机首次FAST_OBSERVED:APK源3d989a00a822989339a6d7cb4d23acc04396670e SHA=57d68800770d431a2ce9247406f636e7a01cc19fccaf4c59f5eb3a7c99128135,FZG1-2安装/preflight PASS;STATIC_RC_NO_TIDE 90秒 FastDelta1819/FullDelta0、R Build1/Reuse3276、24Gate mismatch0、Native64,missingSourceIds=[]。仅正向目标首次通过,负例/视觉/GTest/iOS未验,STEP119 PARTIAL。详见 docs/报告/STEP-119_Android真机Fast首次通过_阶段验收_v1.md。
## RDC STEP-119 RCRR 多配置续验(2026-10-11 JST)
- Integration origin/main 经 RDC 同步核验为 `80667d6c343411d03a760425438e3d292f452118`;Particle 起点 `46c88b0209474376225b54ae0ad701881673743d`,仍在 `particle/main`;两份 STEP110/111 未跟踪任务文档未触碰。
- 同一已安装 APK 源 `3d989a00a822989339a6d7cb4d23acc04396670e`,SHA `57d68800770d431a2ce9247406f636e7a01cc19fccaf4c59f5eb3a7c99128135`,真机原 `PARTIAL_RCRR` 80秒:R0 Build0/Reuse0/missing tide;R2/R3 各 Build1/Reuse536;FastDelta0/FullDelta596、decisionMismatch0。即局部复用有效、全帧 Fast 安全拒绝。
- 真机导出原63层 `styleParity.baseLayerManifest` 确认 tide=2、sea=3、clip-outline=31、special=37;天气实际在原宽 C 中,不在 R0。由此修正早期错误的分段位置推测。
- Test App 增加三种 `RCRR_*_NO_TIDE` 场景,分别测试窄 C、宽 C 与错误动态 R;基于上述真实 Manifest 校准成 Native64 精确 R/C 区间,未修改正式 PBF/原场景/24 Gate。验证器精确验三段 R 边界;4项 Python unittest PASS、Style Manifest 静态索引核对 PASS、git diff --check PASS。
- **尚未完成**:新 Kotlin Test App 的用户手工 APK 编译及三场景真机 90秒回归、图像目视、Camera/Source/Style/Tile/Annotation/Context 真机失效和恢复、STEP119 13项 GTest 真跑、Metal/iOS。Mac 原有 libuv 缺失限制保持,不擅自安装或升级工具链。STEP-119 **PARTIAL,不 CLOSED**。
- 文档:`docs/60_测试/STEP-119_RCRR多配置验收矩阵_v1.md`、`docs/报告/STEP-119_RCRR多场景局限性_阶段验收_v1.md`。原始真机记录位于 ignored `artifacts/maplibre-particle/android-validation/step119-rcrr-observe-20261011_172720/partial_rcrr/`。
## RDC STEP-119 粒子独占 C 与 CPU 效率收敛(2026-10-11 JST)
- 用户确认:RCRR 正常运行的 `CONTINUOUS` 原则上**只含 `RasterParticleLayer`**,`WeatherColorLayer` 与其他非逐帧图层在 R;下一轮验收必须结合 CPU/帧、CPU/秒和实际 FPS,不只观察 FastDelta。
- RDC 已同步 Integration `main`=`80667d6c343411d03a760425438e3d292f452118`;Particle 以 `1072816740fa69b7b23cd70602a402be19e5be2a` 为起点,现有 `particle/main` 与 STEP110/111 未跟踪文件不变。
- Native 源码检查确认 `singleContinuousSegment(plan)` 是 Fast 准入条件,Fast 上传/绘制仅访问 C 范围,所有 R 需满足原24 Gate 与真实 Render/Symbol 缓存证书;WeatherColor 放 R 后是否能安全 Build/Reuse 尚待真机验证,不能提前 PASS。
- 将 `RCRR_C_NARROW_NO_TIDE` 改为粒子独占 C[32,33)(WeatherColor 在 R[0,32)),新增 RCR 两 R、RCRRR 四 R,以及同 Style/Plan 关闭缓存的 Full 对照;保留旧 C 宽、粒子误入 R 两种负例和原63层 `PARTIAL_RCRR`。菜单 ID 改为100~102避免新增enum与操作冲突。
- 新增严格 SHA/HEAD/设备锁的 CPU 采样器(App进程 utime+stime、6秒采样、35秒warmup、90秒观测)、A-B-A 对照比较器;对照同时列 CPU核秒/秒、CPU估算ms/完成帧、Native FPS、FastFraction≥95%、Build/Reuse/Invalidate。ADB实机只读确认 `CLK_TCK=100`,未以旧APK冒充新测试;未进行Android编译。
- Python 非Jest结构+CPU测试 12项 PASS。正式测试矩阵 `docs/60_测试/STEP-119_RCRR多配置验收矩阵_v2.md`,本轮阶段报告 `docs/报告/STEP-119_粒子独占C与CPU效率_阶段验收_v1.md`。
- **待验**:用户手工编译、安装新APK及Full-Fast-Full CPU实测,三种粒子独占C布局的Build/Reuse/Fast、视觉等价、变更后失效与恢复,13项GTest/Metal/iOS。STEP-119继续PARTIAL、不宣称CPU性能提升。
## RDC 2026-10-11 STEP-119 粒子独占 C 与 CPU 实机综合验收(续)
- 用户已明确授权 Mac RDC 执行 Android APK 编译/安装及综合测试。Integration main 已同步到 `80667d6c343411d03a760425438e3d292f452118`;APK 真机源码 Commit 为 `32ac795fc4abd04006673bf644f87a711329e78f`;x86_64 APK SHA-256 为 `2bbd1cb4dc9e03ec56d8404363970e56f76b03819f6f2b50c8aee6dbb6fa43d2`,Mac build exit0、设备压缩下载解压 SHA 同源、`pm install -r` Success exit0、安装 Base APK 同 SHA、严格 preflight exit0。
- CPU 测量工具首次 `sh -c` 多参数读取阻塞 exit3;随后采用单个 ADB shell 命令并保存 warmup+末尾日志的临时修正测量,第一组 Full 不齐整 exit2排除。最终 9次Benchmark中8次有效:2×Full、3×RCRR(三R)、2×RCR(两R)、1×RCRRR(四R)。全部24 Gate完整、判定失配0、同 APK/Style指纹、固定相机与场、Native最后120帧窗口119 Fast/0 Full(正向组)。
- 实测进程CPUms/完成帧中位:Full 60.67ms,RCR 3.50ms,RCRR 7.04ms,RCRRR 7.82ms;RCR两次3.30/3.70ms,RCRR三次6.96/7.04/7.37ms。RCRR Full-Fast-Full 基线漂移2.70%,中间 Fast约55FPS、CPUms/帧7.04,相对Full估算降低88.40%。RCR约59.4–59.7FPS、CPU占单核约19.7–22.0%,比RCRR在该固定场景更省 CPU。仅为进程级CPU近似,不证明GPU、耗电。
- 两种负例独立90秒:宽 C 海图混入 `Fast0/Full+339`、Particle误入R `Fast0/Full+322`,两者均24 Gate完整、`decisionMismatch=0`、`unsupported-continuous-layer`安全拒绝;严格验证器返回2属于预期负例 PASS,并非进程崩溃。
- 定向验证中每个正向 R 都曾 Build1,稳定采样窗口 BuildDelta/InvalidateDelta=0,Reuse持续增加。天气切换后的 R 刷新、相机/源/Tile/Annotation/Context生命周期、Full与Fast的视觉同等、GPU计时、iOS/Metal、13个GTest仍未证实;STEP119 **PARTIAL**,不将稳态验收写成CLOSED。
- RDC 将临时已实际运行的 CPU 采样器 `sh -c` 与日志边界修正合入正式Python测试脚本,**不重新构建/修改当前已验APK**;Python 12项 PASS,Git收尾只包含此脚本、阶段报告、Step记录与开发记录。正式结果:`docs/报告/STEP-119_RCRR粒子独占C_CPU真机验证_阶段报告_v2.md`,综合数据 `artifacts/maplibre-particle/android-validation/step119-rcrr-cpu/compare-20261011_193724.json`(本地忽略目录)。

View File

@@ -0,0 +1,172 @@
# STEP-114 独立 Fast Smoke 场景与 RDC 验收 v1
日期:2026-10-11。状态:**源码交付;下列编译、测试和设备操作全部 NOT RUN。** Codex 不执行本页命令。Mac Native 由 RDC/普通 Terminal 独立执行;Android 构建、安装、ADB 与实体机步骤由用户在后续授权条件下手工统一执行。
## 1. 场景入口和范围
Test App:`STEP 093 → STEP-114 独立 FAST_SMOKE(非正式 PBF)`。正式网络 PBF 仍在 `STEP 093 → STEP-113 FZG1-PBF GATE`,保留 PBF63、STATIC_RC、PARTICLE_OFF、PARTIAL_RCRR、SOURCE_CHANGE 原实现。
| 独立场景 | 预期层序/保护 | 待验收结果 |
|---|---|---|
| FAST_SMOKE | R=background + 非空本地 GeoJSON/circle;C=WeatherColor + RasterParticle。无 Annotation 实例时删除空内建 Annotation layer,保留 source | Native 实际4层;R Build/Reuse 和 Full/Fast 必须真实增长 |
| CIRCLE_C_BLOCKED | baseline 后追加普通 circle C;Annotation 层仍按独立空场景移除 | 实际5层,#22/#23 阻止 Fast;R 可正常 Build/Reuse,不等于整个 scene 可 Fast |
| ANNOTATION_BLOCKED | baseline 加正常自动内建 Annotation Symbol C,并创建真实 marker;不删该层 | 实际5层;#22/#23 阻止 Fast;marker 可见、可单次 position 更新 |
| SYMBOL_R_BLOCKED | baseline 的 R 内加本地16×16图标 Symbol;无需网络 glyph/sprite | 实际5层;Symbol R 首建仍被 Placement gate 阻止;不能把关闭 transitions 当修复 |
全部使用既有离线 ECMWF canonical 双帧资产、固定预报 `1786060800000` ms、PAUSE、30fps目标和1024粒子,SDK camera 初始33.45/129.97、zoom10.5、bearing/pitch0;手势禁用且退出恢复。30fps目标不是实际帧率测量。actualForecastTimeObservedMs 保持 null,不能当成 Native 已回读请求时间。sceneReady 只表示加载回调与控制命令成功,不替代 Native 层表/valid/实际渲染核验。
不要在旧正式 V23 APK 上验收本轮新入口;版本号未变,必须保存本轮源码 diff/hash、build-info 的 dirty 状态与 APK SHA。不得发行或覆盖已有版本。
## 2. Mac Native 精确定向命令(仅 RDC 执行)
基于现有 `macos-metal` preset 和实际 `mbgl-test-runner` target,无全仓测试、无 Jest。STEP-113 曾缺 libuv;本轮没有安装依赖。配置失败则停止,不运行旧 runner。
```bash
cd /Users/tei/dev/MapLibreParticleLab
bash <<'SH'
set -eu
mkdir -p artifacts/maplibre-particle/step114-native
if cmake --preset macos-metal > artifacts/maplibre-particle/step114-native/configure.log 2>&1; then
echo 'configure exit=0'
else
step114_rc=$?
tail -n 80 artifacts/maplibre-particle/step114-native/configure.log
exit "$step114_rc"
fi
if cmake --build build-macos-metal --target mbgl-test-runner --parallel 3 > artifacts/maplibre-particle/step114-native/build.log 2>&1; then
echo 'build exit=0'
else
step114_rc=$?
tail -n 80 artifacts/maplibre-particle/step114-native/build.log
exit "$step114_rc"
fi
if build-macos-metal/mbgl-test-runner --gtest_filter='Step114FastPathSafety.*:FastGateTrace.*:ParticleFastPath.*:RetainedUnderlayCache.*:RetainedUnderlayRevision.*:RenderSegmentPlacementFree.*:RenderSegmentLocality.*:RenderSegmentPlan.*:RenderSegmentDiagnostics.*:RenderOrchestratorError.*:RenderOrchestratorTeardown.*:ImageManager.*:GlyphPBF.*' --gtest_color=no --gtest_output=xml:artifacts/maplibre-particle/step114-native/tests.xml > artifacts/maplibre-particle/step114-native/tests.log 2>&1; then
tail -n 20 artifacts/maplibre-particle/step114-native/tests.log
else
step114_rc=$?
tail -n 80 artifacts/maplibre-particle/step114-native/tests.log
exit "$step114_rc"
fi
SH
```
记录 configure/build/tests 各退出码、耗时、实际测试数量与是否 skipped;失败回传首个真实 error、target、必要上下文与日志路径。新增测试应实际发现3个 `Step114FastPathSafety` 用例,否则不能把空过滤器执行成功作为 PASS。这些主要是纯状态机/结构判定测试,**不能证明 Android GPU Fast 命中、帧像素一致或 iOS 设备通过**。
## 3. Android 后续一次集中手工验收
由用户手工,在设备 ABI=x86_64 且 Java17/SDK 环境满足现有规范时,运行唯一既有 Validation Pipeline;不自行改 ABI、build 参数或依赖,不全量 clean。
```bash
cd /Users/tei/dev/MapLibreParticleLab
./tools/particle_android_validation/build_validation_apk.sh
```
失败保留 build.log,禁止复用旧 APK。用户手工核对同包签名和数据后再安装;本页不授权卸载用户数据。下面 ADB 全部固定既有 FZG1-2 serial,仅作为用户后续手工命令:
```bash
adb -s 192.168.200.139:5555 shell getprop ro.product.cpu.abi
adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
adb -s 192.168.200.139:5555 shell am start -S -n org.maplibre.android.testapp/org.maplibre.android.testapp.activity.particle.ParticleCoreLayerValidationActivity --es step114_scenario FAST_SMOKE
```
启动后预热10秒,再观察60秒。在开始和结束分别点击“导出当前 Native 快照”。标签 `STEP114Gate` 每30秒只输出真实 Native 摘要,完整JSON写 App 私有 files。生命周期计数可以含预热,必须比较同 lifecycle/sceneGeneration 的两次计数 delta;不要称整个累计为60秒帧率。
菜单“单次更新 R GeoJSON”“Pan/Zoom 往返”“单次 Image 变更”“禁用/恢复缓存”保存 before/submitted/100/500/1500ms 的5份快照。一次只做一项,等恢复稳定并手动导出后再做下一项。自动文件有 actionSequence 和 phase,不追加 candidate 输入或人工 frame 结果。
每个负场景单独启动、预热、观察、导出,不把以下命令连续执行当验收:
```bash
adb -s 192.168.200.139:5555 shell am start -S -n org.maplibre.android.testapp/org.maplibre.android.testapp.activity.particle.ParticleCoreLayerValidationActivity --es step114_scenario CIRCLE_C_BLOCKED
adb -s 192.168.200.139:5555 shell am start -S -n org.maplibre.android.testapp/org.maplibre.android.testapp.activity.particle.ParticleCoreLayerValidationActivity --es step114_scenario ANNOTATION_BLOCKED
adb -s 192.168.200.139:5555 shell am start -S -n org.maplibre.android.testapp/org.maplibre.android.testapp.activity.particle.ParticleCoreLayerValidationActivity --es step114_scenario SYMBOL_R_BLOCKED
```
ANNOTATION_BLOCKED 点击“单次更新真实 Annotation”,截图应见位置变化,Native 保持 fail-closed。FAST_SMOKE 中点击该操作会明确 BLOCKED(没有 marker),不创建隐藏 Annotation 或假事件。
## 4. 回读完整快照(用户手工)
在普通 bash Terminal 保存全部 step114 JSON,不通过 Logcat 分块拼接。路径来自当前 Debug App:
```bash
cd /Users/tei/dev/MapLibreParticleLab
bash <<'SH'
set -eu
mkdir -p artifacts/maplibre-particle/android-validation/step114
adb -s 192.168.200.139:5555 shell run-as org.maplibre.android.testapp ls files > /tmp/step114-files.txt
tr -d '\r' < /tmp/step114-files.txt > /tmp/step114-files-normalized.txt
while IFS= read -r step114_file; do
case "$step114_file" in
step114-*.json)
adb -s 192.168.200.139:5555 exec-out run-as org.maplibre.android.testapp cat "files/$step114_file" > "artifacts/maplibre-particle/android-validation/step114/$step114_file"
;;
esac
done < /tmp/step114-files-normalized.txt
SH
adb -s 192.168.200.139:5555 logcat -d -v time -s STEP114Gate:I '*:S' > artifacts/maplibre-particle/android-validation/step114/device.log
adb -s 192.168.200.139:5555 logcat -b crash -d > artifacts/maplibre-particle/android-validation/step114/crash.log
adb -s 192.168.200.139:5555 exec-out screencap -p > artifacts/maplibre-particle/android-validation/step114/screen.png
```
每阶段独立命名截图/日志;同名覆盖前先归档。App files 自动快照只在显式操作和生命周期变化时创建,数量无硬上限;验收后由用户管理归档,Codex 不删文件。100ms采样也可能错过一帧的变更窗口,未捕获不能标反向 Gate PASS,需保留日志再复核。
下面是读取**实际导出文件**的正向检查;把路径替换为稳定 FAST_SMOKE 手动快照的明确文件名(不要随意用最后的 teardown/旧场景文件):
```bash
python3 - 'artifacts/maplibre-particle/android-validation/step114/替换为稳定FAST_SMOKE文件.json' <<'PY'
import json, sys
from pathlib import Path
d = json.loads(Path(sys.argv[1]).read_text())
assert d['scenario'] == 'FAST_SMOKE' and d['sceneReady'] and d['blockedReason'] is None
n = d['native']; t = n['fastGateTrace']
assert n['valid'] and n['styleLayerCount'] == 4
assert [l['layerType'] for l in n['styleLayers']] == ['background', 'circle', 'weather-color', 'raster-particle']
assert [s['strategy'] for s in n['segments']] == ['RETAINED', 'CONTINUOUS']
r, c = n['segments']
assert (r['beginIndex'], r['endExclusive'], c['beginIndex'], c['endExclusive']) == (0, 2, 2, 4)
assert not r['placementCapable'] and r['placementGatePassed']
assert r['cache']['sourceReady'] and r['cache']['sourceStable'] and r['cache']['supported']
assert r['cache']['build'] > 0 and r['cache']['reuse'] > 0
assert t['fullRendered'] > 0 and t['fastRendered'] > 0 and t['decisionMismatch'] == 0
assert len(t['gates']) == 24
fs = [f for f in t['frames'] if f['completed'] and f['outcome'] == 'fast-rendered'
and f['post']['configRevision'] == n['configRevision']
and f['post']['topologyRevision'] == n['topologyRevision']
and f['post']['contentRevision'] == n['contentRevision']]
assert fs, '完整窗口没有真实完成的 Fast 帧'
for f in fs:
assert f['candidate'] and f['decisionConsistent'] and f['knownMask'] == 0xffffff and f['failMask'] == 0
assert f['gateStatus'] == ['PASS'] * 24
assert not f['cacheInputsTruncated'] and f['cacheInputs']
assert all(x['valid'] and x['buildRevision'] == x['staticRevision'] for x in f['cacheInputs'])
assert f['post']['fullCpuFrame'] == f['pre']['fullCpuFrame']
assert any(x['actualExecution'] == 'composite' for x in f['post']['retained'])
print('仅此快照 Native 正向证据通过;反向/生命周期/视觉/双平台仍需独立验收')
PY
```
不能通过修改导出的 JSON、补造 frame、重命名旧文件或放宽断言制造结果。
## 5. 定向反向、生命周期与回归矩阵
| 项目 | 必需证据/接受条件 |
|---|---|
| 稳定 baseline | 两次同生命周期快照 Fast/Reuse delta>0;至少一个 completed Fast 的24 Gate全通过;Build>0;orange R circle可见,天气/粒子正常;decisionMismatch=0 |
| Source mutation/异步 Tile | 单次sourceMutationCount增长;sources 表及 lastMutation 定位 step114-r-geojson;content/source revision变化;#8/#5/ready/pending 等真实拒绝证据、成功布局后 Full/Build 增长再恢复 Fast。tombstone/失败布局用Native状态机用例补充,不冒充GPU验证 |
| Image 更新 | images signature/content/global revision 导致 Full;重建后恢复。baseline图标未引用也保守失效;不能宣称已验证 glyph atlas像素 |
| Glyph | baseline完全无文字;本轮未新增 glyph mutation Test App控制。已有 GlyphPBF/ImageManager 测试仅资源回归;真实 Symbol glyph URL/font/atlas变化属于 Symbol结果证书阻塞,NOT VERIFIED,不能以image替代 |
| Pan/Zoom 往返 | SDK cameraObservedFromSdk与Native gate/key对应,Full/Build增量;恢复相机后不能直接复用旧key;截图无陈旧circle/错位 |
| 缓存禁用/恢复 | 禁用时#2失败、实际Full/live,无completed Fast增长;恢复后合法Build与Fast恢复;比较截图确认R内容/层序一致 |
| Reload | 保存reload-before与加载后快照;sceneGeneration变更、Native当前层表/plan正确,重新Build;trace lifecycle仅在实际Native reset时变,不要求UI generation=Native lifecycle |
| 非粒子C/真实Annotation | CIRCLE_C_BLOCKED/ANNOTATION_BLOCKED稳定窗口#22/#23失败、无completed Fast;真实marker可见/位置变化;#24是短暂pending保护,不要求在3秒末态仍FAIL |
| Symbol R | 本地icon可见;R placementCapable=true,正常Full placementGatePassed=false/Build0或对应安全fallback;Fast不放行,scene #23可能另挡;无证书不改判定 |
| 前后台 | baseline导出、手工Home后台10秒、回前台观察/导出;pause-before文件、恢复后Native增长、无死循环/错层/crash。此处未强制改变WeatherRuntime播放语义 |
| Teardown/重建 | 退出场景/关闭Activity产生teardown-before,dispose与callback取消;用户确认MapView销毁返回/无crash,重新启动baseline。teardown-before不是销毁完成证明 |
| 原PBF5场景 | 分别采集STEP-113五场景;仍保留全部基础63层定义/顺序及Annotation;缺tide时应明确missing-source,CustomMVT若实际出现仍unknown;不要求生产Fast>0、不篡改旧统计 |
| 原STEP-102 | T1/T2/T3/T4 fixture和既有动态poll回归;尤其含Symbol路径不因本轮无Symbol优化被放行 |
| iOS/Metal | 同Shared Core源码的Mac Metal定向测试先执行;iOS实机与GPU像素/性能未验收。Android Fast成功不能替代此项 |
## 6. 收口规则
记录各场景来源commit/dirty diff、APK SHA/版本、device serial/ABI、真实Native层表/plan、before/after文件与SHA、24Gate、Full/Fast/Build/Reuse增量、截图、首错及crash日志。processSimulationAdvance是进程级,不作为单Map Fast证据。post的原始 placementTransitionActive仍可能true,但无Symbol的preFastPlacementTransition=false是本轮受控变化,decisionMismatch应仍0。
没有真实Fast或反向证据不足,保持 PARTIAL;Symbol证书/CustomMVT/C放行分别继续 BLOCKED/fail-closed。不得根据静态检查、runner退出0或APK成功宣称整个Fast Path修复/性能PASS。Codex未commit/push,RDC后续Git收尾另需明确授权与scoped路径,不包括STEP-110/111或正式发行。

View File

@@ -0,0 +1,57 @@
# STEP-116 原 STEP-113 PBF 专用 APP 实机测试报告
日期:2026-10-11 JST;结论:**PARTIAL。空 Annotation / C 段结构修复确实有效;原 STATIC_RC 进入 Fast Path 的目标未达成(FAIL)。**
## 1. 用户授权与源码/设备闭环
用户明确授权本次由 RDC 执行标准 Android APK 编译、安装和专用 APP 测试。环境:Mac.lan,仓库 /Users/tei/dev/MapLibreParticleLab,Particle源码commit c48a72a9d492ea0d05e4d58464f9643453158d50;Integration main 80667d6c343411d03a760425438e3d292f452118。Git tracked clean;两个STEP110/111用户文件仍未跟踪且不改动。Android Test App版本字符串13.6.1-particle.23,本轮真实二进制以commit和SHA而不是版本名辨识。
RDC执行标准 tools/particle_android_validation/build_validation_apk.sh,进程退出0,Gradle与APK pipeline回报BUILD SUCCESS,构建Native OpenGL/x86_64、Kotlin、ZIP/strip/体积检查通过。Java17;APK文件66,603,829字节,libmaplibre.so未压缩40,035,512字节。APK SHA-256:
a8b7f4e0f8e026bde8835e18a04876062be84cce3a3749a511cb08605d9f8a35
设备FZG1-2,ADB serial 192.168.200.139:5555。实际adb install -r返回Performing Streamed Install / Success,没有卸载应用或清空数据;pm path得到新安装base.apk,设备sha256sum与上述新APK完全一致。STEP115 Python工具的preflight-only返回PREFLIGHT_PASS,确认当前源HEAD、build-info、APK、设备ABI和安装包SHA完全匹配。build-info中的git_dirty=true来自构建前保护的两份未跟踪任务书,不是tracked源码变更。
## 2. STEP-113原STATIC_RC的90秒结果
RDC实际启动原 STEP-113 正式PBF STATIC_RC(非STEP114简化FAST_SMOKE),进程PID19771,同sceneGeneration=2/lifecycle=1,观察90.008秒。两份完整运行摘要一致且单调:
- STATUS=NO_FAST_OBSERVED,工具exit=2(真实Fast目标未达成)
- FastDelta=0,FullDelta=920
- sceneReady=true,planValid=true,baseLayersPreserved=true
- 原正式PBF基础63层定义/顺序保留;正式Style fingerprint e0f9c2d1df38ae5d5953b561c52efe2f8590336de80b988e1d2935580d30b996
- Native实际层数65,R[0,63)、C[63,65);C只包含rn-weather-color和rn-weather-particles
- emptyBuiltinAnnotationLayerRemoved=true、annotationInstancesAtRemoval=0、annotationSourcePreserved=true;原内建Annotation Source仍在Native列表并Loaded
- 实际Native Sources为mapple:raster、navsea_delivery:vector、org.maplibre.annotations:annotations、rn-weather-fields:raster-array;tide Source确实缺失
- R Cache Build=0、Reuse=0;lastFallbackReason=missing-source、missingSourceIds包含tide;placementCapable=true、placementGatePassed=false
RDC经设备菜单STEP093→STEP113→EXPORT LAST SNAPSHOT,回读真实Native完整JSON,大小274,693字节,SHA-256:
cd491d2acca97119f1d4f2d6f77eb7b229c17b90d4be1181a59202308429b219
完整120帧窗口验证:120帧、24项Gate、每帧knownMask=0xffffff、120/120 decisionConsistent=true、decisionMismatch=0;完成帧为119 Full、0 Fast;另1帧pending。
| Gate | 最近120帧失败数 |
|---|---:|
| #3 cache-invalid | 120 |
| #4 revision-mismatch | 120 |
| #12 static-unstable | 105 |
| #13 placement-or-fade | 105 |
| #22 unsupported-continuous-layer | 0 |
| #23 unsupported-scene | 120 |
其他Gate当前窗口失败数为0。#22生命周期初期累计6次,不能称启动全程均无失败。#23仍因R不满足整体准入而持续失败。diagnostic decisionMismatch=0仅说明当前决策与trace一致,不代表Fast有效。
## 3. 与旧STEP-113相对照及有效性判断
原STEP-113旧APK的STATIC_RC Native层数66,多出内建org.maplibre.annotations.points Symbol落在C中,Gate #22/#23曾每帧失败,R Build=0、Fast=0。旧场景累计Full875,和新测试FullDelta920不是相同窗口/时长,不可据此计算帧率或性能提升。
新APK实际65层、C仅两张天气层,最近120帧#22为零,证实STEP116针对空Annotation/C准入的**局部修复有效**。然而真实R中缺tide、Placement/Fade条件未稳定、R Build/Reuse仍为0,#3/#4/#12/#13/#23持续失败;**Fast仍为0,没有任何性能收益证据**。没有改动或删除原63层,不以STEP114烟测替代。
## 4. 保存的证据和未覆盖的边界
本机Git忽略路径:artifacts/maplibre-particle/android-validation/step113-retest-20261011_121308/static_rc/,包括result.json、launch.txt、logcat.txt、native-full.json、screen.png、ui.xml。本轮截图仅存档,尚未人工验收像素/航标显示;没有执行五场景全部回归、Annotation真实增删、缺源恢复、Camera/Tile/Image负例、iOS或Native GTest(Mac先前CMake缺libuv)。本轮当前正式PBF用户目标已获得明确失败结论,不得标CLOSED/PASS。
后续必须由Particle Shared Core跨平台负责人确定R缺源零像素证明、Source同名重建的Full失效保证及Symbol Placement已提交GPU结果证书;不得伪造tide、删正式图层、禁用安全Gate或声称已有Fast性能改善。
注意:若本结果文档随后产生新的纯文档Git commit,APK仍绑定上述c48a72a9源码。后续使用STEP115严格HEAD预检时,不能拿新文档HEAD冒充该APK源码,也不得为修改审计证据而重新标记APK编译来源。未执行GSRL正式发行、Source Lock或Tag。

View File

@@ -0,0 +1,151 @@
# STEP-116 STEP-113 原 PBF 专用 APP 验收 v1
日期:2026-10-11(JST)。**以下命令交 RDC / 用户执行,Codex 本轮全部未运行。** 本轮源码状态 PARTIAL;缺 tide 与 Symbol R 准入仍阻塞,预期仍可能 Fast=0。不得拿 STEP114 Smoke 或旧 V23 APK 替代原 STATIC_RC。
## 1. RDC 源码锁定和 Native 定向验证
先重新读取 AGENTS、当前基线、STEP-116 审查/报告与 diff。审计包含 Native Annotation 修复的线程/生命周期、原63层逐层定义/顺序、移除对象重载、stop恢复、后续marker/style添加、五场景分支、生产Gate未变和七项新测试。执行只读命令:
```bash
cd /Users/tei/dev/MapLibreParticleLab
git branch --show-current
git rev-parse HEAD
git status --short
git diff --check
git diff -- src/mln/annotation/annotation_manager.cpp src/mln/annotation/annotation_manager.hpp src/mln/renderer/render_segment_json.hpp test/api/annotations.test.cpp test/renderer/retained_underlay_cache.test.cpp platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step113PbfGateScene.kt tools/particle_android_validation/verify_step113_fast_path.py tools/particle_android_validation/README.md 粒子开发记录.md
shasum -a 256 docs/40_任务/STEP-110_R0_Placement跨段归因只读调查_Codex任务_v1.md docs/40_任务/STEP-111_R段Placement结果等价与独立刷新可行性只读调查_Codex任务_v1.md
```
两任务书 SHA 应分别为 `33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0`、`f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4`。不要暂存它们。RDC按获授权的scoped流程收尾后,记录真正的最新HEAD;本报告起点 e2d202b 并不包含本轮修改。构建前 tracked 必须 clean;原未跟踪任务书可保留,build-info 的 git_dirty 不能单独用来推断源码不一致。
Native 定向命令逐项执行,上一项失败立即停止,保存完整 stdout/stderr 与首个真实 error;历史缺 libuv 不当成新结果,不运行旧runner:
```bash
cmake --preset macos-metal
```
```bash
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
```
```bash
build-macos-metal/mbgl-test-runner --gtest_filter='Step116Safety.*:Annotations.*:FastGateTrace.*:ParticleFastPath.*:RenderSegmentDiagnostics.*:RenderSegmentPlacementFree.*:RenderSegmentLocality.*:RenderSegmentPlan.*' --gtest_color=no
```
Annotations.Step116 三用例使用现有 point_annotation 图像做实际渲染比较;不能仅检查 getLayer 指针就声称像素 PASS。账本/谓词用例不构成 GPU rendered Fast,Metal host 成功也不代替 Android OpenGL/设备回退。若依赖/编译阻塞,保持现场并在报告标 BLOCKED/NOT RUN,Android集中构建是否继续由用户安排。
## 2. 用户一次手工 Android 构建与安装
在锁定的最新 HEAD 上,用户手工依序执行,Codex/RDC不得代用户启动 Android 构建或安装:
```bash
cd /Users/tei/dev/MapLibreParticleLab
git rev-parse HEAD
git status --short
./tools/particle_android_validation/build_validation_apk.sh
```
标准脚本使用 Java17、OpenGL/x86_64、strip native debug symbols,保存 build-info/build.log,检查 ZIP/ABI/体积/SHA。失败不得续用原路径旧 APK。
```bash
cat artifacts/maplibre-particle/android-validation/build-info.txt
shasum -a 256 artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
```
记录源码HEAD、branch、精确修改文件/SHA、构建日志、APK大小/ABI/SHA、设备序号及型号。不能仅凭 `13.6.1-particle.23` 或安装 Success 认定是新源码。
## 3. STEP115 preflight → 原 STATIC_RC ≥75秒
用户安装成功后,由 RDC / 用户执行,serial/adb路径明确:
```bash
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --adb /Users/tei/Library/Android/sdk/platform-tools/adb --serial 192.168.200.139:5555 --preflight-only
```
preflight 检查当前 tracked clean、build-info.commit==HEAD、APK SHA==build-info.sha256、设备 ABI x86_64、安装包 SHA==本地 APK。STALE_APK / SOURCE_NOT_LOCKED / SHA失败时停止,必须先纠正版本。PREFLIGHT_PASS只代表版本一致。
```bash
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --adb /Users/tei/Library/Android/sdk/platform-tools/adb --serial 192.168.200.139:5555 --scenario STATIC_RC --seconds 90
```
使用90秒,脚本在取得 PID 后实际等待完整90秒(≥75秒);加载太慢导致摘要不足则 INSUFFICIENT_SAMPLES / NO_READY_SUMMARY,不推断Fast。保留 result.json、logcat.txt、launch.txt、实际观察秒数。摘要须同sceneGeneration/lifecycle、planValid/basePreserved、24项、mismatch=0、累计计数单调;独立blocked日志和摘要撤销不能被早先Fast盖过。退出2为没有可接受Fast证据,退出3为版本/环境等阻塞,不能改成0制造PASS。
`FAST_OBSERVED`仅指真实 Native fastRendered 增量>0,不是安全/像素/性能终验。要求原63层manifest、NavSea原PBF/Sprite/Glyph/Vector URI及定义/顺序不变,仅声明SDK空辅助层调整;正常静态预期Native65层/C仅weather-color和raster-particle,实际以新导出为准。PUBLIC PBF若变成非63层,场景保留现场并 BLOCKED,不更换简化Style来通过。
## 4. 手动导出完整120帧及设备画面
保持同次 STATIC_RC 运行,用户在 STEP093 → STEP113 菜单选择 EXPORT LAST SNAPSHOT。这是最近一次3秒共享采样,不额外渲染。以下由 RDC / 用户回读,必须核对所选文件确属本次新运行:
```bash
mkdir -p artifacts/maplibre-particle/android-validation/step116
step116_file="$(/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 shell run-as org.maplibre.android.testapp ls -t files | tr -d '\r' | grep -E '^step113-STATIC_RC-.*[.]json$' | head -n 1)"
test -n "$step116_file"
/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 exec-out run-as org.maplibre.android.testapp cat "files/$step116_file" > artifacts/maplibre-particle/android-validation/step116/static-rc-full.json
shasum -a 256 artifacts/maplibre-particle/android-validation/step116/static-rc-full.json
/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 exec-out screencap -p > artifacts/maplibre-particle/android-validation/step116/static-rc-screen.png
/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 logcat -b crash -d > artifacts/maplibre-particle/android-validation/step116/crash.log
```
`test -n`失败立即停止后续命令。同一个Full JSON先做离线证据结构检查(不是安全测试):
```bash
python3 -B - <<'PY'
import json
from pathlib import Path
p = Path('artifacts/maplibre-particle/android-validation/step116/static-rc-full.json')
v = json.loads(p.read_text())
assert v['scenario'] == 'STATIC_RC' and v['sceneReady'] and not v['blockedReason']
assert v['commands']['emptyBuiltinAnnotationLayerRemoved'] is True
assert v['commands']['annotationInstancesAtRemoval'] == 0
assert v['commands']['annotationSourcePreserved'] is True
assert v['styleParity']['baseLayerCount'] == 63 and v['styleParity']['baseLayersPreserved']
assert not v['styleParity']['full113Equivalent']
n = v['native']; t = n['fastGateTrace']; frames = t['frames']
assert n['valid'] and len(frames) == t['windowCount'] == 120
assert len(t['gates']) == 24 and t['decisionMismatch'] == 0
assert [g['order'] for g in t['gates']] == list(range(1, 25))
assert all(f['knownMask'] == 0xffffff and f['decisionConsistent'] and len(f['gateStatus']) == 24 for f in frames)
assert all(a['sequence'] < b['sequence'] for a, b in zip(frames, frames[1:]))
complete = [f for f in frames if f['completed']]
assert complete and all(f['post'] is not None for f in complete)
print('结构完整;仍须独立核对源码/制品SHA、同次generation/lifecycle及用户画面')
print('生命周期 Full/Fast:', t['fullRendered'], t['fastRendered'])
print('窗口 completed/Full/Fast:', len(complete), t['windowFullRendered'], t['windowFastRendered'])
for s in n['segments']:
if s['strategy'] == 'RETAINED':
print('R', s['beginIndex'], s['endExclusive'], s['missingSourceIds'],
s['placementGatePassed'], s['cache'])
print('窗口失败项:', [(g['order'], g['reason'], g['windowFailures']) for g in t['gates'] if g['windowFailures']])
PY
```
允许最后一帧 pending,但不能把其post作为当前Full。pre为上次完成树、post/full为本次完成树,避免跨帧拼状态。环形窗口可能覆盖Mutation瞬间,必须变更前/后分别及时手动导出,若没捕获则如实 NOT VERIFIED。120条帧不等于120条completed。
用户确认基础PBF/标注/天气色带/粒子可见、无漏内容/闪烁/层序错误;截图归档及用户判断不能代替同条件Full/Fast像素比较。当前缺源/Placement未解时应记录 Fast0 和每项失败,不宣布性能提升。
## 5. 失败定位与受保护回归
| 结果 | 判断与下一步 |
|---|---|
| missingSourceIds含tide,R Build0 | 正式Style缺源仍fail-closed;不补假源、不删tide-spots,记录Source类型/缺失层ID |
| 缺源消失但Symbol placementGatePassed=false | 独立Symbol准入阻塞,检查Full changed/stale/fade与pre/post;不要用等候时间放行 |
| #22仍失败 | 查Native C范围实际层清单;空层调整是否被撤销/失败,普通circle或真实Annotation仍应阻挡 |
| #23仍失败 | 查retainedSupported和preparedScene聚合,消除#22不保证#23通过 |
| mismatch>0 / unknown Gate /版本不符 | 停止记录通过,保留首错和完整证据 |
| Fast>0 | 先核查没有缺源/场景撤销、原Style完整,继续资源变化/回退/画面对照;仅记FAST_OBSERVED |
原五场景仍在。每项独立运行并保留导出,不连续启动冒充采样:
```bash
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --scenario PBF63 --seconds 90
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --scenario PARTICLE_OFF --seconds 90
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --scenario PARTIAL_RCRR --seconds 90
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --scenario SOURCE_CHANGE --seconds 90
```
这些场景可能预期Fast0,脚本退出2是受保护负例结果,不自动视为回归失败或改预期。SOURCE_CHANGE保留circle与Annotation;用户执行一次原 MUTATE PROBE SOURCE ONCE,导出前后commands和native lastMutation/affectedSegmentIndices,区分同时发生的真实瓦片更新。
Native七项新测试之外,还需 RDC/用户记录:遗留Annotation拒绝移除;STATIC_RC后添加真实Marker(同步恢复points再Full、像素可见),no-op更新;额外Style.addLayer/重排不删除新内容;disable、退出到STEP102、Style reload及后台恢复;Source创建/同名更新/async Tile、Glyph/Image变化必定invalid/Full。Test App没有新增自动Mutation来假装覆盖这些事件;若当前UI无法触发,标该项设备NOT VERIFIED并使用对应Native真实测试/另行授权的操作。STEP114四场景回归使用其既有命令;Smoke结果只归其独立场景。
最终提交证据应包含最新HEAD/精确源码diff、Native日志/首错、用户build-info/build.log/APK及安装SHA、设备信息、原STATIC_RC摘要和完整JSON、manifest、24Gate/真实Build/Reuse/Full/Fast增量、回退与画面。编译成功、preflight成功或静态检查都不把STEP-116判为CLOSED/PASS。

View File

@@ -0,0 +1,165 @@
# STEP-117 FZG1 原 PBF 真实 Fast 验收计划 v1
日期:2026-10-11(JST)。**本计划全部运行命令交RDC/普通Terminal;Codex本轮未执行。当前安全准入BLOCKED,测试源码交付不表示真实Fast已修复。**
## 1. 执行顺序与停止条件
先独立审计STEP117调查、diff和七项测试,确认B1缺源零贡献与B2 Symbol结果证书仍未实现。本轮只有测试/文档,不应期待重新打APK获得不同Fast行为。RDC可先验证C++回归;粒子负责人协调Shared Core证书设计及正式tide语义,再授权具体实现。完成可证明的实现并复核后,安排一次完整Android build/install/原STATIC_RC集中验收。
本任务不授权Codex执行任何Native编译/测试、Gradle/ADB/安装、Jest、commit/push。RDC后续Git收尾须按独立授权scoped提交,核对origin远端HEAD,保护STEP110/111。Android与设备命令只能在用户明确授权的对应阶段执行。当前缺libuv历史记录只用于排查;不跳过失败、不改门槛、不运行旧runner、不改build-info使旧包冒充新源码。
遇到编译/测试失败、Source/Style版本不符、mismatch>0、未知Gate、错误恢复/漏内容或Fast仍为0,保存首个真实error/target及完整日志路径,停止相应通过判定。不要换STEP114烟测、删原层或补假tide来继续。
## 2. 独立源码与定向Native验收
```bash
cd /Users/tei/dev/MapLibreParticleLab
git branch --show-current
git rev-parse HEAD
git status --short
git diff --check
git diff -- test/renderer/retained_underlay_cache.test.cpp test/renderer/render_orchestrator_error.test.cpp 粒子开发记录.md
shasum -a 256 docs/40_任务/STEP-110_R0_Placement跨段归因只读调查_Codex任务_v1.md docs/40_任务/STEP-111_R段Placement结果等价与独立刷新可行性只读调查_Codex任务_v1.md
```
STEP110/111预期SHA分别为 `33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0`、`f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4`。查看新增四份文档及报告静态核查章节。生产src/include/Shader/TestApp/旧测试断言应与起点保持一致;不得把test fixture的markBuildSucceeded或测试线程注入事件当作实际GPU/并发完成。
以下逐项串行运行,上一项失败立即停止。使用当前Mac配置的CMake;如果PATH没有cmake,可用已配置的 `/Users/tei/Library/Android/sdk/cmake/3.31.6/bin/cmake`,不得自行升级依赖。
```bash
cmake --preset macos-metal
```
```bash
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
```
```bash
build-macos-metal/mbgl-test-runner --gtest_filter='Step117Safety.*:Step116Safety.*:Annotations.*:RenderOrchestratorError.*:RenderOrchestratorTeardown.*:FastGateTrace.*:ParticleFastPath.*:RetainedUnderlayCache.*:RetainedUnderlayRevision.*:RenderSegmentDiagnostics.*:RenderSegmentPlacementFree.*:RenderSegmentLocality.*:RenderSegmentPlan.*' --gtest_color=no
```
新增Step117Safety必须实际运行7项,不能以filter未匹配的exit0通过。test/CMakeLists.txt已登记两个源文件,不需要绕过原target。记录当前HEAD、runner构建来源、命令、exit/耗时、用例数量、完整日志路径。Annotations.Step116是实际出图比较;其通过也不替代原PBF的Symbol结果证明。Mac Metal runner成功不代表Android OpenGL或iOS设备PASS。
## 3. 一次未来授权的Android制品闭环
在B1/B2受控实现完成、源码已锁定且tracked clean后,用户授权RDC/普通Terminal逐项执行标准Pipeline。保护未跟踪MD保留,git_dirty=true需结合tracked状态判断。记录真正的源HEAD,不能拿本轮起点43e5f5b当作包含未来实现的提交。
```bash
cd /Users/tei/dev/MapLibreParticleLab
git rev-parse HEAD
git status --short
./tools/particle_android_validation/build_validation_apk.sh
```
Pipeline必须Java17、OpenGL/x86_64、strip,成功后核对ZIP、唯一ABI、libmaplibre.so、体积、完整build.log/build-info与SHA;失败不复用旧APK。版本名13.6.1-particle.23本身不能标识源码。
```bash
cat artifacts/maplibre-particle/android-validation/build-info.txt
shasum -a 256 artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
```
不得卸载/清数据。设备serial须重新核实为FZG1-2;变化时显式记录并修改后续参数。回读已安装base.apk SHA,由以下现有工具复核同HEAD/本地APK/设备安装包:
```bash
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --adb /Users/tei/Library/Android/sdk/platform-tools/adb --serial 192.168.200.139:5555 --preflight-only
```
STALE_APK、SOURCE_NOT_LOCKED、ABI/SHA不符立即停止。PREFLIGHT_PASS仅说明版本闭环;旧c48a72a9 APK与本轮源码HEAD不同,即使生产文件未改也不能修改来源记录。
## 4. 原STATIC_RC≥90秒正式验收
```bash
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --adb /Users/tei/Library/Android/sdk/platform-tools/adb --serial 192.168.200.139:5555 --scenario STATIC_RC --seconds 90
```
从取得同PID起实际观察至少90秒,取得前后同sceneGeneration/lifecycle的摘要,计数单调、不撤销、不重置。保存result.json/logcat/launch、实际观察秒数。exit2/NO_FAST_OBSERVED如实失败;exit3为版本/环境阻塞。FAST_OBSERVED只是必要条件。
正式通过同时要求:
- 原Style的63基础层定义/顺序保留,记录productionFingerprint与baseLayerManifest、productionSources;原Style/Sprite/Glyph/Vector URI一致。若服务器内容改变导致fingerprint或层数不同,停止跨版本比较,交Style所有者协调。
- STEP116空SDK Annotation规则保留:零实例后一次受控移除points,Source保留;原C正常只有WeatherColor+Particle,Native65层/R[0,63)/C[63,65)。后续真实Annotation/Style改变不得被再次删除。
- 实际每个R Cache Build>0、Reuse>0,同生命周期FastDelta>0;completed fast-rendered帧必须cacheValid/revision一致、24Gate已知且全部PASS、mismatch=0。pending帧不计成功。
- 缺源准入若未来获证书,必须能回读该absent source的零绘制/旧数据排除及同名恢复失效证据;missingSourceIds非空不能自动通过,也不能简单规定非空一律失败而回避新证书审查。
- Symbol缓存绑定成功提交的R结果证书,列出受影响bucket/atlas/collision/opacity/动态属性/camera/context和变更代次;源码/设备证据中不得强制清三字段或改Gate。
- 用户验收底图、海岸/等深线、航标、文字、天气色带与粒子,无漏内容/闪烁/层序错误;同条件Full/Fast像素对照及资源变化回退分别取证。性能提升需额外相同条件测量,不能从Full减少单独推断。
## 5. 完整120帧与用户画面
同次运行在STEP093→STEP113菜单手动EXPORT LAST SNAPSHOT。导出由用户触发,RDC回读同次generation/lifecycle的文件;确认文件名/时间不属于旧运行。以下shell命令逐项执行,任何失败立即停;变量只用于本次任务:
```bash
mkdir -p artifacts/maplibre-particle/android-validation/step117
step117_file="$(/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 shell run-as org.maplibre.android.testapp ls -t files | tr -d '\r' | grep -E '^step113-STATIC_RC-.*[.]json$' | head -n 1)"
test -n "$step117_file"
/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 exec-out run-as org.maplibre.android.testapp cat "files/$step117_file" > artifacts/maplibre-particle/android-validation/step117/static-rc-full.json
shasum -a 256 artifacts/maplibre-particle/android-validation/step117/static-rc-full.json
/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 exec-out screencap -p > artifacts/maplibre-particle/android-validation/step117/static-rc-screen.png
/Users/tei/Library/Android/sdk/platform-tools/adb -s 192.168.200.139:5555 logcat -b crash -d > artifacts/maplibre-particle/android-validation/step117/crash.log
```
完整JSON离线结构核对示例(不是新安全证书/性能验收):
```bash
python3 -B - <<'PY'
import json
from pathlib import Path
v = json.loads(Path('artifacts/maplibre-particle/android-validation/step117/static-rc-full.json').read_text())
assert v['scenario'] == 'STATIC_RC' and v['sceneReady'] and not v['blockedReason']
assert v['styleParity']['baseLayerCount'] == 63 and v['styleParity']['baseLayersPreserved']
assert v['commands']['emptyBuiltinAnnotationLayerRemoved']
assert v['commands']['annotationInstancesAtRemoval'] == 0
assert v['commands']['annotationSourcePreserved']
n = v['native']; t = n['fastGateTrace']; fs = t['frames']
assert n['valid'] and n['styleLayerCount'] == 65
assert len(fs) == t['windowCount'] == 120
assert len(t['gates']) == 24 and t['decisionMismatch'] == 0
assert [g['order'] for g in t['gates']] == list(range(1, 25))
assert all(f['knownMask'] == 0xffffff and f['decisionConsistent'] and len(f['gateStatus']) == 24 for f in fs)
assert all(a['sequence'] < b['sequence'] for a, b in zip(fs, fs[1:]))
assert all(f['post'] is not None for f in fs if f['completed'])
assert all(f['post'] is None for f in fs if not f['completed'])
assert [(s['strategy'], s['beginIndex'], s['endExclusive']) for s in n['segments']] == [
('RETAINED', 0, 63), ('CONTINUOUS', 63, 65)]
fast = [f for f in fs if f['completed'] and f['outcome'] == 'fast-rendered']
assert fast, '原PBF当前窗口没有完成Fast帧;保持FAIL/BLOCKED'
assert all(f['failMask'] == 0 and all(c['exists'] and c['valid'] and
c['buildRevision'] == c['staticRevision'] for c in f['cacheInputs']) for f in fast)
assert all(s['cache']['build'] > 0 and s['cache']['reuse'] > 0 for s in n['segments'] if s['strategy'] == 'RETAINED')
print('必要结构/计数成立;继续检查证书、同次SHA/manifest、变更前后帧与用户画面')
PY
```
在当前STEP117无准入实现的代码上,Fast/Build断言预期不能通过;不得删除这些断言制造PASS。pre为上一完成树,post/full为本次完成树;#13另取当前controller,不能混为同帧。环形窗口可能覆盖变更瞬间,必须每项操作前/后及时导出;未捕获则NOT VERIFIED。
## 6. 安全反例与旧五场景
| 操作 | 必须取证的正确行为 | 当前入口/限制 |
|---|---|---|
| 初始缺源 | 没有证书时missing-source、Build/Fast0;未来证书需证明双group零贡献和无旧数据 | 原STATIC_RC真实缺tide;不补造source |
| 同名source创建/重建/数据到达 | 下一次尝试Full,旧证书和cache失效;真实内容可见后才重建 | 新C++真实sourceDiff用例及状态机;当前App没有生产tide注入入口,需Integration/真实来源协调,设备项NOT VERIFIED |
| 已有旧Symbol后缺源/合法Style替换 | 两类group停用请求执行,旧drawables/buckets排除;不得复用旧R | 当前测试只有registry生命周期,无实际GPU旧数据证书;必须追加真正渲染用例后验收 |
| Tile/Glyph/Image异步更新 | #5/#8/#14或对应稳定性Gate拒绝旧Fast,受影响R重建;未加载/fading继续fail-closed | C++回调注入不等同网络并发;实际资源延迟与事件顺序需独立工具/测试授权 |
| Symbol collision/fade/anchor/orientation/沿线 | Full/Fast最终像素等价;所有碰撞受害R失效;时间相关opacity不能冻结 | 结果证书当前不存在;不能用默认空桶测试代替 |
| Source mutation | 保留原SOURCE_CHANGE circle负例和手动MUTATE PROBE SOURCE ONCE;记录sourceId/affectedSegmentIndices与真实Full | 当前probe属C,不能冒充R的tide恢复 |
| Style mutation/重排/添加 | non-camera/topology/fingerprint拒绝旧树;内容不删除 | 原入口无自动全矩阵;没有触发能力则NOT VERIFIED |
| 真实Annotation add/no-op update/reload | STEP116同步恢复points并可见;#24/层表拒绝旧Fast;空层验收条件撤销 | Annotations.Step116图像回归和用户合法操作;不可周期删真实层 |
| Camera/viewport/pixelRatio/projection | #6/#7/#8、cache key/context失效,Full后正确重建,无拖影或错位 | 用户pan/zoom/rotate/pitch/resize;逐事件导出 |
| context/后台/恢复/disable/退出场景 | PreparedTree/资源正确失效释放,无旧GPU数据复活 | 用户设备生命周期及Native实际Renderer测试;现有callback用例不替代 |
原五场景依次独立运行、分别保存日志/导出/画面。以下复测工具命令由RDC在同SHA预检后执行,SOURCE_CHANGE保留普通circle与Annotation负例:
```bash
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --adb /Users/tei/Library/Android/sdk/platform-tools/adb --serial 192.168.200.139:5555 --scenario PBF63 --seconds 90
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --adb /Users/tei/Library/Android/sdk/platform-tools/adb --serial 192.168.200.139:5555 --scenario PARTICLE_OFF --seconds 90
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --adb /Users/tei/Library/Android/sdk/platform-tools/adb --serial 192.168.200.139:5555 --scenario PARTIAL_RCRR --seconds 90
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --adb /Users/tei/Library/Android/sdk/platform-tools/adb --serial 192.168.200.139:5555 --scenario SOURCE_CHANGE --seconds 90
```
受保护负例可能预期Fast0,exit2应按场景语义记录,不能调整工具返回码或删除负例。STATIC_RC正式正例仍要求真实Fast,不接受STEP114 FAST_SMOKE替代。
## 7. 最终证据包
RDC回填:锁定源HEAD与精确diff/相关源码SHA、scoped提交与远端HEAD(若获授权执行)、Native配置/构建/七测试日志、首错/阻塞;build-info/build.log/APK与安装包SHA、设备型号/serial;原Style fingerprint/63层manifest/C层表;同次90秒摘要、120帧完整JSON及SHA、每项24Gate/cache/Full/Fast/pre/post;Mutation前后/异步/碰撞/GPU生命周期证书;用户画面及同条件Full/Fast对照。
没有证书保持BLOCKED;编译成功、静态检查、PREFLIGHT_PASS或测试进程exit0不能代替原PBF真实Fast安全验收。

View File

@@ -0,0 +1,12 @@
# STEP-118 FZG1-2 tide移除专用TestApp验收
使用Mac仓库/现有标准build_validation_apk.sh和明确Android ADB serial 192.168.200.139:5555。严格串行,每一步退出值和实际产物均需PASS才能推进。完整证据存Git忽略artifacts目录。
1. Git源HEAD锁/Integration当前main,确认只改TestApp Kotlin、Python verifier、说明及报告,两份STEP110/111未跟踪用户任务书不动。
2. 标准 ./tools/particle_android_validation/build_validation_apk.sh。核验Gradle build SUCCESS、ZIP/strip/native x86_64、APK/source commit及sha256。
3. ADB -s 192.168.200.139:5555 install -r新APK,Success后pm path/设备base.apk SHA与本地APK一致。
4. python3 -B tools/particle_android_validation/verify_step113_fast_path.py --preflight-only。必须PREFLIGHT_PASS。
5. python3 -B tools/particle_android_validation/verify_step113_fast_path.py --scenario STATIC_RC_NO_TIDE --seconds 90。验证新scene的实际Native Layer64、原生产63层manifest及同旧fingerprint、有效62基础层、C天气2层、R[0,62)、tide Source确实不存在、missingSourceIds不再包含tide。
6. 读取result.json的真实FastDelta/FullDelta、R Build/Reuse/lastFallbackReason、Gate #3/#4/#12/#13/#22/#23、decisionMismatch。退出码2 NO_FAST_OBSERVED是负结论,不允许跳过。
7. 必要时通过设备菜单EXPORT LAST SNAPSHOT并用ADB run-as取原生JSON、截屏;Native120帧24Gate的各窗口失败计数和原动画/像素效果分开验收。
8. 新场景不等于原63层:原STATIC_RC仍保留严格basePreserved=true与真实missing-source负基线。若Fast仍为0且placementGatePassed=false,结论只能是缺源局部已解除、Fast仍BLOCKED;不能冒称已获得性能收益。真正Fast>0仍需要资源/Source/Style变更回退Full及用户实际画面验收。

View File

@@ -0,0 +1,62 @@
# STEP-118 潮汐引用排除版:新 APK 真实设备验收报告
日期:2026-10-11 JST。**最终状态:PARTIAL;tide 缺源阻塞修复PASS、首次R缓存/Fast目标FAIL。**
## 二进制闭环
- 源码 HEAD(测试副本实现,正式PBF未改):3051196054c7b69815c298040dae61d26499d9f2
- 标准 Android OpenGL Debug/x86_64 BUILD SUCCESS;Java17,APK66,603,850字节,libmaplibre.so40,035,512字节。
- 新APK SHA-256:44afcfabe0cf4f1ec93f35899241bd73093aa6db4b5cb82ca2120dd531b8f3e9
- RDC Android FZG1-2 (ADB明确 serial 192.168.200.139:5555):adb install -r返回Success,设备base.apk SHA与本地完全一致。Python preflight为PREFLIGHT_PASS。
- 启动专用 STEP-113 的新增STATIC_RC_NO_TIDE,实际进程PID=22825;没有使用STEP-114烟测、旧APK或修改Gate。
## 90秒真实运行结果
| 指标 | 结果 |
|---|---|
| 当前场景 | STATIC_RC_NO_TIDE(测试副本,非正式63层原样) |
| 请求/实际观察时间 | 90 / 90.008秒 |
| 场景Ready / Native计划 | True / True |
| 原生产基础Layer / 有效基础Layer | 63 / 62 |
| 唯一被排除引用 | tide-spots → tide(1层) |
| 原始basePreserved | False(正确,派生实验不是原样63层) |
| Native实际Layer总数 | 64 |
| R段范围 | [0,62) |
| 失效/缺失Source IDs | [] |
| R fallback reason | static-placement |
| PlacementCapable / PlacementGatePassed | True / False |
| R Cache Build / Reuse | 0 / 0 |
| FastDelta / FullDelta | 0 / 942 |
| 完整摘要个数 / windowCount | 2 / 120 |
| FastWindow / FullWindow | 0 / 119 |
| decisionMismatch | 0 |
| 自动验证最终 | NO_FAST_OBSERVED,退出码2 |
## 当前Gate累计诊断
注意:下面为APP当前生命周期累计失败次数,不是最近120帧窗口的精确失败数;同一帧可能命中多项Gate。
| Gate | 累计失败次数 |
|---|---:|
| unsupported-scene | 1778 |
| revision-mismatch | 1773 |
| cache-invalid | 1772 |
| placement-or-fade | 1587 |
| static-unstable | 1583 |
| content-mutation | 11 |
| unsupported-continuous-layer | 6 |
| style-not-loaded | 5 |
| no-particle-repaint | 5 |
| camera-changed | 3 |
| update-parameters-changed | 3 |
| no-prepared-tree | 1 |
| viewport-changed | 1 |
## 证据、限制与下一步
- 原始result.json保存于Git忽略路径 artifacts/maplibre-particle/android-validation/step113-retest-20261011_140448/static_rc_no_tide/result.json,SHA-256 d3d20fc35bbd17e9f0ea0dd014ca4af8e0c5c5284d9f0c009bc20981d876ff10。
- 设备截图screen.png保存于同目录(SHA-256 c8b57a8432626c9209ca9a614a747728d4256bd09273b28c26a793a75a6d44d4),保留参考但尚未做人工图像与航标视觉PASS。
- STEP-116同设备原STATIC_RC(原63基础Layer)R Build/Reuse=0,fallback=missing-source,缺tide。新测试副本R Build/Reuse仍0、Fast0,但fallback真实改变为static-placement且missingSourceIds为空,故**缺失tide阻塞已解除**;没有性能收益。
- 原PBF生产Style与Sprite/Glyph/Tile URI、Server、NavSea并未修改;正式生产若决定永久去潮汐,应由Style所有者按跨项目流程移除tide-spots或修正正式数据。当前实验不能冒充已修改正式PBF。
- 本轮没有放宽Symbol Placement、fade、24 Gate或改变Shared Core。剩余原因是R中其它Symbol Layer的Placement/Transition首次GPU缓存安全证书未建立;需要Shared Core受控设计与双平台验证。
- 尚未进行新R>0后的Source/Style负例、原63五场景批量回归、GTest、Metal、用户人工像素验收;因此整体保持PARTIAL而非CLOSED/PASS。

View File

@@ -0,0 +1,42 @@
# STEP-119 Android STEP-119 原PBF真实Fast实机验收结果
日期:2026-10-11 JST。执行者:GPT指挥RDC,Mac/Android FZG1-2。**阶段结论:ANDROID_FAST_CORE_PASS / OVERALL_PARTIAL(仅完成本页证据覆盖范围,不能宣称所有失效与双平台PASS)。**
## 0. 源码和设备版本锁
- Integration origin/main 与本地一致:80667d6c343411d03a760425438e3d292f452118。
- Particle本次APK源码commit:3d989a00a822989339a6d7cb4d23acc04396670e;已由RDC限定文件提交并push origin/particle/main成功、本地远端HEAD一致。
- APK标准OpenGL/x86_64 build_validation_apk.sh:Gradle BUILD SUCCESSFUL in 3m 40s;86 tasks(22 executed,64 up-to-date);APK 66,702,125字节,libmaplibre.so 40,138,024字节;SHA-256 57d68800770d431a2ce9247406f636e7a01cc19fccaf4c59f5eb3a7c99128135。
- 设备:192.168.200.139:5555,ABI x86_64。ADB install -r返回Success,设备base.apk SHA-256与本地新APK完全相同。Python --preflight-only返回PREFLIGHT_PASS。
- 不是正式release;没有修改原PBF服务器Style、业务API、iOS/Metal发行版本、SOURCE_LOCK或GSRL Tag。
## 1. Mac与C++编译范围
- Mac Metal CMake首次PATH中找不到cmake;用已有绝对路径重试后CMake配置因**原有libuv环境缺失**终止。Homebrew尝试安装libuv时提示将连带升级CMake/Python/OpenSSL,RDC中止安装,未改变项目第三方依赖和Android构建工具链。**Mac原生mbgl-test-runner未编译/运行,13项Step119SymbolResults非Jest测试没有实际执行;不能列为GTest PASS。**
- Android NDK28.2 OpenGL/x86_64真实compile_commands.json,C++20 + 原项目严格-Werror逐个-fsyntax-only检查:7个生产编译单元和2个已登记GTest单元,共9个全部PASS。修复2处util::toString命名空间和测试Immutable所有权类型问题。
- Android真实Gradle编译链接、符号打包、APK安装已PASS,证明当前Android源码不仅能语法检查还可以全链构建。
## 2. 正向:STATIC_RC_NO_TIDE派生62基础图层+两天气图层
- 在同一正式PBF URL读取原始63基础层;**测试副本只移除唯一tide-spots引用**,远端真实Style未改。
- 实测原基础63 / 有效基础62 / Native64,R=[0,62),C=[62,64),Source: mapple/navsea_delivery/org.maplibre.annotations/rn-weather-fields,missingSourceIds=[],planValid=true。
- 设备App PID 27241,观察 90.010秒,CompleteSummary=2,windowCount=120。
- 当前最后Snapshot R cache:Build=1、Reuse=3276(累计),state=reuse,placementCapable=True,placementGatePassed=True。
- 实际观察期FastDelta=1819,FullDelta=0,最近120帧Fast=119/Full=0(第120条可能pending),decisionMismatch=0。
- Gate历史累计的static-unstable/cache-invalid等失败包含**App启动阶段**,不能解读为当前120帧仍失败;当前120帧是完整119Fast/0Full/1pending,决定匹配为0。最终R.reason字段中的静态字符串保留最后历史fallbackReason,不等于当前frame fallback。
- 验证器状态FAST_OBSERVED/exit0;它本身只验计数,R Build/Reuse、Gate由独立解析真实Native摘要后确认。
## 3. 负向:原STATIC_RC完整63基础+两天气图层
- 同一APK另起App PID 27492,观察 90.010秒。
- 原始basePreserved=true,Native65,R=[0,63),C=[63,65),sources仍没有tide,missingSourceIds=['tide'],reason=missing-source。
- R Build=0 / Reuse=0,FastDelta=0 / FullDelta=305,最近120帧Fast=0、Full=119,decisionMismatch=0。
- 验证器返回NO_FAST_OBSERVED/exit2:**这是负对照的预期安全拒绝(该测试目的PASS),不是STEP119正向失败;也不是原63正常可用Fast的证据。**
## 4. 原始证据
- 正向 result.json:artifacts/maplibre-particle/android-validation/step113-retest-20261011_170012/static_rc_no_tide/result.json;SHA-256=8718ce113820e7cc0cc2b4d5b801e6847c92e9ced79cd4fecbee58bfeef1c27c。
- 正向设备截图 PNG 1920x1200:artifacts/maplibre-particle/android-validation/step113-retest-20261011_170012/static_rc_no_tide/screen.png;SHA-256=ce3480d7ee934dc871c1035cbcaf84e61bbcefe680717001f05751dc0e16fd23。该截图是设备取证,**未开展人工作业图像对比或Symbol无重影验收**。
- 负向 result.json:artifacts/maplibre-particle/android-validation/step113-retest-20261011_170359/static_rc/result.json;SHA-256=0cc425ca138188af38aa04919631f2374257d2888630473fc93b4bff120ce128。
- APK build-info.txt、build.log、严格C++定向语法日志均在原Git忽略artifacts目录。两份保护的STEP110/111 MD不提交、哈希不变。
## 5. 没有验证的边界
- 缺少Mac libuv导致本次**未真正运行13项新GTest、Metal/iOS二进制或iOS实体机**;Android GL PASS不等于Metal PASS。
- 未逐项做Source/Style/glyph/atlas/Annotation、相机Pan/Zoom/Rotate、viewport/pixelRatio、context恢复后的证书失效和重Build/Reuse真机负例。
- 未进行同相机强制Full截图与Fast静态Symbol像素对比,也没有用户实际航标/文字/图标层序验收。
- 因此本阶段判定**核心功能Android真机Build/Reuse/Fast成功且缺源fail-closed成功,整体仍PARTIAL**。后续应优先在同一APK上完成状态变化负例与人工像素,再由Mac Metal独立路径验证;不得重启不必要工具链或因此声称正式GSRL发版。

View File

@@ -0,0 +1,68 @@
# STEP-119 RCRR 多配置实机与验证矩阵 v1
日期:2026-10-11(JST)。执行入口:Mac.lan RDC,Particle `particle/main`。状态:**一组真机完成;三组新配置已实现、静态核对通过但未构建/上机**。非最终验收。
## 1. 场景与边界
本轮沿用 STEP-119 源码 APK(commit `3d989a00a822989339a6d7cb4d23acc04396670e`,SHA-256 `57d68800770d431a2ce9247406f636e7a01cc19fccaf4c59f5eb3a7c99128135`),不重复已通过的 Android 原生完整编译。设备 `192.168.200.139:5555`,x86_64。当前远端 HEAD 起点为 `46c88b0209474376225b54ae0ad701881673743d`,其与 APK commit 的后续差异为已有报告/Step 文档,而非新 Android 源码。
不改正式 PBF/Sprite/Glyph、Source 接口、Shared Core、24 Gate、发布版本,不清理 STEP-110/111 两份未跟踪任务书。保留原 `PARTIAL_RCRR`、原 `STATIC_RC` 与 `STATIC_RC_NO_TIDE`,后续新场景只在测试副本中排除唯一 `tide-spots→tide` 孤立 Symbol 层,并恢复空 Annotation 辅助层的正常生命周期。
## 2. 已执行:原 PARTIAL_RCRR
- 80.007 秒实机,PID `28169`,完整摘要 3 份,同源 APK/设备 SHA 一致。
- 原始 Native 图层 66,原 63 PBF + 2 天气 + 1 SDK Annotation;R[0,3)、C[3,39)、R[39,64)、R[64,66)。
- R0:Build 0、Reuse 0、Fallback 595、missingSourceIds=[tide];R2:Build 1、Reuse 536;R3:Build 1、Reuse 536。
- 观测区间:FastDelta=0、FullDelta=596,最后 120 帧 Full=119、Fast=0,decisionMismatch=0。
- Native 24 Gate 累计 `unsupported-continuous-layer=599`、`unsupported-scene=599`、`cache-invalid=595`;这些是场景累计,**不是**对应原因的独立帧数。
- 结论:**全帧 Fast 安全拒绝通过;局部 R2/R3 的 Build/Reuse 已实际观察到**。这不构成 RCRR 加速性能通过。
原证据:
`artifacts/maplibre-particle/android-validation/step119-rcrr-observe-20261011_172720/partial_rcrr/result.json`,同目录有 `logcat.txt` 与 `screen.png`。
## 3. 基于真实设备 Style Manifest 校准分段
从设备 `run-as` 只读提取的 `step113-STATIC_RC-324220201.json`,包含当前实际 63 层 `baseLayerManifest`;其 PBF 指纹 `e0f9c2d1df38ae5d5953b561c52efe2f8590336de80b988e1d2935580d30b996` 与已通过 Fast 的场景一致。
原基础层:background=0、base-raster=1、tide-spots=2、sea-area-fill=3、clip-outline=31、special-pattern-line-754=37、最后的 place-labels-land-ja=62。天气插在 clip-outline **之后**。因此原 R0 是前三层(包括 tide),天气并未落入 R0;原 C1 是宽 C,包含天气与普通静态 PBF 层。禁止再用此前错误的“天气在 R0”解释原负例。
新增 Test App 变体(全部保留其余 62 原图层、天气数据与顺序;移除 SDK 空 Annotation 辅助层;Native 总层数预期 64):
| 场景 | 期望 R/C 索引 | 主要目标 |
|---|---|---|
| `RCRR_C_NARROW_NO_TIDE` | R[0,31)、C[31,33)、R[33,63)、R[63,64) | C 仅含 WeatherColor/Particle;检验所有 R 缓存与整帧 Fast 能否真实通过 |
| `RCRR_C_WIDE_NO_TIDE` | R[0,2)、C[2,38)、R[38,63)、R[63,64) | C 混入普通 PBF;验证不支持的连续图层是否安全拒绝整帧 Fast |
| `RCRR_R_DYNAMIC_NO_TIDE` | R[0,33)、C[33,38)、R[38,63)、R[63,64) | 故意将动态天气放入 R;检验版本/连续输入不允许错误静态复用 |
源级校准证据:
`artifacts/maplibre-particle/android-validation/step119_rcrr_style_structure.json`。该结果是**实际设备导出数据上的静态切分核对,不是新 APK GPU 验证**。Mac 通过 Python 直接 GET 正式 Style 曾收到 HTTP 403,因此优先复用了相同 Style 指纹的设备导出 Manifest;没有修改服务器资源。
## 4. 静态测试与当前缺口
新增 `tools/particle_android_validation/test_step119_rcrr_scenarios.py`,四项 Python unittest 全部 PASS:三种索引互相排斥、缺 tide/缺 Annotation 清理不得通过、范围尾部和段数不对必须拒绝、原 STATIC_RC_NO_TIDE 不回退为错误结构。验证器 `scene_parity_valid` 对 64 层 RCRR 三 R 段改用精确区间校验;原 63 层判断、90 秒观察、24 Gate 和 Fast 判据不放宽。`git diff --check` PASS。
**没有对新 Kotlin 场景做 Android 编译/安装**(遵守用户手工 Android 编译及本轮不重复已有完整编译限制)。新增三场景的 Kotlin 可编译性与 Native 真机结果必须记 `NOT VERIFIED`。新增 13 项 STEP-119 GTest 仍未运行;Mac Metal CMake 被既有 libuv 缺失阻塞,本轮不安装或升级工具链;iOS Metal/实体机亦未验证。
## 5. 下次同一次人工 APK 构建后的串行设备验收
源码 scoped commit/push 完成后,由用户按已有标准在 Mac 手工构建**新 HEAD** 的 x86_64 Android OpenGL Test APK,不得使用旧 `3d989a` APK冒充新场景;验证 build-info commit、APK SHA、设备 base.apk SHA 一致后,逐一运行:
```bash
cd /Users/tei/dev/MapLibreParticleLab
./tools/particle_android_validation/build_validation_apk.sh
adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --preflight-only
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario RCRR_C_NARROW_NO_TIDE --seconds 90
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario RCRR_C_WIDE_NO_TIDE --seconds 90
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario RCRR_R_DYNAMIC_NO_TIDE --seconds 90
```
每步独立读取退出码与 result.json:完整 R/C、Source/Tile、R0/R2/R3 Build/Reuse/Invalidate、Fast/Full 差分、24 Gate、120 帧、生命周期/判定一致性。正向窄 C 必须先 Full 建立证书,再 Build 和成功 Reuse 后 Fast;若失败记录真实 gate,而不是降阈值。宽 C 和动态 R 是安全负例,`NO_FAST_OBSERVED/exit2` 若符合完整原因和分段预期属于负例通过,而非直接整体失败。禁止直接用这些负例宣称 Fast 不可用。
之后须补:静止视觉 Full/RCRR 同资源同视角人工航标、文字、Symbol、层序对照;Camera/Source/Style/Tile/Annotation/Context/cache toggle 的瞬时失效、Full 回退、重新稳定后的 Build/Reuse;13 GTest 真实执行和 Mac Metal/iOS 独立验证。视觉比对时动态粒子会造成像素差异,不能简单拿整屏逐像素一致作为静态 Symbol 结论。
## 6. 当前判定
`ANDROID_RCRR_FAIL_CLOSED_OBSERVED / NEW_VARIANTS_SOURCE_READY / STEP119_PARTIAL`。
主要已确认局限:多 R 缓存复用并不代表全帧 Fast;含缺源 R 会阻断、过宽 C 中非支持类型的连续层触发拒绝;未实测新窄 C 前,不能推断 RCRR 性能收益。待验证风险:多 RenderTarget/Composite 成本、全局 Symbol Placement/Atlas 证书联动、局部更新导致跨 R 全局失效、上层标记/文字的层序与图像等价、iOS/Metal 一致性。

View File

@@ -0,0 +1,93 @@
# STEP-119 RCRR 粒子独占 C 与 CPU 效率测试矩阵 v2
日期:2026-10-11 JST。状态:**TEST SOURCE PREPARED / ANDROID NOT RUN / STEP-119 PARTIAL**。本 v2 取代 v1 对“窄 C 包含 WeatherColor+Particle”的测试建议;v1 保留为历史证据,不改写当时实机事实。
## 一、目标与核心原则
原则:**一个 Continuous 段只放 RasterParticleLayer**。WeatherColorLayer 不是逐帧运动图层,正常预测/颜色不变化期间可作为 R 候选,但数据提交、forecast time、色阶、Opacity、Style、Camera、Source/Tile/Atlas 等发生实际结果变化时必须安全撤销旧缓存并 Full,再重 Build/Reuse。这些具体动态失效条件仍需真机证明,不能因为分段配置正确而提前视为 PASS。
Native 当前代码以 `singleContinuousSegment(plan)` 选择唯一 C;快路只在这个 C 范围执行 tweaker/upload/postRender,全部 R 需具备有效缓存、Revision 与 Symbol 结果证书;现有 24 Gate 不能放宽。因而“一个 C + 多个 R”符合技术实现边界,但不是任意 R 划分都必然适合 Fast。
## 二、可比 Style 与真实图层顺序
正式原 PBF Style 63 层、测试副本只排除无源 `tide-spots`,其余 62 层定义和次序完全保留,WeatherColor / Particle **按原插入点顺序**位于 `clip-outline` 之后,Native 预计总 64 层。基于设备导出与原正向成功相同指纹的 Style Manifest:
- R 下部 [0,32):含基础海图 [0,31),并在索引31绘制 `rn-weather-color`;WeatherColor 留在 R 中。
- Particle C 唯一图层 [32,33):`rn-weather-particles`。
- 上部 R [33,64):各类静态海图、Symbol 和文字。按测试目的拆成 1/2/3 个 R 片段,不改实际图层顺序。
**不要在 NavSea 产品中按本测试索引硬编码**;生产实际 Style 上应按图层 ID 验证真实边界。
## 三、新一轮候选与负对照
| 场景 | 预期分段 | 目的 |
|---|---|---|
| `RCRR_FULL_CONTROL_NO_TIDE` | R[0,32) C[32,33) R[33,63) R[63,64) | 与主 RCRR 相同 Style/Plan/Field,但关闭 Retained 缓存,取得 Full CPU 基线 |
| `RCRR_C_NARROW_NO_TIDE` | R[0,32) C[32,33) R[33,63) R[63,64) | 主 RCRR 正向,仅粒子进入 C;3 个 R |
| `RCR_PARTICLE_ONLY_NO_TIDE` | R[0,32) C[32,33) R[33,64) | 仅 2 个 R,测缓存片段数量收益/开销 |
| `RCRRR_PARTICLE_ONLY_NO_TIDE` | R[0,32) C[32,33) R[33,38) R[38,63) R[63,64) | 4 个 R,测额外 RenderTarget/Composite/缓存生命周期成本 |
| `RCRR_C_WIDE_NO_TIDE` | R[0,2) C[2,38) R[38,63) R[63,64) | 历史宽 C 错误配置,继续作为负对照,不作为性能候选 |
| `RCRR_R_DYNAMIC_NO_TIDE` | R[0,33) C[33,38) R[38,63) R[63,64) | Particle 在 R 内的错误配置,必须 Fail-Closed |
| 原 `PARTIAL_RCRR` | R[0,3) C[3,39) R[39,64) R[64,66) | **已经实测**:R0 缺 tide、R2/R3 各 Build1/Reuse536、FastDelta0/FullDelta596、decisionMismatch0 |
旧正向 `STATIC_RC_NO_TIDE` 用于证明原有 FastPath(FastDelta1819),但天气层位置与粒子独占 C 的四个 A/B 场景不相同,**不能直接拿其 CPU 数据得出 RCRR 快慢结论**。
## 四、CPU 指标及可比性
使用 Android `/proc/<PID>/stat` 同一进程的 `utime+stime`(含 App 主线程与原生工作线程),以设备实际 `getconf CLK_TCK` 转换为 CPU 秒。记录该进程在观察期内消耗的累计 CPU 核秒。设备实测此接口可访问且 `CLK_TCK=100`,此预检不是新 APK 实测。
每场景冷启动不同 PID,固定设备、x86_64 OpenGL APK、Style URL/PBF fingerprint、中心/zoom/rotation、1920×1200 设备条件、Weather 数据、forecast pause、1024 粒子和目标 FPS30。先 Warmup35 秒(等待载入/Placement/Cache settle),再观察至少 90 秒;每 6 秒读取一次 CPU 累计值,场景 Native summary 同样按 6 秒采样,最多允许 CPU/Native 边界时间差 9 秒,否则记测量不完整。
同时必须记录:
- `CPU 核秒/秒 = Δ(utime+stime)/CLK_TCK/观察秒数`;乘100为**相对单核的进程 CPU%**,多线程时可超过100%。
- `Native FPS = (ΔFast+ΔFull)/Native摘要实际区间秒数`。
- `估算 CPU ms/完成帧 = (CPU核秒/秒) × 1000 / Native FPS`;这是进程级近似值,不是 C++ 单次 render 函数的精确 CPU 耗时。
- FastFraction、Full/Fast 增量、R Build/Reuse/Invalidate、24 Gate mismatch、源指纹、生命周期、帧率、帧率波动及进程 PID。
- Full→Fast→Full(A-B-A)控制热机/网络缓存时序偏差;Full 前后的 CPU 核秒/秒漂移超过15%则需要复测,不凭一次高速帧数下性能结论。
- 如果 Fast FPS 比 Full 高而 CPU/秒也提高,必须同时呈报“每帧成本下降”和“整机持续负载未必下降”。`CPUms/frame`改善不等于省电或 GPU time 已改善。
## 五、顺序执行(用户手工编译一次,其余通过 RDC)
必须先将本轮 Test App 修正提交/Push,**由用户手工使用当前 HEAD** 进行 Android APK 构建/安装;禁止用旧 `3d989a...` APK 冒充新场景。完成后 RDC 检查 build-info commit、SHA-256、设备 base.apk SHA 和目标 ABI。
```bash
cd /Users/tei/dev/MapLibreParticleLab
./tools/particle_android_validation/build_validation_apk.sh
adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --preflight-only
# 每条命令执行完读取状态/result.json,再进入下一条;A-B-A:
python3 -B tools/particle_android_validation/benchmark_step119_cpu.py --scenario RCRR_FULL_CONTROL_NO_TIDE --seconds 90 --warmup 35
python3 -B tools/particle_android_validation/benchmark_step119_cpu.py --scenario RCRR_C_NARROW_NO_TIDE --seconds 90 --warmup 35
python3 -B tools/particle_android_validation/benchmark_step119_cpu.py --scenario RCRR_FULL_CONTROL_NO_TIDE --seconds 90 --warmup 35
python3 -B tools/particle_android_validation/benchmark_step119_cpu.py --scenario RCR_PARTICLE_ONLY_NO_TIDE --seconds 90 --warmup 35
python3 -B tools/particle_android_validation/benchmark_step119_cpu.py --scenario RCRRR_PARTICLE_ONLY_NO_TIDE --seconds 90 --warmup 35
```
每次自动写入 Git 忽略目录:
`artifacts/maplibre-particle/android-validation/step119-rcrr-cpu/<时间>-<场景>/result.json`;同目录保存日志和启动记录。脚本全程真实读取现有 Native 摘要,不改 Gate、不执行 Gradle。验证器 Fast 场景须稳定 Fast 比例≥95%,Full 对照须 FastDelta=0、FullDelta>0。未满足时标记 `MIXED_OR_NO_FAST` 或 `NO_EXPECTED_FRAMES`,**不把回退帧混入 Fast 成本声称速度提升**。
在取得相应结果路径后使用:
```bash
python3 -B tools/particle_android_validation/compare_step119_cpu.py \
--full-before '<第1次Full/result.json>' \
--fast '<RCRR正向/result.json>' \
--full-after '<第2次Full/result.json>' \
--rcr '<RCR/result.json>' \
--rcrrr '<RCRRR/result.json>'
```
结果自动核验同 SHA/设备/Style 指纹、Native窗口、失配、Full控制与顺序漂移,输出 CPU per frame 节省比例、CPU 核秒/秒和 FPS 对照。若主 RCRR 尚不能稳定 Fast,不执行“有性能提升”的结论,只报告准确的 Gate 和成本。
## 六、其他 STEP-119 待验
相同视角的 Full/Fast 截图与 Symbol/文字/航标图层层序人工对比;Camera pan/zoom/rotate、Source/Style/Tile/Annotation/context/forecast 变更导致的证书失效和 Full → Build → Reuse 恢复;13 项新增 C++ GTest 真实运行和 macOS Metal/iOS 独立验证,均保持 NOT VERIFIED。Mac 现存 libuv CMake 配置缺口不授权升级第三方系统工具。全任务 STEP119 仍是 `PARTIAL`,本批仅准备测试能力,没有 Android 编译或新数据。
## 七、保护
- 不改变 Shared Core 公开 API、24 Gate、Renderer 行为、正式远端 PBF Style、发行 Tag/SOURCE_LOCK。
- 原 `STATIC_RC` 63 层与历史 RCRR/负对照保留。
- 不清理两份 STEP110/111 未跟踪文档。
- 本阶段只把 Test App/CPU 工具/中文报告/Step/开发记录按 Git 任务范围提交到已有 `particle/main`,Push后回读远端 HEAD。

View File

@@ -0,0 +1,89 @@
# STEP-119 原PBF FAST实机联合验收计划 v1
日期:2026-10-11(JST)。执行者:RDC/用户,Codex本轮仅交源码和命令。状态:NOT RUN;没有新的Native、Android、Metal或视觉PASS。
## 1. 先独立审计,再锁定源码
读取AGENTS、STEP119任务、调查、Step、完成报告及实际diff,检查原Style/API/ABI/24Gate/SourceRevisionBook、原STEP117/118证据与两个保护任务书。重点核对:普通Full不清结果证据、证书在upload之后采集、成功present之后完成、Build不能借Reuse自举、Reuse成功后才Fast、所有缺证/变更/错误仍拒绝。
此工作区尚有本轮未提交源码。现有verify_step113_fast_path.py严格要求tracked clean与APK来源HEAD一致,会报SOURCE_NOT_LOCKED;本轮保留该保护,不能添加dirty bypass、借旧APK或只提交报告来通过预检。源码锁定由RDC按其阶段明确授权scoped处理;Codex没有commit/push。任何Git收尾不得包含STEP110/111、无关dirty或旧ignored artifacts;若push已获授权,必须回读远端HEAD。
如果仍无法审计实际GPU输入覆盖、shader provenance、提交/恢复或异步线程边界,保持BLOCKED,不进入APK周期。
## 2. Native定向验证(仅交RDC执行)
在本轮最终锁定源码上,逐条执行;任一步失败立即停止后续命令并保存首个真实error、target、exit、耗时、完整日志路径。旧Mac缺libuv是历史环境证据,本轮未重试/未安装依赖,不能推断当前结果。
```bash
cd /Users/tei/dev/MapLibreParticleLab
cmake --preset macos-metal
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
build-macos-metal/mbgl-test-runner --gtest_filter='Step119SymbolResults.*' --gtest_list_tests
build-macos-metal/mbgl-test-runner --gtest_filter='Step119SymbolResults.*:Step117Safety.*:Step116Safety.*:Step114FastPathSafety.*:Annotations.*:CrossTileSymbolLayerIndex.*:RenderOrchestratorError.*:RenderOrchestratorTeardown.*:RetainedUnderlayCache.*:RetainedUnderlayRevision.*:FastGateTrace.*:ParticleFastPath.*:RenderSegmentPlacementFree.*:RenderSegmentLocality.*:RenderSegmentPlan.*:RenderSegmentDiagnostics.*' --gtest_color=no
```
必须发现并运行13个新增Step119SymbolResults;不得用零匹配/旧runner/仅fsyntax-only称GTest PASS。renderer=Metal、target=mbgl-test-runner、并行3;Host Native测试不能替代iOS真机或Android OpenGL。本轮没有Jest,没有Bazel/codegen授权。
RDC还须按已有Android OpenGL/x86_64真实compile_commands.json独立核查修改的Core/GL及两个测试文件,保持-Werror,不附加-Wno-error;语法结果单独记录。Metal context/provenance/UBO consolidation和原生source组合须编译实际路径,不能靠宏关闭绕过。
## 3. 一次集中APK build/install与严格来源闭环
独立审计及源码锁通过后,由RDC/用户按本阶段授权执行一次标准流水线;任何失败不得安装旧文件,不重复启动并发构建。本轮不是正式发行,不改VERSION/Source Lock/公开坐标。
```bash
cd /Users/tei/dev/MapLibreParticleLab
./tools/particle_android_validation/build_validation_apk.sh
adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --preflight-only
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario STATIC_RC_NO_TIDE --seconds 90
```
固定FZG1-2、x86_64、OpenGL Debug、Java17。ADB每条显式-s,包括人工补采样命令。流水线记录build-info、branch/HEAD/dirty、完整build.log、APK大小/ZIP/ABI/strip及SHA;设备base.apk SHA必须与本次本地APK相同。版本号仍.23不足以判定新源码,旧3051196054/44afcfabe… APK不得冒充本轮。
原STEP118 result.json与screen.png只读保留,不覆盖、移动或加入Git。现有verifier固定生成新的step113-retest-<timestamp>/<scenario>目录(Git忽略);保留此真实输出路径,在RDC交付报告标记为STEP119本轮证据,不改脚本或覆盖旧目录。
## 4. 静止真实PBF正向验收
| 项目 | 必须取得的真实结果 |
|---|---|
| 原始资源 | 相同正式PBF/Sprite/Glyph/Tile URI,不修改服务器或造tide;manifest保留原63基础层 |
| 派生场景 | STATIC_RC_NO_TIDE,唯一排除tide-spots/tide;有效基础62+天气2,Native64,R[0,62)、C[62,64),missingSourceIds=[] |
| 启动/settle | 正常Full完成原生Placement/vertices/opacity/atlas/UBO;fade未完成拒绝;不得预置Cache有效 |
| 首次Build | R真实Build>0,texture/composite创建与Build后的static/build revision匹配,placementGatePassed=true,截图仍包含其余Symbol |
| Reuse | R真实Reuse>0;成功Full Reuse先于Fast完成,不能只在状态机测试手工mark |
| Fast | PID确认之后持续观察>=90秒,FastDelta>0;FullDelta、真实窗口和生命周期分别记录,不把生命周期累计当区间增量 |
| 24Gate | 完整120帧knownMask=0xffffff,decisionMismatch=0;candidate帧24项全部通过、completed fast-rendered;失败帧保留全部失败位 |
| 像素/层序 | 同相机和资源对照强制Full与开启retained后的截图;用户核对航标/文字/图标/重叠/方向/沿线/variable anchors及WeatherColor/Particle层序,无缺失或ghost |
自动验证器已有真实Build/Reuse/Fast检查,不改判据;完整120帧通过现有EXPORT LAST SNAPSHOT取得。保存场景开始、Build后、Reuse后及Fast稳定的快照/日志,pre是上一完成树、post是当前完成树,不能跨帧拼接条件。没有图像证据时即使计数增长仍为PARTIAL。
## 5. 原63层负对照与变更矩阵
在同一个新APK中运行原STATIC_RC至少90秒,原63基础层+天气2保持;missingSourceIds=[tide]、missing-source、R不Build、不Reuse、不Fast。这是缺源负对照,不是需要被此次证书放行的正向目标。PBF63/PARTICLE_OFF/PARTIAL_RCRR/SOURCE_CHANGE原入口与定义保留,不能用STEP114烟测替代。
每项动作保存操作前、动作提交时和稳定后证据;至少保留一次真实失效后的Full与新Build/Reuse。500ms之后的摘要不足以证明瞬态,必要时现有完整环形快照与平台trace补证。
| 负例 | 安全预期与证据 |
|---|---|
| Source数据/同名Source替换/恢复 | Native source/global revision与lastMutation变化;旧证书失配、Full先执行;不能复用同名旧GPU结果 |
| Style布局/paint/显隐/层序、glyphURL/fontFaces | signature或结果/segment revision变化;真实颜色/透明度/位置正确;恢复仍重新取证 |
| pan/zoom/rotate/pitch、手势/动画 | #7/#8或动态保护失效,旧camera结果不composite;停稳重新Build,Symbol没有跳位/碰撞残影 |
| viewport/pixelRatio | #6/#8及新binding,缓存按新尺寸重建,没有拉伸或旧FBO |
| Glyph/Icon/Atlas延迟加载与替换 | requested/error/loaded/image epoch实际更新;opacity/vertex/atlas已上传才取证,旧纹理不可凭指针复用 |
| text/icon fade、variable anchor/方向/沿线 | fade过程中拒绝,settled不靠fade==0;新顶点/UBO或排序改变时旧证书无效 |
| tile边界/world wrap/crossTile碰撞/跨Source | 正常全场Placement不拆段,异步tile epoch和最终多段结果对应;碰撞受影响的另一R也正确重建 |
| 跨段LayerGroup添加/移除/隐藏Symbol | pending先拒绝;processChanges后全局数量/ordinal/深度和注册关系更新,不能使用旧上方R |
| 真实Annotation添加/更新/删除 | #24或Source/update输入失效,先Full;原SDK helper恢复正确、没有遗漏Annotation。执行既有Annotations.Step116图像测试 |
| context丢失/后台恢复、缓存关闭再开、Style clear/reload | GPU资源与prepared结果重置,旧证书不能跨代;退出没有旧Drawables/资源悬挂 |
| shader替换/未知placement-capable类型/不可读或超限输入 | strict拒绝证书,普通Full继续;不能用相同shader名或空数据取得证明 |
| 上传/绘制/提交错误 | error/content revision变化,失败Full不记已完成证明,保存失败现场 |
设备缺少某个可控入口时写NOT VERIFIED并给出具体限制,不新增业务数据、不修改正式Style或公开接口。完整测试源码的predicate/模拟buffer只验证局部不变量,不能替代这张设备矩阵。
## 6. 结果判定、修复闭环与交付证据
实现审计、Native构建、GTest、APK自验、来源/设备SHA、90秒正向、原63负对照、各负例、视觉、Metal/iOS分别记录。全部必需项实际通过之前不写全任务PASS。
FastDelta=0、Build=0、Reuse=0、24Gate mismatch非零或可见Symbol异常:保存新的result.json、完整120帧、screenshots、crash log、source HEAD/APK与设备SHA,报告FAIL/PARTIAL/BLOCKED,不降低阈值。最多5轮当前阶段明确修复;编译/测试首次真error保留,架构或公共协议问题交Integration,不能跳下一阶段或改Contract。
RDC回传必要信息:命令、exit、耗时、首错/target及日志路径、实际用例数量、正负场景Layer/Source/R/C清单、Build/Reuse/Full/Fast增量、24Gate window、frame输入来源、视觉判定与未验项目。本计划命令全部尚未由Codex运行。

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

@@ -0,0 +1,34 @@
# STEP-108 V23 统一正式发行 RDC 完成报告 v1
日期:2026-10-09(日本时间)
结论:GSRL PASS / RELEASED;设备实测独立待验收。
仓库:Mac /Users/tei/dev/MapLibreParticleLab,particle/main,未新建 worktree。
## 源码及正式身份
- V23 构建源码提交:87f3d1c59a52581fb8339f0ea7d106b9f4b94fef。
- Android / RN:13.6.1-particle.23;iOS SwiftPM:6.29.0-particle.23。
- 正式源码 Tag:maplibre-particle/rn/13.6.1-particle.23。
- 正式 Tag 指向:a0a4787b4379ab2819943d20ce05a7ae760a88c3。
- Source Lock:release/maplibre-particle/13.6.1-particle.23/SOURCE_LOCK.json,记录六项文件、哈希、依赖、状态及下载地址。
## 严格串行结果
1. Prepare:V23 冻结版本,源码提交 87f3d1c59a,已 push origin/particle/main。
2. Build:release.sh 23 build,exit 0,Android 四 ABI OpenGL Release、Maven Local、iOS Metal Bazel opt XCFramework、RN tgz 全 PASS。日志 /tmp/maplibre_v23_unified_release_build_20261009.log。
3. Publish:release.sh 23 publish,exit 0;NAS 原子发布,六个文件公网读回与本地逐字节一致;SwiftPM Tag 6.29.0-particle.23 远端可获取。日志 /tmp/maplibre_v23_unified_release_publish_20261009.log。
4. GSRL:正式 Source Lock 六项 SHA-256 与本地构建产物全部匹配;release/README.md 和 V23 使用说明已经入 Git。
5. Finalize:release.sh 23 finalize,exit 0,提交 a0a4787b43;push particle/main 和不可变 V23 Tag,远端 Git 回读 PASS。日志 /tmp/maplibre_v23_unified_release_finalize_20261009.log。
6. 独立复核:六项公网下载均为 HTTP 200、size/SHA-256 与 Source Lock 一致;SwiftPM Package.swift checksum 与 iOS ZIP 一致;GSRL 最终 TASK_PASS_V23_GSRL_FINAL_VERIFY。
## 三个主要文件 SHA-256
- Android AAR:263821b686a569110ad704f817b288522eb2a153f49bf4162ace2226b28220b7
- RN tgz:6d8d2fa2312deee97fe9304c62887cd11490a70bc08e4a90f4ee8cf7ec83ed87
- iOS Metal ZIP:9dbc477458377ab7e207bfd125d8f4106d3d83e7f923499a7bc048e39f74a76a
## 运行时验证边界
STEP-107 XCTest、iPad 唐津ヨット ハーバ直接点击、多 Source 重叠、粒子/天气回归尚未执行。V23 Android 设备测试、RN 宿主整体集成、iOS Metal 真机也均 NOT_TESTED。这些不是 GSRL 发行身份核验的组成部分,严禁写为设备 PASS。
正式 V22 Tag、制品与 V23 已发布 Source Lock 不覆盖、不改写。本报告作为 V23 Tag 之后的审计追加提交。

View File

@@ -0,0 +1,44 @@
# STEP-109 V23 CPU FieldSnapshot 深比较快路 — RDC 阶段报告 v1
日期:2026-10-10(JST)
结论:**PARTIAL / 开发态**。已针对 V23 源码热点实施局部修正;尚无 V23 真机 CPU 改善证据。
## 1. 基线
- 仓库:Mac /Users/tei/dev/MapLibreParticleLab,particle/main。
- 起点:5b8bdfbfbb4263fe12a060e77bc310cf77f27d61,起点工作区干净。
- 正式已发布 V23:13.6.1-particle.23(Android/RN)、6.29.0-particle.23(iOS);发行 Tag、Source Lock、制品均未变更。
- NavSea Mac V23 集成 checkout 为 /Users/tei/dev/NavSea-Build;Windows D:\React\NavSea 当前仍引用 13.4.1-particle.21,且有多项其他工作区修改。本轮未改 NavSea。
## 2. V23 实际源码发现
- render_particle_validation_layer.cpp 每帧调用 sameFieldGpuEndpoint() 校验既有 T0/T1,内部逐项比较 vector 和 valid。
- render_weather_color_layer.cpp 每帧调用 sameFieldGpuContent(),内部比较 scalar/vector/valid。
- 两条判断在 immutable T0/T1 未改变时仍可执行 O(N) 深比较。
- STEP-090 Fast Path 存在;STEP-102/106 RenderSegment Placement 局部修复已有 FZG 定向证据,因此不能直接照搬 V21 cache-invalid 诊断。
- V21 93.8% CPU 与 simpleperf 浮点数组采样仅为历史方向提示,不能归因于此次代码或称为 V23 基线。
## 3. 已实施代码
- src/mln/renderer/layers/render_particle_validation_layer.cpp:资源绑定持有 T0/T1 不可变快照 shared_ptr;当前资源已验证同一快照时 O(1) 判等,新对象仍严格核对内容。
- src/mln/renderer/layers/render_weather_color_layer.cpp:WeatherColor 纹理同样记录 Source 快照身份;相同内容的新对象仍使用原严格比较及资源复用。
- 全部身份绑定只在纹理准备成功或内容等价验证后建立;资源销毁、重新创建与 context loss 均自然清除。
- 未改 RN 对外 API、风场 canonical contract、T0/T1 mix、Runtime Field Window、粒子数量、GPU 纹理算法及 RenderSegment 安全回退。
## 4. 验证与限制
- scoped git diff --check:**PASS**。
- Mac Metal Core:**INTERRUPTED / NOT COMPLETED / NOT PASS**。执行 `bazel build -c opt --//:renderer=metal //:mbgl-core` 触发 649 actions 的通用 Native 重编译;基于本轮仅两文件微调、尚未安排设备验收,主动中止以控制开发成本。原远程 session 8049 和 Bazel PID 8350 均已停止;未取得构建最终通过或编译错误结论。日志:`/tmp/maplibre_step109_cpu_field_identity_bazel_20261010.log`。
- Android 用户手工编译/安装、OpenGL 实机、iOS 实机、性能同屏 A/B、全量定向测试:**NOT RUN**。
- 没有测得实际 CPU 下降百分比;本次不生成正式发行版,不覆盖 V23 Tag 或 Source Lock。
## 5. 后续验收门槛
1. Native 编译通过并定向执行现有 Field GPU / Timeline / Source 测试。
2. Android FZG1-2 以相同天气输入和粒子设置、相同 Layer stack、同一 BuildType 做 V23 原版与修正版 A/B,采样进程、RenderThread、JS,并检查 FieldTextureUpload/SimulationReset。
3. 时间切换、T0/T1 重叠窗口、clear/reload/context loss、切层、地图平移与关闭天气后恢复,在 Android/iOS 分别核对画面正确、无旧场、无多余纹理重建。
4. 用户验收后,再新建发行版本,按 GSRL 发布不可变制品与 SOURCE_LOCK.json。
## 6. Git
本次开发提交与远端回读事实见下一节。提交只意味着代码可追溯,不意味着已编译、通过真机或正式发行。
## 7. Git 开发提交与远端回读(2026-10-10)
- 本次仅提交两份 Renderer C++ 源码、STEP-109、RDC 阶段报告、粒子开发记录共五个相关文件,未执行 `git add -A` 或触碰正式发行文件。
- 首个开发提交:`2aae38f4023f2ef682c9039c93dfa1e96b0e6e1b`;`perf(particle): skip repeated field snapshot deep comparisons`。
- `git push origin HEAD:refs/heads/particle/main`:**PASS**,fast-forward;远端 `git ls-remote` 回读同一 SHA:**PASS**。
- 本次只是开发态代码与记录推送;Mac Metal build 中断、Android/iOS 真实 CPU A/B NOT RUN,STEP-109 保持 **PARTIAL**。正式 V23 GSRL Tag 和制品仍完全不变。

View File

@@ -0,0 +1,46 @@
# STEP-110 R0 Placement 跨段归因只读调查 — Codex 调查 / RDC 收尾报告 v1
**2026-10-10|状态:PARTIAL(调查);Git 交付另行核验。**
## 1. 真实执行边界
- Mac:Mac.lan,仓库 /Users/tei/dev/MapLibreParticleLab,分支 particle/main;起点 2df504cdc0403bdad85d3f31f16fd54a8744abac。
- Codex CLI v0.161.0,实际启动输出确认模型 **gpt-6-astra**、推理 **high**,会话 01a12495-eb1a-78e3-ab8d-25554c6c207b。
- Codex 完成只读调查与关键结论输出;但生成文档阶段两次长期无进展,由 RDC 终止并确认会话退出。因此**不能将 Codex 调查进程记为自然完成或将其声称为直接写出了文档**。
- GPT 指挥 RDC 独立核查代码路径与 Git 状态、编写报告、Step 和追加开发记录,负责后续 scoped Git/Push 验收。
- 原有未跟踪任务书 docs/40_任务/STEP-110_R0_Placement跨段归因只读调查_Codex任务_v1.md 保持原样、不提交。
- 严守调查边界:没有编译、链接、测试、Jest、Gradle、ADB、设备操作;未修改 Native/JS/Kotlin、NavSea、Integration 或正式发行 Tag/Source Lock。
## 2. 任务项逐项验收
| 事项 | 状态 | 证据 / 限制 |
|---|---|---|
| 任务书与项目规定、既有 STEP 阅读 | 已完成 | Codex 会话逐项读取;本机原始日志和必要历史记录已交叉对照 |
| V23 Tag、构建源码、main 对照 | 已完成 | GSRL 源码 87f3d1c,Tag 提交 a0a4787,调查起点 2df504c;六份关键代码 Blob 一致 |
| Q1 Placement Controller/Collision/Transition | 已完成(源码调查) | 主报告第 2 章,文件、函数与行号;非 NavSea 实时归因实验 |
| Q2 per-segment Gate/Cache/Reason | 已完成(源码调查) | 主报告第 3 章;global gate 与 source-local revision 区分 |
| Q3 七种反事实场景安全判断 | 已完成(安全边界判定) | 主报告第 4 章;无法证明者明确 fail-closed |
| Q4 Fast Path 独立前置依赖 | 已完成(源码调查) | 主报告第 5 章;R0 缓存不是 Fast Path 充分条件 |
| STEP-106 T1/T2 历史验证重读 | 已完成 | 已保存设备 stream.log,仅用历史证据,不做新设备测试 |
| NavSea R0 per-Symbol 视觉等价 | **无法验证** | 无生产 Placement/Collision/Opacity 逐帧签名,不得静态猜测 |
| 真机 CPU 降低、R0 缓存命中 | **未完成(本 Step 禁止执行)** | 当前只有任务书提供的症状,不能声称改善 |
| Native Gate/公共 API 修改 | **未完成(本 Step 禁止)** | 本任务只读 |
| 任务文档及简短开发记录 | **已完成(RDC 补充落盘)** | 指定三份本 Step 报告/记录和开发记录追加 |
| scoped commit、普通 push、远端核验 | **以报告的 Git 收尾补记为准** | 首次 Mac 远端通信出现 DNS 解析异常,不允许提前宣称 Push PASS |
## 3. 核心调查结论
现有 Source/Layer 事件可有条件地追踪,但 PlacementController 更新在整图语义下执行,V23 缺少可直接供 R0 复用的逐段视觉结果等价证明。跨 Source Collision 开启时,C1 Symbol 可能影响 R0 地名/图标;关闭时仍必须检查 Source 归属、Fade、Tile、Camera、层序。含 Symbol 的 R0 采用全局 Placement/Fade/Transition Gate 属于**正确保守限制**,目前未找到现有契约或不变量被违背的明确 Bug。
推荐顺序:Native 内部来源与逐段结果诊断 → 可证明安全的 Placement 等价判据 → 单独授权实现和 FZG1-2 对照 → 再审查 Fast Path 其他 Gate。不得先取消保护,也不在本任务更改 C-001/C-002。
## 4. Git 收尾字段
- 调查起点 branch:particle/main。
- 调查起点 HEAD:2df504cdc0403bdad85d3f31f16fd54a8744abac。
- 待提交路径:docs/20_调查/R0_Placement跨段归因与缓存安全边界调查_v1.md、docs/50_Step记录/STEP-110_R0_Placement跨段归因只读调查_v1.md、docs/报告/STEP-110_R0_Placement跨段归因只读调查_Codex完成报告_v1.md、粒子开发记录.md。
- **第一份文档提交**:`cdb45c023b86898117a6bd2b83b24719dd68f5fd`,message:`docs: investigate STEP-110 R0 placement attribution`;4 个且仅 4 个授权路径,`git diff --cached --check` PASS。
- **第一次 Push 结果**:`git push origin particle/main`,exit code 0;远端响应 `2df504cdc0..cdb45c023b particle/main -> particle/main`。
- **第一次远端回读**:`git ls-remote origin refs/heads/particle/main` 返回 `cdb45c023b86898117a6bd2b83b24719dd68f5fd`;此时本地 HEAD、origin/particle/main、远端 HEAD 三者一致。
- **远端分支**:`origin/particle/main`,普通 Push,未 force;仍只有原先未跟踪任务书保留在工作区。
- 本条 Git 证据写入报告属于**随后独立的文档补记提交**,该补记提交会使 Final HEAD 再前进一版。最终收尾提交 SHA 和最终远端回读由 GPT/RDC 的逐步骤验收记录提供,不能把上面的第一份提交 SHA 冒充补记后的最终 SHA。

View File

@@ -0,0 +1,44 @@
# STEP-111 R 段 Placement 结果等价调查 — Codex 执行状态 / RDC 阶段报告 v1
**2026-10-10|PARTIAL,Codex 未完成任务,不是 Native 优化通过报告。**
## 1. 执行来源
- 设备:Mac.lan;仓库 `/Users/tei/dev/MapLibreParticleLab`;`particle/main`;起点 HEAD `7ec9ac8c1635c7a3b607abc2ed4be4fd764e7b6d`。
- Integration `/Users/tei/dev/navsea-particle-integration` 同步核对:本地 `main` 与 `origin/main` 相同,HEAD `80667d6c343411d03a760425438e3d292f452118`。
- Codex CLI `0.161.0`,用户可见 GNU Screen `20825.7445.codex` 新窗口 STEP111;启动参数 `-m gpt-6-astra -c model_reasoning_effort=high -s workspace-write -a never`;屏幕实际显示 `GPT-6 Astra high`。
- 原始会话:`~/.codex/sessions/2026/10/10/rollout-2026-10-10T16-51-37-01a124cb-c9ae-75e3-afd8-c15a75815d1b.jsonl`(本机取证路径);脚本记录 `/tmp/particle_step111_codex_tui.log`。
- 17:00 前原始会话源码探索多次执行;最后连续完整事件停留于约 16:54:51;因长时间无新增结果,RDC 发送 ESC 中断并在同会话要求直接落盘报告。再次长时间没有输出,随后中断并退出 TUI。启动脚本没有可靠写出退出码;进程已消失,**不可声称自然成功退出**。
## 2. 任务验收矩阵
| 任务 | 判定 |
|---|---|
| STEP-111 只读任务书创建 | 已完成,原样保留未跟踪 |
| Integration main/规则核验 | 已完成(RDC) |
| GPT-6 Astra High 实际启动 | 已完成(TUI 确认) |
| Codex 源码搜索与浏览 | 有部分实际轨迹;非完整调查交付 |
| Codex 字段级 per-R Result Signature 论证 | **未完成** |
| Codex RCRCRC 完整场景、A-D 难度证明 | **未完成** |
| Codex 生成主报告、Step、完成报告、记录 | **未完成** |
| GPT/RDC 独立核对当前 Gate/Cache 调用点 | 已完成 |
| GPT/RDC 编写初步难度与后续候选方案 | 已完成,标为 PARTIAL |
| Native 功能改动或设备性能改善 | **未执行/未验证** |
| 构建、测试、Jest、Gradle、ADB、Bazel、CMake | **未执行** |
| Source Lock、发行 Tag、正式交付 | **未执行** |
## 3. 实际修改范围
- **没有改动 Native/JS/Swift/Kotlin/Tests**。
- GPT/RDC 依据 STEP-110 与本次只读核对,补充以下 PARTIAL 文档:
1. `docs/20_调查/STEP-111_R段Placement结果等价与独立刷新可行性调查_v1.md`
2. `docs/50_Step记录/STEP-111_R段Placement结果等价调查_v1.md`
3. `docs/报告/STEP-111_R段Placement结果等价调查_Codex完成报告_v1.md`
4. `粒子开发记录.md` 追加 STEP-111。
- 不得把这三份文档称为 Codex 独立完成;实际作者为 GPT/RDC。
- 已存在未跟踪 `STEP-110` 任务书及本次新建未跟踪 `STEP-111` 任务书不提交、不清理。
## 4. 已核验的源码与部分结论
`include/mln/renderer/render_segment.hpp:85-90` 含 Symbol R 使用全局 placementChanged/symbolFadeChange/placementTransitionActive Gate。源代码中 `src/mln/renderer/render_orchestrator.cpp:792-809` 是全局 Placement 更新;`src/mln/renderer/renderer_impl.cpp:638-686` 已有逐 Retained Segment Cache 的 fingerprint/source state/evaluate。由此可以合理提出“保留全局 Placement、后续逐 R 判断已完成结果是否等价”的设计调查方向,**但没有完成可以用于修改 Gate 的安全判据证明**。
## 5. Git 与报告状态
- 代码和正式发行状态未变化;本次只是调查阶段 PARTIAL。
- Codex 不承担本轮 Git 收尾;RDC 后续 Git 实际 Commit/Push/远端 HEAD 以独立工具返回为准。当前撰写时**尚未验证提交与远端**,不得把文档写入视为 Push PASS。
- 后续可以在可用模型/运行环境中重新开展限界的结果数据所有权审计和 shadow-mode 方案设计,但**不能将本轮 PARTIAL 当成允许直接修改 Gate 的授权**。

View File

@@ -0,0 +1,93 @@
# STEP-112 V23 粒子 Full Render 与 Symbol 缓存调查 Codex 完成报告 v1
日期:2026-10-11(JST)。
## 1. 执行结论
**A—E只读源码调查和四文件交付已完成;综合判定 PARTIAL。** 正常Continuous Full的Symbol R首次缓存准入不可达已有决定性源码证明;生产逐帧原因、原RCRR完整布局、可安全替代的准入证据和真实性能仍不完整,不标全任务/实现/性能PASS。
关键证据:`render_orchestrator.cpp:792-807` 的重算/不重算分支,`placement.cpp:160-175` 的recent/stale语义,与 `render_segment.hpp:85-90` 的三条件组合。重算则changed=true,不重算则Controller stale使transition=true,Symbol R均不能首次Build。`symbolFadeChange==0`不是终态条件;transitions关闭时正常Full返回fade=1。全局prepared scene和PREFIX普通Symbol/Line C仍独立阻塞Fast。
主报告第4—10章包含完整调用图、24项矩阵、缓存状态机、两种配置与真值表、S1—S5、A—D成本风险提案、最小采样方案;第11章区分源码证明、现场输入、条件推断和设备待验。
## 2. 实际模型、机器与Git基线
- 机器:Mac.lan,Darwin 24.6.0,x86_64;未连接或操作Android/iOS设备。
- 仓库:`/Users/tei/dev/MapLibreParticleLab`;branch起终均 `particle/main`。
- start HEAD:`41b416c261fa72bd21171328045036a124694f47`;end HEAD同此,未commit。
- **模型 `gpt-6-astra`,reasoning effort `high`**。环境会话ID `01a12686-49ee-70b1-953e-f95f8a4bd9ca`;实际读取本地 `~/.codex/sessions/2026/10/11/rollout-2026-10-11T00-54-57-01a12686-49ee-70b1-953e-f95f8a4bd9ca.jsonl` 的 `turn_context.model/effort` 确认;没有自行切换或升级,没有启动第二实例/子代理。
- 起点tracked clean;只有两份未跟踪STEP-110/111任务书。`git submodule status` 全为空格前缀,无未初始化/偏移标记;未操作子模块。
- 终态预期且经收尾检查核对:新增本Step三文档、开发记录修改,加上原有两份未跟踪任务书,无其它变化。
- V23构建源码 `87f3d1c59a52581fb8339f0ea7d106b9f4b94fef`;相关路径与HEAD只差STEP-109的两Layer snapshot快比较,核心Gate行为一致。V23正式制品不含这项开发快路,本轮未发行。
- Integration main `80667d6c` 是用户提供的RDC核验事实;本轮未fetch/pull/远端回读,只读相关contract/coordination。
## 3. 任务逐项:已完成 / 未完成 / 无法验证
| 项目 | 结果 | 证据与限制 |
|---|---|---|
| 前置阅读、实际模型、工作区核对 | 已完成 | 根AGENTS、PARTICLE_BASELINE、8份规范、现行总则README+4份、STEP-112、STEP-102/106/109/110/111任务/记录/报告 |
| A 调度/Idle/cadence/CPU-GPU区别 | 已完成 | 主报告4;SDK Handler/NSTimer唤醒、Core cadence、Observer和prepared-tree链;实测30FPS来源仍未知 |
| B 全部Fast Gate | 已完成 | 主报告5;24项按首错顺序,完整non-camera signature及prepared scene二重锁 |
| C R2缓存状态机 | 已完成 | 主报告6;首次未建、反复无条件invalidate、revision/key变化、资源/Build失败分开 |
| D Placement开关/Fade真义 | 已完成 | 主报告3、7;无窗口的分支证明,300/30ms/zoom和8组合表,GL/Metal shader依据 |
| E S1—S5及布局 | 已完成(缺证据明确标未知) | 主报告8;原RC/PREFIX和理想保序布局;原RCRR完整边界未获得,不伪造 |
| A—D工程提案与最小验证 | 已完成 | 主报告9—10;首次Build/scene/C分别评估,候选文件/成本/风险/1—5复杂度、双后端、Interface Blocker |
| 任务书四项落盘 | 已完成 | Codex本轮直接写入,早期框架与后续章节分阶段保存;不是由RDC补写 |
| FZG1每帧options、请求来源、所有Gate、CPU分摊 | 无法验证 | 只有任务/历史现场计数与末态;本轮禁止插桩和设备操作 |
| Symbol结果等价/安全新Gate充分性 | 无法验证 | 当前无与成功Build版本绑定的最终GPU输入证书;提案需shadow和运行对照 |
| Native修复、首建成功、Fast hit提升、CPU改善 | 未完成(本轮不授权) | 未改实现,不能将只读控制流证明当修复PASS |
| 编译/测试/设备/制品 | 未执行(本轮禁止) | 无新build log、APK/AAR/XCFramework、运行结果或SHA |
| commit/push/远端HEAD | 未执行(用户交RDC) | 无新commit/hash/message/push结果,不冒称Git收尾完成 |
## 4. 实际写入文件
新增:
1. `docs/20_调查/STEP-112_V23粒子FullRender循环与Symbol缓存准入全面源码调查_v1.md`
2. `docs/50_Step记录/STEP-112_V23粒子FullRender与Symbol缓存调查_v1.md`
3. 本报告 `docs/报告/STEP-112_V23粒子FullRender与Symbol缓存调查_Codex完成报告_v1.md`
修改:`粒子开发记录.md`,仅在末尾追加STEP-112短条,不重写历史。
无Native/C++/Kotlin/Java/Swift/ObjC/JS/TS/Shader/测试/Style/脚本改动;无NavSea/Integration跨项目写入;无分支/worktree变更或dirty清理。
## 5. 检查与禁止操作
已执行:`pwd`、`uname -a`、branch/HEAD/status/submodule只读核对;`rg`、带行号源码读取;V23相关路径`git diff`;文档内容和变更范围检查。收尾的文档空白、原文件哈希、开发记录前缀检查结果见第8节。
**全部NOT RUN**:CMake/Ninja/Bazel、编译/链接、codegen、C++测试、Jest、Gradle、APK/AAR/XCFramework打包、ADB/安装/设备/logcat/截图、新性能测量、发布、commit、push。没有已观察到的编译或测试失败;因为没有执行,不能推断会通过。
少数初始按类名探测的路径不存在,随后按实际文件定位,例如RenderRasterParticleLayer实际实现位于 `render_particle_validation_layer.cpp`,RenderTreeImpl位于 `render_orchestrator.cpp`;未用缺失路径作为证据,也没有为此创建源码文件。
## 6. 与任务书差异和阻塞
- 用户当前明确指定gpt-6-astra/high,覆盖任务书旧建议,已实际核实。
- 本轮Codex执行指令明确不由Codex提交/推送,遵循AGENTS的Codex→RDC职责分工;STEP-112任务书第十一节要求RDC独立完成scoped Git提交、推送及远端验收。用户并未禁止本任务的Git收尾。
- 用户已给Integration同步事实,本轮只读相关contract/coordination,不重新做全仓同步。
- 完整原RCRR plan、现场options/逐帧日志和CPU阶段分摊缺失;限制在主报告单独标记,不将条件推断升格为FZG1实测。
- 公共“外部明确Full且不按相同数据去重/延迟”目前没有相应force-Full/序号字段。Interface Blocker仅约束未来D提案,需Integration决议,未新增API或改责任边界。
## 7. 独立验收与下一步
RDC依据STEP-112任务书第十一节,独立复核主报告第3章的两分支证明和第5章24项来源,核对四份交付文件、两份未跟踪任务书及HEAD,然后做scoped Git提交/推送及远端HEAD核验;以独立Git收尾补记为最终证据。
下一代码阶段先授权A低开销同帧gate/Placement/R状态诊断,再审查B的Full后安全Build证书;需要per-R结果时用C shadow比较。保护跨Source/跨段Collision、text/icon Fade、camera、资源、层序与GL/Metal一致性。独立验收必须取得源码/制品版本、plan/Style表、逐帧gate及计数、trace成本、CPU阶段耗时和视觉证据。未经新任务授权,不实施Native修复,不降低FPS、不删除Symbol、不放宽fail-closed。
## 8. 实际收尾检查结果
- `git diff --check -- 粒子开发记录.md`:无空白错误;diff统计仅 **8行追加、0行删除**。
- 三份新增文档分别执行 `git diff --no-index --check /dev/null <明确路径>`:均无诊断输出;退出码1表示与空文件存在差异,不作为构建或测试结果。正文代码围栏成对,Fast矩阵已人工核对包含顺序1—24,S1—S5齐全。
- 开发记录与 `git show HEAD:粒子开发记录.md` 的原始 **159061字节前缀完全相同**,原SHA-256=`9a1690188a9c283024c74fa275ec68d7442459c73008126087e0d9ec9704910a`;本轮追加1381字节。
- STEP-110未跟踪任务书SHA-256仍为 `33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0`。
- STEP-111未跟踪任务书SHA-256仍为 `f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4`。
- 最终 `git status --short` 只显示:开发记录M、本Step三文档??、原有两份任务书??。`git diff --cached --name-only` 无输出,未暂存任何文件。
- `git branch --show-current`=`particle/main`;`git rev-parse HEAD`=`41b416c261fa72bd21171328045036a124694f47`,与起点一致。
- Codex调查阶段没有执行commit/push,commit message不适用且当时未回读远端。这不是用户禁止Git收尾;其后RDC按STEP-112任务书要求独立执行的实际结果见第9节。
这些仅是源码/文档取证与范围完整性结果,不是编译、设备、性能或安全新Gate的PASS证据。
## 9. RDC 独立 Git 验收补记(非Codex操作,2026-10-11)
- RDC独立核对Mac `particle/main` 起点HEAD=`41b416c261fa72bd21171328045036a124694f47`;起始远端SHA相同,Integration仍在main=`80667d6c343411d03a760425438e3d292f452118`。仅stage本任务三份报告及开发记录追加;`git diff --cached --check`通过,四文件且只有四文件。两份原未跟踪STEP-110/111任务书仍保留。
- 文档成果首次commit=`abc137ca9637abc18be8c64cd98f2c7f1ed68704`,message=`docs: investigate STEP-112 particle full render and symbol cache`,四文件共570行新增。普通`git push origin particle/main`退出码0,远端返回`41b416c261..abc137ca96`;`git ls-remote origin refs/heads/particle/main`回读`abc137ca9637abc18be8c64cd98f2c7f1ed68704`,与当时本地HEAD完全一致,首次Git收尾PASS。
- 本节由RDC在Codex任务完成后追加,不应归为Codex调查成果;补记形成后续文档提交,最终HEAD及远端核验另以该后续提交的实际命令返回为准。Native实现、编译、设备及性能依然未执行,综合研究判定保持PARTIAL。

View File

@@ -0,0 +1,265 @@
# STEP-113 FZG1 PBF Layer 与 Fast Gate 诊断 Codex 完成报告 v1
日期:2026-10-11(JST)。结论:**PARTIAL:诊断和专项测试场景源码已交付;构建、设备及生产113层完整等价未验收。**
## 1. 基线、模型与授权
- Mac.lan / Darwin 24.6.0 / x86_64;仓库 `/Users/tei/dev/MapLibreParticleLab`。
- 起点及交付 HEAD:`particle/main@c6d13e1059d464d312ac33acac87967300fa5dc4`;无 commit/push、无新 branch/worktree、无发行。
- 实际模型 **GPT-6.1-Sol(`gpt-6.1-sol`)/ high**,从本会话 `01a12808-4436-7d33-bc56-9135722348b1` rollout 的 `turn_context.model/effort` 核实。未切换模型、未启动其他 Codex/子代理。
- Native/Test App 的源码版本 `platform/android/VERSION=13.6.1-particle.23`,RN package 同为 `.23`;没有改版本或覆盖正式制品。新诊断只存在于本次未提交源码,不能把现有正式 V23 APK 当本次制品。
- NavSea 只读 HEAD `ad9aa043fde37f87462f31de3fbd8ce008f8424b`,读取时 tracked/untracked status 均无输出;Integration 本地 HEAD `80667d6c343411d03a760425438e3d292f452118`。没有 fetch/pull、跨项目写入或消息发送;Integration 远端核验采用用户提供的 RDC 事实。
- 起点 tracked clean;已有 STEP-110/111/113 三份未跟踪任务书均保留。子模块状态均为空格前缀,没有操作子模块。
- 已读根 AGENTS、PARTICLE_BASELINE、项目规范全部文件、总则 README 和四份有效总则、STEP-113 任务书、STEP-112 调查/记录/报告,以及 STEP-102/106 的相关实现与记录。
- 本轮不执行 CMake/Ninja/Bazel、Native 测试、Gradle/APK、ADB/安装/Logcat、Jest、发布、提交或 push。以下命令仅交 RDC 执行。
## 2. 实际修改与新增文件
| 文件 | 变更目的 |
|---|---|
| `src/mln/renderer/particle_fast_path.hpp` | 24项固定顺序表、无副作用失败掩码;生产 evaluator 原样保留 |
| `include/mln/renderer/render_segment.hpp` | 平台无关帧/phase/cache诊断值结构,现有 snapshot 可选 `fastGateTrace` |
| `src/mln/renderer/retained_underlay_runtime_state.hpp` | 120帧环形缓冲、生命周期聚合、受锁保护的快照/reset/完成记录 |
| `src/mln/renderer/render_segment_json.hpp` | 现有 JSON 出口增加可选 trace,24项状态、窗口与生命周期计数、pre/post |
| `src/mln/renderer/renderer_impl.cpp` | 原始 input/decision 采集点、实际 pre cache validity/revision、渲染后完成记录 |
| `src/mln/renderer/renderer.cpp` | 生命周期 reset 与没有 RenderTree 时的明确 outcome |
| `src/mln/map/map.cpp` | 外部诊断读取携带 trace;renderer 内部读不复制历史环形数据 |
| `test/renderer/retained_underlay_cache.test.cpp` | 新增4个 `FastGateTrace.*` C++ 回归用例;未运行 |
| `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step113PbfGateScene.kt`(新增) | 真 PBF、五个场景、parity证明/限制、固定预报、低频UI与分块导出 |
| 同目录 `ParticleCoreLayerValidationActivity.kt` | 菜单/Intent、scene生命周期与旧场景隔离,诊断区域临时扩展 |
| `docs/50_Step记录/STEP-113_FZG1_PBF_Layer与FastGate诊断_v1.md`(新增) | 当前状态、RDC验收填写栏 |
| 本完成报告(新增) | 源码交付事实、命令、证据与限制 |
| `粒子开发记录.md` | 仅末尾追加本次短记录 |
没有修改 Shader、Placement、Retained Cache 的准入/等价/回退算法、RN 类型或方法、JNI 方法签名、NavSea、Integration、构建流水线和发行版本。扩展的是任务明确授权的可选诊断 JSON 与其内部值结构,不新增公共控制 API/Host 契约。若后续需要新的公共控制面,应先走 Integration RFC。本任务不为未来接口变更提供授权。
## 3. B:24 Gate 同帧记录与真实性边界
每次真实 `tryRenderParticleFastFrame` 用**已经计算好的同一个 input**调用原 evaluator 一次,再独立计算掩码。没有再次调用 `particleFastPathDynamicState`、needsRepaint、Placement 计算或 cadence 推进。新增 Cache getter 是 `const noexcept` 的纯字段读取;用于观察已被 aggregate 短路掩盖的 R 状态。
`knownMask=0x00ffffff`:当前24条 evaluator 布尔输入全部已知;`gateStatus` 逐项给出 PASS/FAIL。若以后缺少输入,可通过 knownMask 显式输出 UNKNOWN。**这里观测的是实际 Gate 输入;如没有 prepared scene,其已有 false 标志导致 Gate FAIL,不表示调查过一个尚不存在的 scene。**
`firstFailure` 保存生产 evaluator 真正返回的首错;`failMask` 保存同一 input 的失败全集。`decisionConsistent` 比较首位失败与生产决定,`decisionMismatch` 累计不一致;任何非零值均需要保留现场,不能接受归因结果,也不会放宽生产 Gate。位0对应顺序1;enum 的 camera/viewport 数值顺序不能用作掩码顺序。
| 顺序 | 失败名 | FAIL 谓词 |
|---:|---|---|
| 1 | no-prepared-tree | `!hasPreparedTree` |
| 2 | retained-disabled | `!retainedEnabled` |
| 3 | cache-invalid | `!cacheValid` |
| 4 | revision-mismatch | `!revisionMatches` |
| 5 | content-mutation | `!contentRevisionMatches` |
| 6 | viewport-changed | `!viewportMatches` |
| 7 | camera-changed | `!cameraMatches` |
| 8 | update-parameters-changed | `!updateParametersMatch` |
| 9 | style-not-loaded | `!styleLoaded` |
| 10 | boundary-changed | `!boundaryMatches` |
| 11 | topology-changed | `!topologyMatches` |
| 12 | static-unstable | `!staticStable` |
| 13 | placement-or-fade | `hasPlacementTransition` |
| 14 | pending-changes | `hasPendingChanges` |
| 15 | non-particle-transition | `hasNonParticleTransition` |
| 16 | non-particle-repaint | `hasNonParticleRepaint` |
| 17 | fading-tiles | `hasFadingTiles` |
| 18 | no-particle-repaint | `!particleNeedsRepaint` |
| 19 | still-image-request | `stillImageRequest` |
| 20 | unsupported-mode | `!continuousMode` |
| 21 | debug-options | `!debugDisabled` |
| 22 | unsupported-continuous-layer | `unsupportedContinuousLayer` |
| 23 | unsupported-scene | `!supportedScene` |
| 24 | annotation-mutation | `annotationMutation` |
因此 CacheInvalid 同帧也会保留 #22、#23 的真假,不再通过后续Full快照推测此前Gate。
每帧有 lifecycle 内唯一 `sequence`、renderer `frame`、steady-clock 的 `updateTimeNs`;没有树时多个尝试可以共用一个尚未完成的 renderer frame,**关联主键是 lifecycle+sequence**。不把 updateTimeNs 当 UTC/forecast。外层原有fastPathHit/Miss/Full atomics沿用原快照语义,可能与正在写入的trace末帧相差一个调用阶段;逐帧归因以frame记录为准,不把外层字段拼成额外原子帧。
- `cacheInputs` 是实际 pre-fast cache exists/valid/buildRevision/staticRevision;最多16个 R,超限明确 `cacheInputsTruncated=true`,所有 aggregate Gate 仍完整。
- `pre` 保存判定阶段的缓存计数/失效/回退原因与上一已完成 RenderTree 诊断快照。`preFastPlacementTransition` 才是本次 Gate #13 的当前 Controller 值;`placementTransitionsEnabled` 来自本次 UpdateParameters。
- `post` 在渲染末尾取得同帧 R build/reuse/invalidation/fallback、sourceReady/stable、decision、actualExecution、Placement changed/transition/fade与Full计数。Full、Fast、无树分别有明确 outcome;中途异常或尚未结束时 `completed=false/post=null`。
- `placementBasis` 区分历史 RenderTree、当前 Full tree、当前 Fast tree(未重新计算Placement);没有完成过的诊断快照标 UNKNOWN。没有将 post Full 数据回填 pre Gate。
- 每phase最多16个R;`retainedSamplesTruncated` 明示超限。无同帧每Symbol输出签名、旧Placement options/duration/commitTime/fadeStartTime、CPU/GPU阶段计时,这些不在本次实现中伪造。
- Debug 的 `processSimulationAdvance` 复用现有进程级 atomic 计数;Release为null。它不是单Map的计数,也不等于Fast尝试或FPS。
120帧全部Gate每次尝试都记录;容量满时覆盖最早帧,`overwritten` 累计。生命周期 `gates[].failures/firstFailures` 保留聚合;`windowFailures`、窗口首末序号、窗口 Full/Fast/无树计数另行导出。默认30fps时120帧约4秒,真实渲染频率可能更高,不能承诺完整60秒逐帧档案。
环形记录在 render thread 更新;写入、完成、reset和外部copy沿用 `rangeMutex`。Android UI只读现有共享snapshot,不操作Renderer可变对象。内部render snapshot默认不复制trace;只有外部 `Map::getRenderSegmentDiagnostics()` 包含历史。reset发生于绑定新的 runtime state、观测到 styleLoaded true→false、context lost、Renderer.clearData;每次普通 Full miss清prepared tree**不会**清trace。sceneGeneration/configRevision另用于区分UI场景切换;同步加载没有暴露false帧时,不能假定trace epoch必然随UI场景切换。
新增CPU工作为O(24+R)、固定容量历史;没有额外GPU pass。每帧仍有小型vector/string副本与短mutex临界区,外部读取序列化120帧也有成本。**内存字节数和CPU成本没有实测,不声明低于某一百分比**;RDC需量测。没有诊断开关的新RN API;如需成本对照,应使用同环境基线源码制品与本次制品并记录差异。
## 4. A:Style、Layer 与场景等价度证据
| 项目 | 证据等级与当前结论 |
|---|---|
| 正式Style/Sprite/Glyph/VectorTile URI | **SOURCE-PROVEN**:NavSea `src/config/builtinRuntimeUrlConfig.ts` 与新场景常量相同 |
| NavSea当前分段选择器 | **SOURCE-PROVEN**:`RenderSegmentHost.tsx` 为R→C,C从 `rn-weather-color` 开始;不是现场四段计划 |
| 天气片段 ID/type/source | **SOURCE-PROVEN**:`particleStyle.ts` 调用shared fragment,追加到顶;shared `packages/maplibre-particle-rn/src/index.ts` 定义 `rn-weather-fields`、`rn-weather-color`、`rn-weather-particles` |
| 正式63层保序/显隐/完整定义 | **TEST-APP-STRUCTURAL**:运行时从正式URL加载,要求63层和实际锚点ID;完整原始定义逐层不变、顺序不变,并导出生产manifest与实际Native styleLayers。**尚未在设备执行该证明** |
| Sprite/Glyph/VectorTile访问成功 | **NOT-VERIFIED**:本机web open失败;urllib回读失败于域名无法解析,没有服务器JSON/SHA或瓦片访问成功证据 |
| 113层及[0,13)/[13,33)/[33,107)/[107,113) | 用户STEP-112历史现场输入;本轮没有同版本完整Layer/Source/visibility/业务数据快照,**NOT-VERIFIED,未完整复制** |
| 旧NavSea FZG采样 | 只读找到V15报告与129层采样;年代/版本/层数均不同,不用于补造当前113层 |
| 本轮真机/性能 | **DEVICE-MEASURED:无本轮结果** |
生产URI分别为:
- Style:`https://pbf.jpcn.ccwu.cc/style/style.navsea-delivery-full-semantic-extentfix.json`
- Sprite:`https://pbf.jpcn.ccwu.cc/sprite/sprite`
- Glyph:`https://pbf.jpcn.ccwu.cc/fonts/{fontstack}/{range}.pbf`
- VectorTile:`https://pbf.jpcn.ccwu.cc/pbf/pbf-delivery-full-20260418-rebuild/{z}/{x}/{y}.pbf`,source=`navsea_delivery`。
场景会检查真实回读的资源URI;变更或缺失则显示BLOCKED并导出原因,不切换假地图。`productionFingerprintSha256` 是SDK回读序列化JSON的UTF-8 SHA-256,**不是HTTP原始字节SHA**,且URL本身可变。导出styleVersion/name/metadata(styleVersion是Style规格版本,包版本未知时为null)、生产/增强fingerprint、basemanifest、请求plan、最终Native所有Layer的index/id/type/source/visibility,以及段边界与层名。Native可能追加Annotation等内部层,不能把JSON基础63层等同实际总层数。
| 场景 | 行为与等价限制 |
|---|---|
| PBF63 | 正式63层原定义;无测试天气片段;全RETAINED,实际总层数以Native为准 |
| STATIC_RC | 原63层+正式类型的天气/粒子片段(JSON65层);保序append;当前NavSea源码RC选择器;不等价NavSea全部动态业务层 |
| PARTICLE_OFF | 与STATIC_RC同样层序,粒子visibility=none;WeatherColor/forecast/camera保持对照 |
| PARTIAL_RCRR | 原63层+天气片段,片段放在实际 `clip-outline` 后;四段锚点为 `sea-area-fill`、`special-pattern-line-754`、**实际回读的最后生产层ID**;明示结构RCRR,与现场113层/数字边界不同 |
| SOURCE_CHANGE | STATIC_RC加一个独立GeoJSON/circle测试Source;显式菜单单次更新;结构探针,不冒充AIS/Radar/航线等生产数据 |
所有带天气场景使用既有ECMWF canonical两个时间片(2026080700/06),forecast请求 `1786060800000` ms,PAUSE,targetFps=30,粒子1024。与NavSea当前QBIN/预报业务输入不等价。命令接受结果逐项保存;实际Native预报时间没有新增查询API,`actualForecastTimeObservedMs=null`,不把Host请求值当Native独立回读结果。camera请求33.45/129.97、zoom10.5、bearing/pitch0;场景禁用手势,退出恢复原手势设置。静态轮询不改Source/Symbol/forecast/camera。
Native准入限制保持原样;RCRR结构模式预期可以暴露不支持的C层和Symbol R阻塞,但这只是源码预期,不是已经在FZG测到的结果。
## 5. C:UI、导出及精确RDC命令
入口:Test App `STEP 093 → STEP-113 FZG1-PBF GATE`。五个场景、`EXPORT LAST SNAPSHOT`、`MUTATE PROBE SOURCE ONCE`、退出。加载中或失败时也保留低频诊断读取,sceneReady=false/blockedReason明确标注,不能把可能仍在加载的旧Native层表当已完成场景。每3秒只有一次Native诊断查询;UI首错来自trace末帧,失败排名来自同copy聚合,各段显示范围/state/build/reuse/invalidation。仅STEP-113期间增大诊断区域,退出恢复。
每30秒一条逻辑summary(通常少量物理Logcat分块);不是每33ms输出JSON。完整120帧和Layer表仅手动导出最近一次copy,写App私有 `files/step113-<SCENARIO>-<elapsed>.json` 并使用标签 **STEP113Gate** 输出Base64分块。每块payload≤2400 ASCII字符,携带schemaVersion/kind/exportId/chunkIndex/chunkCount/encoding;按exportId和chunkIndex完整拼接后Base64解码,缺块不能当完整证据。手动完整导出会产生较多块,优先直接回读私有JSON文件。
以下仅RDC执行,本轮Codex没有执行。先在macOS普通Terminal核对设备主ABI与标准脚本的x86_64一致;不同则停止本构建流程并记录,不自行改脚本/环境:
```bash
cd /Users/tei/dev/MapLibreParticleLab
step113_abi="$(adb -s 192.168.200.139:5555 shell getprop ro.product.cpu.abi | tr -d '\r')"
step113_abilist="$(adb -s 192.168.200.139:5555 shell getprop ro.product.cpu.abilist | tr -d '\r')"
printf 'FZG1-2 primary ABI=%s; ABI list=%s\n' "$step113_abi" "$step113_abilist"
test "$step113_abi" = x86_64 || { echo 'ABI不一致,停止'; return 1 2>/dev/null || exit 1; }
./tools/particle_android_validation/build_validation_apk.sh
```
标准产物:`artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk`、`build.log`、`build-info.txt`;脚本记录commit/branch/dirty/SHA/体积并自验ZIP与Native ABI,失败不得复用旧APK。本轮源码未提交,build-info里的dirty必须真实,另保留任务文件diff/hash以证明制品来自本次修改。
安装前RDC核对现存同包签名,签名不匹配不得自动卸载用户数据;保留INSTALL_FAILED错误。安装与静态场景启动:
```bash
adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
adb -s 192.168.200.139:5555 shell am start -S -n org.maplibre.android.testapp/org.maplibre.android.testapp.activity.particle.ParticleCoreLayerValidationActivity --es step113_scenario STATIC_RC
adb -s 192.168.200.139:5555 logcat -v raw -s STEP113Gate:I '*:S' > artifacts/maplibre-particle/android-validation/step113-static-rc.log
```
最后一条在RDC普通前台Terminal采集,窗口完成后Ctrl-C。其他场景各用一条明确启动命令,并独立保留日志:
```bash
adb -s 192.168.200.139:5555 shell am start -S -n org.maplibre.android.testapp/org.maplibre.android.testapp.activity.particle.ParticleCoreLayerValidationActivity --es step113_scenario PBF63
adb -s 192.168.200.139:5555 shell am start -S -n org.maplibre.android.testapp/org.maplibre.android.testapp.activity.particle.ParticleCoreLayerValidationActivity --es step113_scenario PARTICLE_OFF
adb -s 192.168.200.139:5555 shell am start -S -n org.maplibre.android.testapp/org.maplibre.android.testapp.activity.particle.ParticleCoreLayerValidationActivity --es step113_scenario PARTIAL_RCRR
adb -s 192.168.200.139:5555 shell am start -S -n org.maplibre.android.testapp/org.maplibre.android.testapp.activity.particle.ParticleCoreLayerValidationActivity --es step113_scenario SOURCE_CHANGE
```
不要把这些命令连续执行当场景测试;每个场景至少预热10秒、观察60秒、手动导出窗口,再切下一场景。SOURCE_CHANGE先导出baseline,单击手动探针变化,等一次3秒刷新后再次导出;不添加周期Symbol更新。
点击 `EXPORT LAST SNAPSHOT` 后回读最近文件(run-as仅适用于该Debug App):
```bash
step113_file="$(adb -s 192.168.200.139:5555 shell run-as org.maplibre.android.testapp ls -t files | tr -d '\r' | rg '^step113-.*[.]json$' | head -n 1)"
test -n "$step113_file" || { echo '未找到手动导出文件'; return 1 2>/dev/null || exit 1; }
adb -s 192.168.200.139:5555 exec-out run-as org.maplibre.android.testapp cat "files/$step113_file" > "artifacts/maplibre-particle/android-validation/$step113_file"
shasum -a 256 "artifacts/maplibre-particle/android-validation/$step113_file"
adb -s 192.168.200.139:5555 exec-out screencap -p > artifacts/maplibre-particle/android-validation/step113-screen.png
adb -s 192.168.200.139:5555 logcat -b crash -d > artifacts/maplibre-particle/android-validation/step113-crash.log
```
如果只保留Logcat,使用下面的离线重组命令;每个导出必须收齐chunkCount,否则直接报错:
```bash
python3 - <<'PY'
import base64, json
from pathlib import Path
root = Path('artifacts/maplibre-particle/android-validation')
groups = {}
for line in (root / 'step113-static-rc.log').read_text().splitlines():
try:
c = json.loads(line)
except json.JSONDecodeError:
continue
if c.get('encoding') != 'base64-utf8':
continue
key = (c['exportId'], c['kind'])
count, parts = groups.setdefault(key, (c['chunkCount'], {}))
assert count == c['chunkCount'], key
parts[c['chunkIndex']] = c['payload']
for (export_id, kind), (count, parts) in groups.items():
assert set(parts) == set(range(count)), ('缺块', export_id)
raw = base64.b64decode(''.join(parts[i] for i in range(count)), validate=True)
json.loads(raw)
(root / f'step113-{export_id}-{kind}.json').write_bytes(raw)
PY
```
RDC的macOS Native定向检查可按当前preset执行(历史缺libuv阻塞不代表当前环境仍相同;若失败保留首错,不扩大改依赖):
```bash
cmake --preset macos-metal
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
build-macos-metal/mbgl-test-runner --gtest_filter='FastGateTrace.*:ParticleFastPath.*:RenderSegmentDiagnostics.*:RenderSegmentPlacementFree.*:RenderSegmentLocality.*' --gtest_color=no
```
配置失败就停止该链;不运行旧runner冒充新源码通过。Metal host测试不能替代Android OpenGL/实体机/iOS验收。
## 6. 已执行静态检查与未做项
已执行:只读Git/源码/文档调查、diff范围检查、`git diff --check`(退出0);新增文件另用no-index whitespace检查。静态文本核对24条谓词及极性/顺序一致;生产 evaluator 与HEAD逐字相同;STEP-102 fixture与500ms poll逐字未变。最终hash与append-only核对见第7节。
4个测试源码覆盖:全部24独立失败位/生产首错顺序;CacheInvalid与#22/#23同帧并存、137次记录后120条有序窗口与reset;pending/null post/错误shadow显式计数和完成幂等;18个R截断及pre/post独立缓存计数。**没有运行这些测试,没有C++/Kotlin编译或语法编译PASS结论。**
本轮无APK/AAR、体积或SHA,无设备运行日志、截图、FPS、CPU、trace开销或性能改善数字。没有已观察到的构建/测试失败;未执行不表示会通过。
剩余RDC事项:Native/Kotlin编译和上述定向测试;设备ABI/制品SHA/版本证据;正式资源加载、原63层manifest/实际层序;plan接受与最终valid原因;所有24状态与首错一致性;cache-invalid同帧后续失败;Full/post和pre分离、Source变更前后、粒子关闭对照;reset/退出/恢复与旧场景回归;开销/CPU和截图。**APK成功仅是构建证据。**
113层完整等价仍需同版本真实Layer/Source/业务数据/显隐/选择器快照后才能扩展;本任务交付的部分模式不为生产Full循环提供完整因果或性能验收结论。
## 7. 收尾证据
- STEP-110任务书SHA-256:`33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0`。
- STEP-111任务书SHA-256:`f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4`。
- STEP-113任务书原样SHA-256:`4e3fef391dc3addef2702b3d588c1fcc8f79b898b5d9004bfc1410070533e0b4`。
- HEAD保持起点,暂存区为空;仅任务源码/测试、两个新交付文档和开发记录追加,既有三任务书未暂存。没有远端HEAD回读、commit/push或制品发布行为。
- 开发记录的HEAD原始160442字节前缀完全保留,原SHA-256=`070aaeaf85aa8fdbe8b15a899f74fd492a0e3eb2b6442b37cb3d5ee9724dbd24`;本次仅末尾追加1120字节(7行)。静态检查是范围与一致性证据,不是全任务PASS。
## RDC 独立验收补记(2026-10-11,设备证据)
以下是 Codex 退出后 RDC 新执行的证据,不修改上文 Codex 未构建/未实测的历史结论。本次阶段判定:**PARTIAL;Android 构建 PASS;五场景稳定基线采样 PASS;113 层等价与性能 NOT VERIFIED**。
- 环境:Mac、particle/main;构建源码基于 c6d13e1059d464d312ac33acac87967300fa5dc4 加 STEP-113 未提交改动,build-info 明确 git_dirty=true。此 APK 不是 Source Lock / 正式发行。
- 标准 build_validation_apk.sh 于 2026-10-11 08:35:08 JST 退出码 0;Gradle BUILD SUCCESSFUL in 19m 22s;Java 17.0.20.1;OpenGL/x86_64;ZIP/ABI/体积/SHA 自检通过。
- APK:artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk;66,587,454 bytes;libmaplibre.so 40,033,976 bytes;SHA-256 0054248bc3872e8670df4e9a10d11f1d65c0912a0482586cd7a247e880a6eaef。记录:同目录 build-info.txt、build.log。
- 设备:FZG1-2,ADB 192.168.200.139:5555,主 ABI x86_64;明确 -s 的 adb install -r 返回 Success,未卸载数据;设备安装后版本 13.6.1-particle.23。正式 PBF Style 模式运行并加载。启动前数秒 sceneReady=true 但 Native Layer=0 / plan not-configured 是加载暂态;只接受之后的 Native Layers 非零、planValid=true 稳定记录。
- 五个场景均由 ADB 明确 serial 启动;STEP113Gate Logcat Base64 summary 成功还原。每场景实际设备稳定状态的 decisionMismatch 都是 **0**。Full/Fast/Attempts 为生命周期累计,并非精确 60 秒窗口。
| 场景 | Native Layers | Attempts | Full | Fast | Retained 及关键证据 |
|---|---:|---:|---:|---:|---|
| PBF63 | 64(63+内部层) | 106 | 106 | 0 | R[0,64) Build0,unknown-dependency;无粒子持续驱动 |
| STATIC_RC | 66 | 876 | 875 | 0 | R[0,63) Build0,unknown-dependency;unsupported-continuous-layer、unsupported-scene 各876 |
| PARTICLE_OFF | 66 | 70 | 70 | 0 | no-particle-repaint=70;非相等时长性能对照 |
| PARTIAL_RCRR | 66 | 862 | 861 | 0 | R0[0,3) unknown-dependency;R2[39,64) 与 R3[64,66) Build0/static-placement;两个 unsupported Gate 各862 |
| SOURCE_CHANGE | 67 | 888 | 888 | 0 | 基线已记录;**没有执行菜单手动 Source mutation** |
- STATIC_RC 同帧失败全集汇总 CacheInvalid=870、revision-mismatch=871、placement-or-fade=780;PARTIAL_RCRR CacheInvalid=856。这些失败条件可能在**同一帧**并存,不能累加为帧数。即使修复缓存 Build,也还有 #22/#23 与其它独立 Gate。
- 设备摘要 JSON(不纳入 Git):artifacts/maplibre-particle/android-validation/step113/ 下的 pbf63-summary-ready.json、static-rc-summary.json、particle-off-summary.json、partial-rcrr-summary.json、source-change-baseline-summary.json。它们只是低频 summary,不是导出的完整 120 帧。
- 仍 NOT VERIFIED:原 NavSea 113 层完整 Layer/Source/显隐及真实四段边界;实际 PBF/Sprite/Glyph 画面截图;Source 单次变更后前后对照;120帧手动详情导出;C++ GTest 运行;reset/后台、旧 STEP-102 场景回归;CPU/GPU/诊断开销;生产性能提升、正式发行。不得把部分结构等价当成现场复测或将 STEP-113 判为 CLOSED/PASS。
- 下一阶段必须分别处理 retained first-build 的安全准入、unsupported continuous layer、unsupported scene;不能直接删除 Gate 放行。
## RDC 第二轮设备验收(2026-10-11:完整窗口与Source变更)
**本轮状态:设备逐帧诊断 PASS、单次Source变更链路 PASS,完整113层等价与生产优化仍 PARTIAL。** 本记录追加在原有RDC基线结果之后,旧内容中的“未执行单次Source mutation/120帧导出”是第一轮结束时的历史状态,不代表第二轮。
1. 在设备 FZG1-2、ADB 192.168.200.139:5555、版本 13.6.1-particle.23 上运行 SOURCE_CHANGE;通过屏幕中的 STEP 093 → STEP-113 → EXPORT LAST SNAPSHOT,使用明确serial的 adb exec-out run-as 回读 App 私有JSON;文件字节分别为变更前 277980、变更后约272KiB。精确位置:artifacts/maplibre-particle/android-validation/step113/source-change-before-full.json 和 source-change-after-full.json,均保留在 Mac 本机、未纳入Git。
2. 变更前 trace:lifecycle 1,attempts 13859,capacity/windowCount 120,最新119个完成的Full帧、0 Fast帧、最后1帧为pending、decisionMismatch=0;变更后 trace:同lifecycle,attempts 17476,windowCount=120,同样119个已完成Full、0 Fast、decisionMismatch=0。相同frame中前置状态 pre 的 placementBasis 标为上一完成RenderTree,post 完成Full才使用当前Full tree;knownMask覆盖24条Gate。
3. 在 SOURCE_CHANGE 场景操作一次 MUTATE PROBE SOURCE ONCE。Logcat解码的 source-command:mutation=1;变更后导出 commands.sourceMutationCount=1;Native lastMutation.sourceId=step113-source-probe,affectedSegmentIndices=[1],事件 TileMutation。变更前 lastMutation.sourceId=navsea_delivery,affectedSegmentIndices=[0]。累计 content-mutation Gate failure 从12变为14,但中间同时有正常瓦片更新,**不得把全部增量单独归因为这一次probe**。120帧环形窗口会覆盖发生瞬时变更时的精确帧,故本轮只确认命令/Native依赖定位,不声称捕获到单次Mutation帧的全部过渡时序。
4. 从变更后120帧中统计119个 completed 样本:首错 cache-invalid=119/119;同一帧 Gate #3 cache-invalid=119、#4 revision-mismatch=119、#12 static-unstable=105、#13 placement-or-fade=105、#22 unsupported-continuous-layer=119、#23 unsupported-scene=119。24Gate shadow 对应生产首错持续吻合,decisionMismatch=0。该统计是窗口而非全应用时间的FPS度量。
5. PBF同源核验:原始63层按SDK回读生产Style逐层定义保序、baseLayersPreserved=true;spec version=8,源Style名称 NavSea Delivery Full Semantic ExtentFix;productionFingerprintSha256=e0f9c2d1df38ae5d5953b561c52efe2f8590336de80b988e1d2935580d30b996(SDK回读序列化JSON,不是HTTP包字节)。SOURCE_CHANGE运行时67层,连续段[63,67)依次是 weather-color、raster-particle、测试circle、内建 org.maplibre.annotations.points symbol。STATIC_RC的连续段[63,66)同样包括内建annotation symbol。现有 continuousRangeSupportsParticleFastPath 要求其范围内**每一层**均支持 retained dynamic range;因此这些普通circle/symbol使Gate #22持续失败;这项结论为“源码判定+本机层清单”,未进行绕过Gate的运行实验。
6. Gate #23 supportedScene 源码逻辑与 retainedSupported、fastContinuousSupported 及单C配置有关,**#22与#23不是根因完全独立的两次失败**;二者虽为不同Gate,却可能共享同一个不支持的C层根因。Retained R[0,63)仍持续 Build0、unknown-dependency;部分RCRR其它R也有static-placement。不能只消除C阻断就承诺Fast会成功。
7. NavSea只读核对:当前src/map/particle/RenderSegmentHost.tsx 配置 R→C,从 rn-weather-color 开始;旧STEP-112的FZG1 113层RCRR_PREFIX属不同运行配置。同版本113个Layer的完整Source/ID/type/visibility/业务数据清单仍缺,不能把TEST-APP-STRUCTURAL的63/66/67层宣称为113层完整等价。FZG1-2存在debug NavSea安装,但**本轮未启动/修改NavSea用户数据**。
8. Android设备截图已采集:artifacts/maplibre-particle/android-validation/step113/step113-source-change-screen.png(1920×1200),仅归档,未进行NavSea同版本逐像素比对。Mac Native GTest CMake配置尝试退出码1:缺少libuv(记录/tmp/particle-step113-macos-cmake.log);未安装依赖,未进入GTest构建与运行,不记PASS。
9. 后续受保护顺序:先由跨项目负责人获取NavSea同版本113层完整只读snapshot及真实selector;然后在Test App精准构造Layer/Source/顺序并获取120帧一对一同条件比较;分别建立R Build准入、C范围支持、Scene聚合依赖的源码及像素等价证明;通过后才能在Shared Core按受控范围修改Gate逻辑。禁止在证据不足时关闭 fail-closed Gate。RDC未在本轮运行新Android编译、改变渲染逻辑或做正式发行。

View File

@@ -0,0 +1,134 @@
# STEP-114 Fast Path 安全准入与 Test App 闭环 Codex 完成报告 v1
日期:2026-10-11(JST)。
## 1. 执行结论
**PARTIAL:受控安全子集源码、独立最小场景与中文交付已完成;真实 Fast、编译和设备闭环未验收。Symbol R 准入 BLOCKED,CustomMVTVector 与非粒子 C 继续 fail-closed。**
核心结果:完整场景无任何结构性 Symbol 时,全局 Placement controller 的 stale 单独不再阻止 #13;有隐藏/空Symbol或AnnotationSymbol时保持原判定。R缺源明确报missing-source,仍拒绝Build/Reuse,revision登记机制不变。独立FAST_SMOKE用现有安全GeoJSON/circle R + WeatherColor/Particle C及真实Native计数;另交付3个反向场景,不直接注入candidate或人工Frame结果。
本机旧STEP-113 JSON明确navsea_delivery为普通Vector,R还依赖未在Native source表中的tide;不能把该unknown-dependency归因于CustomMVTVector。这项新事实已记录于调查,旧报告与正式PBF五场景不改。
## 2. 实际模型、Git与版本
- **GPT-6.1-Sol(gpt-6.1-sol)/ high**。实际从本机 `/Users/tei/.codex/sessions/2026/10/11/rollout-2026-10-11T09-44-12-01a1286a-d6a4-7e32-b91a-a9f3e63d8f3e.jsonl` 的最新 `turn_context.model/effort/cwd` 核实;未切换/升级模型,未启动子代理、另一实例或后台Codex。
- Mac / Darwin 24.6.0 / x86_64;仓库 `/Users/tei/dev/MapLibreParticleLab`。
- 起点branch=`particle/main`,start HEAD=`b02e54de3d45fcf612126ae8f037cba204f590ea`;无分支/worktree变更,未commit/push,收尾HEAD保持起点。
- 起点tracked clean;已有STEP-110/111/114三份未跟踪任务书。STEP-110/111原样保护,STEP-114只读不修改;暂存区未写。
- `git submodule status` 起点全部空格前缀,无未初始化或偏移标记;未操作子模块。
- Native/Test App=`13.6.1-particle.23`;没有改版本、RN公共方法/类型、Source Lock或发行。当前旧APK不包含本轮新源码。
- Integration相关正式Contracts/RFC/ADR只读;任务提供main=`80667d6c343411d03a760425438e3d292f452118`采用既有RDC事实,没有同步/fetch/pull或远端回读,没有跨项目写入。
## 3. 实际修改文件(tracked)
| 文件 | 实际内容与影响 |
|---|---|
| `src/mln/renderer/particle_fast_path.hpp` | 内部结构检测helper;非空完整层表无Symbol才可忽略controller transition;生产24Gate evaluator与失败掩码未改 |
| `src/mln/renderer/render_orchestrator.cpp` | #13输入按全场结构限定,层表数量不符保守;缺RenderSource先报missing-source;CustomMVT拒绝仍保留 |
| `test/renderer/retained_underlay_cache.test.cpp` | 3个Step114FastPathSafety用例源码:无Symbol/其它Gate保护,隐藏/空/AnnotationSymbol保护,revision/cache注册/异步失效/全局失效状态机 |
| `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt` | 独立菜单/Intent/状态恢复、统一场景退出、诊断区域与前后台生命周期;原STEP-113菜单明确标正式PBF |
| `粒子开发记录.md` | 仅末尾追加本轮中文状态与下一步 |
## 4. 实际新增文件
1. `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step114FastSmokeScene.kt`:独立离线FAST_SMOKE/3负场景,真实Native低频UI、操作前后文件、Source/Image/Camera/缓存/真实Annotation操作及Reload/生命周期取证。
2. `docs/20_调查/STEP-114_FastPath安全准入与闭环判定_v1.md`:R/C/Placement控制流、旧JSON事实、mutation追踪、安全前提/反例及停止范围。
3. `docs/60_测试/STEP-114_独立FastSmoke场景与RDC验收_v1.md`:场景说明、Mac精确定向命令、后续Android用户手工命令、实际JSON正向检查与反向/生命周期矩阵。
4. `docs/50_Step记录/STEP-114_FastPath安全准入与TestApp闭环_v1.md`:已做/未做勾选、真实结果与待验收表。
5. 本报告。
用户提供的未跟踪STEP-114任务书不是本轮生成物。旧STEP-110/111未跟踪文件未修改/暂存;原STEP-113场景类与STEP-102 fixture未修改。
## 5. 安全范围、兼容与零修改部分
R准入/RevisionBook/GPUcache算法零语义修改;缺源只修正诊断原因。没有证明CustomMVT完整revision契约,保留其拒绝。普通Vector和GeoJSON不是CustomMVT,不混淆类型。独立GeoJSON子集现有sourceDiff/immutable signature/onTileChanged/资源epoch/key保护详见调查。
非粒子C准入与Scene复合条件零修改,#22/#23保留。FAST_SMOKE独立Style在没有Annotation实例时移除其空内建layer;保留source,不改AnnotationManager。ANNOTATION_BLOCKED重新加载合法Style,保留内建layer并创建真实marker;不是生产零重载Annotation能力。原正式PBF五场景完整保留。
Symbol R的Build gate、commit/fade、scene placementStable、全图碰撞、controller计算、资源链均零修改;无最终GPU输入结果证书,停止新增签名设计。#13改动只影响完整无Symbol场景;空表/数量不一致保守,有一个隐藏/空Symbol也按原transition阻止。尚未进入旧表的新layer被immutable signature、Annotation pending和其它原Gate保护。
Shared Core没有平台分叉,没有新RN/JS/JNI控制API/ABI。既有可选诊断schema和24Gate顺序未改;缺源理由从模糊unknown-dependency变为已有理由missing-source是有意诊断差异。preFastPlacementTransition为本轮限定后的实际输入,post原始placementTransitionActive可能仍true;此差异不是shadow mismatch,也不是修改controller。
风险/限制:Native与Kotlin未编译;FAST_SMOKE尚未实测;本地sceneReady不是Native运行PASS;自动短延迟采样可能漏瞬态;App私有快照文件随显式操作增长;性能/像素/GL与Metal差异未测。Future新Placement-capable类型须同步扩展结构检测,当前只有Symbol声明CrossTileIndex::Required。
## 6. 与任务书差异与停止点
按步骤完成源码审查与可安全范围实现;任务允许PARTIAL。没有危险地放行CustomMVT、Annotation C或Symbol R,也没有复刻NavSea113层。R cache正向利用现有已支持GeoJSON,不新增未证source准入。新增无Symbol场景#13限定是调查中确认的安全局部修复;缺源更准确诊断使真正失败点可见。
真实Fast/反向/lifecycle闭环仍待执行。Symbol R结果证书需要更大跨平台设计,列BLOCKED;若涉及公共语义/API,回Integration RFC,不自行扩改。任务允许改测试源但禁止Codex运行编译/测试,遵守该边界。
## 7. 精确定向 RDC 验收命令
下面只交RDC/普通Terminal执行。进入仓库后逐项执行,任何失败立即停止链,保留完整日志,不用旧runner;带完整退出码保护的可复制脚本在测试说明第2节。
```bash
cd /Users/tei/dev/MapLibreParticleLab
cmake --preset macos-metal
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
build-macos-metal/mbgl-test-runner --gtest_filter='Step114FastPathSafety.*:FastGateTrace.*:ParticleFastPath.*:RetainedUnderlayCache.*:RetainedUnderlayRevision.*:RenderSegmentPlacementFree.*:RenderSegmentLocality.*:RenderSegmentPlan.*:RenderSegmentDiagnostics.*:RenderOrchestratorError.*:RenderOrchestratorTeardown.*:ImageManager.*:GlyphPBF.*' --gtest_color=no
```
renderer=Metal,Debug preset,parallel=3,target仅mbgl-test-runner。记录命令/exit/耗时/log路径/实际用例数量/skipped;新增3个Step114用例必须被发现并执行。STEP-113缺libuv的配置失败日志仍是既有证据,本轮未再次配置、未安装依赖;若仍失败,保留首错并记BLOCKED,不能宣称测试通过。
Android由用户后续一次手工运行 `./tools/particle_android_validation/build_validation_apk.sh`;其后手工安装/启动/取证命令逐条见测试说明第3—5节,固定serial=`192.168.200.139:5555`。入口Intent=`--es step114_scenario FAST_SMOKE`,三个负场景名为CIRCLE_C_BLOCKED/ANNOTATION_BLOCKED/SYMBOL_R_BLOCKED。完整快照为App files/step114-*.json;保存source/image/camera/cache动作前、提交后、100/500/1500ms证据。Codex没有执行这些命令。
## 8. 已执行检查与未执行验收
已执行:前置规范/任务/实际源码阅读,Git branch/HEAD/status/submodule只读;本地当前rollout模型字段核实;保留的STEP-113JSON解析、SourceType与缺tide取证;无构建静态函数/范围/哈希/追加完整性核对;tracked及新增文件空白检查。
**NOT RUN**:CMake/Ninja/Bazel/Native编译链接、C++测试、Kotlin编译、Gradle/APK/AAR、ADB/安装/logcat/截图/设备、Jest、性能/GPU/像素对照、iOS实机、发行、commit/push/远端HEAD。没有本轮编译/测试失败日志,也不能据此推断会通过。3个新增C++用例是源码交付,不是运行PASS;状态机markBuildSucceeded不是GPU成功证据。
静态检查的最终实际结果与保护哈希在第12节记录。终态仍有本轮tracked/untracked改动,未暂存、未提交。
## 9. 产物
实际产物仅源文件、测试源、四份中文文档与开发记录追加。无本轮APK/AAR/XCFramework、版本/SHA、构建/设备日志或运行截图。artifact目录的STEP-113JSON属于旧RDC证据,本轮只读未修改,不作为本轮新产物。
## 10. 剩余阻塞与未完成事项
| 项目 | 状态/原因 |
|---|---|
| 独立smoke真实Fast | 未验证;必须NativeFull/Fast/Build/Reuse及24Gate和截图共同证明 |
| Mac配置/编译/测试 | 未执行;旧libuv阻塞是否仍存在未知 |
| Android编译与设备闭环 | 未执行,留用户一次手工统一验收 |
| Source异步变化/资源/camera/cache/Reload/前后台/销毁 | 已有源码和精确取证入口,运行证据待执行;捕获失败不可假称安全反向PASS |
| Glyph实际结果/资源失效 | 未新增TestApp glyph控制;无Symbolbaseline不含glyph像素;现有资源回归不能代替Symbol证书 |
| Symbol R首次Build | BLOCKED;commit/fade没有充分稳定结果证书,不做危险简化 |
| CustomMVT/非粒子C | fail-closed;无完整revision/复合等价证明 |
| PBF缺tide/大量Symbol | 缺源诊断已清楚,原数据/业务层不改;不承诺生产Fast |
| iOS/Metal设备与CPU/GPU收益 | NOT VERIFIED;不以Android或Mac状态机结果替代 |
## 11. 独立验收证据与后续
RDC先核对本报告/调查/源码diff与安全前提,再跑Mac定向命令;用户后续统一Android。baseline必须真实4层与R[0,2)/C[2,4),至少一个completed fast-rendered、knownMask=0xffffff/failMask=0、decisionMismatch=0、cacheInputs valid/revision相等,Full>0、Build>0、同生命周期Fast/Reuse delta>0;同时截图确认Rcircle/天气/粒子及反向重建正确。测试说明含读取真实JSON的断言。
负场景必须继续阻止Fast并正确显示/更新内容;Source/Tile/Image/Camera/缓存禁用/Reload/前后台/Teardown、旧PBF5场景及STEP-102独立回归仍需日志/快照与可见结果。只见Fast数字不等于安全/性能PASS。若未达真实Fast维持PARTIAL,失败保留现场再返回受控修复。
本轮禁止Git提交/push;后续达合理门槛且获授权后RDC仅scoped提交本轮明确路径并核对origin远端HEAD。不得含原STEP-110/111,不发行,不自行创建后续Step。
## 12. 收尾静态事实
- `git diff --check` 退出0;五个tracked修改路径,无其它tracked修改。四份新中文文档与新Kotlin文件各执行no-index空白检查,无诊断;Markdown代码围栏成对。
- 生产`evaluateParticleFastPath`和`particleFastGateFailureMask`与HEAD提取的完整函数逐字相同;C范围准入、R全局/局部稳定性函数逐字相同,CustomMVT拒绝分支仍在。3个新增用例仅核对源码数量,未运行。
- 原`Step113PbfGateScene.kt`与HEAD字节相同,SHA-256=`6e7befeb90ae172aaeff4b636a7640274dfb1065d8c97744cbc6af8d22b4ccae`;`Step102PlacementFixture.kt`同样未变,SHA-256=`e44912970dd6353787be7a3e5f9924fe1249e2305d495f1e25e707352c97d3c7`。
- 开发记录的HEAD原始162365字节前缀完整保留,原SHA-256=`0479875a30577cb497b973f89a0d74b3b8b8d300d5dfb683ed3a537749b37b3f`;仅9行/1583字节追加。
- STEP-110 SHA-256=`33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0`,STEP-111=`f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4`,与既有保护值相同;本次任务书只读SHA-256=`415c20df0e8d514b68dfa79cd6d9394e4c6d2c4f215c402d9a4a365862c87cfc`。
- 旧RDC完整`source-change-after-full.json`只读SHA-256=`24a06fa1cb072a628c650723c58a880895ec656f4972fa55d3cb064bfb2aabda`;本轮新场景需要的两个cycle各metadata.json/field.f32已静态确认存在。
- 一次用Python stdin追加中文记录的尝试因UTF-8源码解析退出1,未写文件;随后用apply_patch完成追加。最终原前缀核对通过。这是工具文本编码失败,不是构建/测试失败。
- 终态`particle/main@b02e54de3d45fcf612126ae8f037cba204f590ea`;`git diff --cached --name-only`为空。status为本轮5个tracked修改、5个新增文件,以及原有STEP-110/111/114三任务书;未commit/push/回读远端。
以上只证明静态一致性、范围与保护文件完整性,不作为Native/Kotlin编译、GPU/Fast或全任务PASS。
## RDC 独立审核追加(2026-10-11)
**结论:源码/差异/接口静态审核 PASS;Native 与 Android 编译、Fast 帧 NOT VERIFIED;整体 STEP-114 PARTIAL。**
- RDC 独立阅读本轮 Native diff、C++ 新用例、STEP-114 Android Test App 新场景与菜单、生命周期接线;git diff --check 退出码 0。本轮只有范围内的五个 tracked 改动和新增 Kotlin/中文文档;原 STEP-110/111 未跟踪任务书不修改、不暂存。
- 新增三项 Step114FastPathSafety 用例存在,覆盖无 Symbol、隐藏/Annotation Symbol 和 Source revision/cache 状态机,但**没有实际运行**。
- Step113PbfGateScene.kt SHA-256 为 6e7befeb90ae172aaeff4b636a7640274dfb1065d8c97744cbc6af8d22b4ccae,Step102PlacementFixture.kt SHA-256 为 e44912970dd6353787be7a3e5f9924fe1249e2305d495f1e25e707352c97d3c7,均未变化。
- Java SDK 只读核对实际提供 Style.removeLayer(String) 与 MapLibreMap.getAnnotations()。Kotlin/Gradle 编译未执行。
- Mac 预检:pkg-config --exists libuv 返回 1,与先前 macos-metal CMake required libuv 失败一致。独立 C++ header -fsyntax-only 试验退出码 1,首错为 Xcode AppleClang 找不到标准 C++ 头文件 array,实际在进入项目源码检查前终止。日志存于 Mac /tmp/step114-header-check.log。两项均是环境阻塞,不是本轮 Native 测试 PASS 或已证明代码错误;未安装/修改依赖。
- 实际 Fast Path 命中、正反 GPU 渲染、Android 设备、性能、iOS 均未验证。正式 PBF 仍有 tide 缺源、Symbol R Placement、内建 Annotation C 的阻断。STEP-114 独立最小场景属于待用户一次 Android 手工构建集中验收内容,不能用旧 APK。
- 后续 scoped Git 收尾若执行仅表示 PARTIAL 源码及测试入口已归档,**不代表正式发布或 Fast Path 修复完成**,不打 Tag/Source Lock。

View File

@@ -0,0 +1,112 @@
# STEP-116 STEP-113 原 PBF Fast Path 实际修复 Codex 完成报告 v1
日期:2026-10-11(JST)。**源码交付 PARTIAL;原 STATIC_RC Fast 未实测,缺 tide 与 Symbol R 准入仍 BLOCKED。已按授权完成可控修改后停止,交 RDC 独立审计。**
## 1. 实际模型与工作区
- 实际模型全名 **GPT-6.1-Sol(gpt-6.1-sol)/ high**。读取本地会话 `01a1286a-d6a4-7e32-b91a-a9f3e63d8f3e` rollout 的 `turn_context.model/effort` 得到 gpt-6.1-sol/high;未切换模型、无子代理或第二Codex进程。
- 机器 Darwin 24.6.0 / x86_64;仓库 `/Users/tei/dev/MapLibreParticleLab`,branch `particle/main`。
- 起点/交付HEAD:`e2d202b560ad0165101546597c43f2ef0ccf4542`,未commit/push,未回读远端HEAD。起点tracked clean,STEP-110/111/116任务书未跟踪;子模块状态正常,未操作。
- Android Native版本仍 `13.6.1-particle.23`。没有新APK/AAR,不能使用原STEP113 APK代表此次源码。
- Integration main `80667d6c343411d03a760425438e3d292f452118` 采用用户给定RDC事实,只读AGENTS/规范/contract/coordination;没有NavSea/Integration写入、同步或发行。
- 已读强制前置:根AGENTS/PARTICLE_BASELINE、全部项目规范、总则README及四份有效总则、STEP112/113/114/115现行交付及STEP116任务、相关实际实现和保留完整JSON。详细范围见调查文件。
## 2. 已实现 / 阻塞 / 未运行
| 项目 | 状态 | 结果 |
|---|---|---|
| 空SDK Annotation C | 已实现(Implemented),未验收 | STATIC_RC确认零实例/内部身份后仅移除一次points,保留Source、原63层;stop恢复,后续添加/Style替换撤销验收条件 |
| 后续真实Annotation不漏内容 | 已实现,未验收 | 私有ensurePointLayer在Native Symbol add/update及style-loaded同步恢复缺层;no-op update也dirty,在Map::onUpdate采样前完成,poll只报告,不承担恢复时序 |
| 缺源R | 阻塞(Blocked) | tide-spots依赖不存在tide;缺已执行GPU零贡献证书,不实现empty准入,维持missing-source |
| Symbol R Build | 阻塞 | Continuous重算changed、不重算stale两分支均阻断;无最终GPU/Placement/atlas等证书,不放宽Gate |
| 诊断/原生计数 | 已实现 | 可选missingSourceIds、独立Placement状态、真实Fast UI;24Gate及生产首错/计数不改 |
| STEP115复测工具 | 已实现,未运行 | tracked源码锁定;PID后实际≥75秒等待;同生命周期/24Gate/blocked撤销/计数reset拒绝,仍校验APK与设备SHA |
| C++测试源码 | 已实现,未运行(Not Run) | 四个Step116Safety账本/诊断/保守Gate用例,三个Annotations.Step116真实渲染回归 |
| 真正原PBF Fast、视觉/性能 | 无法验证 | 没有新构建、设备120帧或Fast增长,不写Closed/PASS |
未删除tide-spots、SOURCE_CHANGE circle、任何正式Symbol;没有改FPS/forecast/原PBF URI、Core C准入、Placement算法、cache成功计数或制造Fast。STEP114独立Smoke和STEP102 fixture未修改。非STATIC_RC四场景保留原Annotation语义;所有场景共享的Native Annotation修复仅在缺Source/points时补回,已有Layer不替换。
## 3. 实际文件清单
修改的源码/测试/工具:
1. `src/mln/annotation/annotation_manager.cpp`:缺失points的同步恢复与no-op更新dirty。
2. `src/mln/annotation/annotation_manager.hpp`:仅私有ensurePointLayer方法,无数据布局变化。
3. `src/mln/renderer/render_segment_json.hpp`:同snapshot的每段missingSourceIds只读诊断。
4. `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step113PbfGateScene.kt`:STATIC_RC空辅助层一次调整、恢复/撤销、manifest及独立阻塞摘要。
5. `test/api/annotations.test.cpp`:三项实际出图/恢复/不覆盖/reload测试源码。
6. `test/renderer/retained_underlay_cache.test.cpp`:四项缺源/async revision/资源全局/保守Symbol/多Gate诊断测试源码。
7. `tools/particle_android_validation/verify_step113_fast_path.py`:时长和拒绝条件;本轮没有执行脚本。
8. `tools/particle_android_validation/README.md`:更新当前复测边界并追加STEP116说明。
9. `粒子开发记录.md`:仅末尾追加本Step。
新增四份中文文档(不覆盖历史版本):
1. `docs/20_调查/STEP-116_STEP113原PBF_FastPath阻塞安全修复审查_v1.md`。
2. `docs/50_Step记录/STEP-116_STEP113原PBF_FastPath实际修复_v1.md`。
3. `docs/60_测试/STEP-116_STEP113原PBF专用APP验收_v1.md`。
4. 本报告。
**公开API/ABI与控制协议不变**:无include公开头、C/Java/Kotlin/RN/JNI方法签名、公共配置、版本/发行坐标变更;新增私有方法不改AnnotationManager成员布局。现有可选诊断JSON每段增加missingSourceIds,Test App私有summary增加诊断字段;这是任务授权的观测扩展,不新增Host控制协议或准入契约。
## 4. 静态结果与保护证据
已经执行:仓库/branch/HEAD/status/submodule只读核对;前置文档及源码阅读;保留JSON解析和SHA;diff人工审查、Python AST解析(没有导入/运行脚本);git diff --check、新增文档空白/围栏和范围检查。最终结果见文末静态收尾。静态检查不等同C++/Kotlin编译或测试通过。
未执行:Native/CMake/Ninja/Bazel、链接/codegen/测试程序、Gradle/APK、ADB/安装/Logcat/设备截图、Jest、性能测量、commit/push/远端回读、发布。没有观察到新编译/测试失败,因为未执行;不能推断将通过。
保留旧JSON SHA=`24a06fa1cb072a628c650723c58a880895ec656f4972fa55d3cb064bfb2aabda`。
- STEP-110未跟踪文件 SHA=`33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0`。
- STEP-111未跟踪文件 SHA=`f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4`。
- STEP102 Placement fixture SHA=`e44912970dd6353787be7a3e5f9924fe1249e2305d495f1e25e707352c97d3c7`。
- STEP114 FastSmoke源码 SHA=`94c28a111883fa784b0d9d81515c9e7ce7232d77a51eab48b4954705f1a39eb0`。
这些文件保持原样;保护任务书未暂存,开发记录原HEAD字节前缀保留。先前探索个别猜测路径不存在后,均按rg找到真实src/mln路径,未将不存在路径当证据或创建替代文件。
## 5. 精确定向交接
完整、按失败停止的命令和证据结构位于 `docs/60_测试/STEP-116_STEP113原PBF专用APP验收_v1.md`。必要顺序:
1. RDC只读审计diff/七项测试、线程和安全边界,按独立授权scoped锁定本轮源码;不得包含保护任务书或无关文件。
2. RDC Native:`cmake --preset macos-metal` → `cmake --build build-macos-metal --target mbgl-test-runner --parallel 3` → 定向filter `Step116Safety.*:Annotations.*:FastGateTrace.*:ParticleFastPath.*:RenderSegmentDiagnostics.*:RenderSegmentPlacementFree.*:RenderSegmentLocality.*:RenderSegmentPlan.*`。首项失败即停,不用旧runner。
3. 用户在最新锁定HEAD手工一次执行标准 `./tools/particle_android_validation/build_validation_apk.sh`,再明确serial手工 `adb install -r`,记录build-info/APK/安装SHA。
4. RDC/用户:`python3 -B tools/particle_android_validation/verify_step113_fast_path.py --preflight-only`,成功后 `--scenario STATIC_RC --seconds 90`;默认serial为192.168.200.139:5555。版本/源码锁不符停止。
5. 用户手动EXPORT LAST SNAPSHOT,RDC回读完整120帧,核对24Gate/mismatch0/pre-post、原63层/Source/空层manifest、真实Build/Reuse/Full/Fast;用户画面及安全回退单独验收。
预期仍有missing-source/Symbol阻塞。即使C空层修复成立,仍不得保证#23、R Build或Fast成功。缺源empty需要GPU排除/资源时序证据,Symbol需要绑定成功Full/Build的结果证书;当前任务授权不足以通过危险跨层重写弥补这两项。收到真实RDC编译/测试错误后按固定闭环修复,本轮停止于实现交付。
## 6. 实际静态收尾
- `git diff --check` 退出0,无空白诊断;四份新文档逐份 `git diff --no-index --check /dev/null <明确路径>` 无诊断,围栏成对;验收文档bash代码块仅以 `bash -n` 解析,内嵌Python和复测工具仅AST解析,没有执行命令正文或导入工具。
- 9个tracked文件修改,统计308行新增/12行删除;另有本轮四份文档新增。起点三份未跟踪任务书仍未跟踪,暂存区为空,branch/HEAD保持原值。
- 开发记录HEAD原始164685字节前缀完全保留,原SHA=`0adbe3fbe366651e3a79369dbf13d2e494a2b4545e29efaad72b299b43d2f4dd`,仅追加1440字节(8行)。
- 已逐字与HEAD比较:生产 `particle_fast_path.hpp`、公开 `render_segment.hpp`、`renderer_impl.cpp`、`render_orchestrator.cpp` 和 Activity接线完全未变;STEP110/111/102/114冻结SHA与上节相符,旧完整JSON也未修改。测试源码实际新增7个用例。
本轮实现与测试源的交付SHA-256(独立审计后若修正,应重算并记录,不能沿用此表冒充新源码):
| 文件 | SHA-256 |
|---|---|
| Step113PbfGateScene.kt | d50281713ae551edc9a48b1550254602e4950c9ddca66855bb689620c9f6f2b6 |
| annotation_manager.cpp | 6e0faf6ab585362bfb1ee4d6e79163390041295331d7c8c652156963318bfe6a |
| annotation_manager.hpp | a4c18d12a12dd4be3ddca7c667e9c45da8a57d6108956e605e1e4fe0796932c0 |
| render_segment_json.hpp | 9c1fa02fa0e7a2eccf4aeb8fe346913fb87b62258c662c303db087399cf9ca1d |
| annotations.test.cpp | b316c23c557a46f819895b7ea74a415ff5c833d0751521c28a5dd8251166daf6 |
| retained_underlay_cache.test.cpp | cc1d26540008df3b74cc97fa13e54e77092f2edb596d4c8c56d7a15504bdb109 |
| verify_step113_fast_path.py | 87a797eee0e907078cd8f49dbceb83dd3affda9b00c51cc691c1f4c553eb705e |
以上只证明交付范围和静态一致性,不证明Native/Kotlin可编译、测试通过或Fast安全。编译、设备、像素及性能仍全部未验收。
## RDC 独立核验追加(2026-10-11)
**本轮结论:限定范围源码/接口及操作说明静态审查完成;Mac Native配置 BLOCKED;Android APK/设备Fast NOT RUN,整体PARTIAL。**
- 起点粒子仓库 particle/main=e2d202b560ad0165101546597c43f2ef0ccf4542;Integration origin/main已由RDC fetch并与本地main同步,HEAD=80667d6c343411d03a760425438e3d292f452118。未接触NavSea与Integration正式文件。
- Codex实际模型 gpt-6.1-sol/high;本轮实现仅原STEP-113 STATIC_RC 的 SDK 空Annotation辅助层、Native AnnotationManager缺层恢复、缺源只读JSON诊断和测试验证器,不改变正式63个PBF layer、原五场景其他分支、Fast Gate/caching的准入或公开控制面。新增7项非Jest C++用例源码,尚未执行。
- RDC直接核对 Android Style.java 对象removeLayer(Layer)文档明确注明被移除Layer可以重新添加;与STEP116在stop中恢复原wrapper的目的相符。Native AnnotationManager恢复与事件生命周期仍须编译、图像和设备验证,不凭静态阅读标PASS。
- RDC核对旧SOURCE_CHANGE实际完整JSON:Native FastGate gates每项有order,firstFrame的knownMask等字段存在;STEP116验收脚本的原JSON结构检查有数据来源,但并非本轮运行证据。
- Mac shell当前无rg可执行,RDC将STEP116手动导出命令中的rg替换为标准grep -E,保留同样的文件筛选表达式。Python验收器ast.parse通过,git diff --check通过。
- Mac Native按当前标准命令重新执行真正的cmake --preset macos-metal配置(使用/Users/tei/Library/Android/sdk/cmake/3.31.6/bin/cmake),退出码1:Required package libuv缺失,日志保存artifacts/maplibre-particle/step116-native/configure.log。严格串行停止,未执行构建目标、未运行7项C++测试。失败是环境依赖,不应改写成代码编译通过/失败或跳过继续。
- 未执行Android Gradle、APK安装、ADB启动STEP113、新Fast计数、逐帧画面、iOS Metal回归、发行/tag/Source Lock。正式PBF缺tide以及Symbol R Placement仍保留fail-closed;此任务只是安全部分实现,并不解决原STATIC_RC无法进入Fast的全部原因。
- 用户在本轮源码scoped Git锁定且push远端核验后,需手工一次集中Android编译与install -r,再执行STEP115同HEAD/安装SHA预检、原STATIC_RC 90秒、完整120帧+24Gate+截图与其它负场景验收。新APK前禁止把旧c6d13e1产物Fast0作为本轮修复验收。若仍Fast0,必须保留缺源、Placement和C实际结果并交跨平台Core负责人确定GPU结果证书。

View File

@@ -0,0 +1,93 @@
# STEP-117 原 PBF Fast 缓存准入 Codex 完成报告 v1
日期:2026-10-11(JST)。**结果:BLOCKED。缺tide零GPU贡献与Symbol首次Build均未获得充分安全证据,保持fail-closed;已完成源码调查、原120帧复核、七项非Jest回归源码及全部中文交付。原PBF Fast目标没有完成。**
## 1. 实际模型、环境和版本
- 实际 **GPT-6.1-Sol(gpt-6.1-sol)/ high**,读取当前会话rollout `01a12912-9a0c-7712-9624-793f04b3f681` 的turn_context.model/effort确认;未升级、切换、启动子代理或第二Codex。
- Mac.lan / Darwin24.6.0 / x86_64;仓库 `/Users/tei/dev/MapLibreParticleLab`;branch `particle/main`。
- 起点/交付HEAD `43e5f5b91598b9c3d3f1c788052d4bbd2d616668`;无commit/push、无远端回读、无分支/子模块操作。起点tracked clean,三份未跟踪任务书STEP110/111/117保留。
- `platform/android/VERSION=13.6.1-particle.23`,未改公开版本/发行/Source Lock;本轮没有APK/AAR/XCFramework。
- Integration本地只读HEAD `80667d6c343411d03a760425438e3d292f452118`;只读instructions/contract等规范,无跨仓写入、同步或向他人发送消息。
## 2. 根因与停止点
原实机JSON SHA `cd491d2acca97119f1d4f2d6f77eb7b229c17b90d4be1181a59202308429b219` 已完整解析并逐帧复算。STEP116的90秒FastDelta=0、FullDelta=920是保留实测。本轮复核120帧为119Full/0Fast/1pending;#3/#4/#23各失败120、#12/#13各105、#22为0、mismatch0。原63层和C两天气层保留,R仍缺tide、Build/Reuse=0。
**缺源**:缺source没有新LayerRenderItem,但RenderLayer的停用只排队RemoveGroup;processChanges执行后才退出registry,停用并不清旧drawables/tiles/placementData。原JSON无双group/旧数据排除与成功提交代次;不能把absence登记为loaded/stable或伪造revision。保持missing-source。
**Symbol**:pre有105帧changed=false/fade非零/transition=true及15帧true/0/false,post104+15。正常Continuous重算必changed,recent必stale;duration0/禁用transition也不能建立结果证书。per-R Build及retainedSupported/preparedScene各有独立保护,layer fingerprint只含Impl指针/index,缺opacity/dynamic attributes/atlas/collision/uniform等最终GPU状态。建立完整证书需要跨Placement/Bucket/Drawable/Atlas/Renderer生命周期实现与平台证据,本轮按任务停止该部分,所有Gate保持。
调查文件给出源码链、已成立/未成立证据、反例和B1–B4协调需求。任务明确允许证据不足保持fail-closed;没有以既有烟测或测试进程代替正式目标,也没有把研究完成称为实现完成。
## 3. 实际修改文件及接口边界
修改:
1. `test/renderer/retained_underlay_cache.test.cpp`:新增4项Step117Safety,实际Controller/Placement生命周期、disabled/zero duration、缺源墓碑与恢复的cache状态机、camera/viewport/content/topology/config失效。
2. `test/renderer/render_orchestrator_error.test.cpp`:新增3项Step117Safety,实际createRenderTree/sourceDiff、registry RemoveGroup队列、Tile/Glyph/Image回调与context/style清理;仅扩展已有测试friend结构的enqueue辅助,不改生产头文件。
3. `粒子开发记录.md`:只追加STEP117记录。
新增:
1. `docs/20_调查/STEP-117_PBF_R首次缓存Build安全证据_v1.md`。
2. `docs/50_Step记录/STEP-117_原PBF_Fast缓存准入_v1.md`。
3. `docs/60_测试/STEP-117_FZG1_原PBF真实Fast验收计划_v1.md`。
4. 本报告。
生产src/include/Shader/Android/RN/JNI/API/ABI零修改;没有新增诊断字段/控制协议、源状态或新的准入bool。test/CMakeLists.txt已登记两个测试文件,无需修改构建表。原Step113PbfGateScene、原五场景/SOURCE_CHANGE、STEP116空Annotation修复、STEP102/114入口、旧测试断言均保留。没有测试App假tide,没有删除正式tide-spots或Symbol。README无运行入口变化,本轮无需修改。
## 4. 七项测试的准确状态
`Step117Safety.*` 七项全部 **NOT RUN / NOT COMPILED**,没有GTest成功日志。Controller与sourceDiff/observer/registry测试调用真实实现;空桶、无数据step117-source与两个测试group是最小生命周期fixture,不能当作原PBF像素/旧GPU资源证书。显式markBuildSucceeded仅属于cache状态机fixture,不是GPU成功;observer在测试线程注入,不代表真正异步网络并发。
尚缺完整Renderer首次Build与提交证书、跨source/跨段collision、真实glyph/atlas延迟、曾渲染后缺源的旧Drawable清理、Full/Fast像素对照和设备恢复。既有Annotations.Step116实际图像测试保留,新增tracker测试不冒充真实Annotation设备验证。
## 5. 已执行与未执行检查
已执行:前置文档和实际源码读取;本机branch/HEAD/status/submodule与Integration本地HEAD只读核对;原JSON SHA/结构/120帧逐项Gate及pre/post统计复核;静态API/变更范围、测试登记/签名/生命周期人工核对;git diff --check、中文文档和开发记录/保护文件完整性核查。最终具体收尾结果见第8节。
未执行:任何Native/CMake/Ninja/Bazel编译/链接/codegen/GTest、Gradle/APK、ADB/安装/运行/设备logcat/截图、Jest、iOS/Metal设备、性能或像素测量、commit/push/远端HEAD、发布。未观察到新编译/测试失败,因为根本未运行;不能推断会通过。历史Mac缺libuv是STEP116 RDC事实,本轮没有重跑或修依赖。
## 6. RDC接手与具体协调需求
1. RDC独立审查本报告、调查、diff及七测试。按验收计划逐项运行Mac Native定向命令,首错即停,报告实际匹配7项的数量/结果;不使用旧runner。
2. 粒子负责人协调Shared Core:缺源absent依赖绑定已应用双group排除、旧Renderable数据与成功提交代次;Symbol结果证书先影子比较,覆盖collision/opacities/vertices/atlas/uniform/camera/context/异步纪元。没有证书维持BLOCKED,不修改Gate迎合计数。
3. Integration协调正式Style/Host tide是否为真实动态注入、加载与身份语义;不在此仓库改服务器/造数据。公共责任/错误语义改变才提交RFC/ADR;内部优化可以平台中性实现,无需凭空扩大公共合同。
4. 后续受控实现完成、RDC审计与源码锁定后,按用户明确授权执行一次标准 `./tools/particle_android_validation/build_validation_apk.sh`、install -r、同HEAD/设备SHA预检、原STATIC_RC至少90秒、完整120帧24Gate与Mutation/用户画面验收。没有生产改动时重建本轮测试源码不能解决Fast0。
精确命令与证据矩阵在 `docs/60_测试/STEP-117_FZG1_原PBF真实Fast验收计划_v1.md`。不由Codex运行,不以STEP114 FAST_SMOKE代替。Native编译与测试、Android SHA、真实Build/Reuse/Fast、用户视觉和安全回退分别判定,不把单项成功写成全任务PASS。
## 7. Git/制品待回填项
本轮不commit/push,提交message/hash不适用;远端HEAD未核验。RDC若后续获授权,只按第3节七个明确任务路径scoped提交(任务书是否纳入另按授权),不得包含STEP110/111、其他dirty/untracked或Git忽略JSON。普通push后回读 `origin refs/heads/particle/main` 并与实际提交核对。
Native runner来源/日志/exit/耗时、Android构建源码HEAD/制品SHA/设备SHA、独立Fast/视觉验收均为“待RDC回填”,不提前填成功或借用旧c48a72a9来源。
## 8. 静态收尾结果
- `git diff --check`退出0,无空白诊断。tracked差异只含第3节两个测试文件和开发记录,新增356行、删除0行;四份新文档逐个执行`git diff --no-index --check /dev/null <明确路径>`,无空白诊断,exit1仅表示与空文件有差异。
- 七项`TEST(Step117Safety, ...)`源码名称唯一,两个文件在现有test/CMakeLists.txt登记;原文件diff只有插入,原测试代码及断言没有删除或替换。人工核对当前UpdateParameters成员顺序、Placement/observer/sourceDiff相关声明,以及测试source销毁前detach与排空scheduler;未作编译,因此不能据此保证测试可执行。
- 生产Renderer/Orchestrator/Placement/particle_fast_path/render_segment_json/render_segment公开头、AnnotationManager和STEP113/114场景与HEAD字节相同。原Step113PbfGateScene SHA=`d50281713ae551edc9a48b1550254602e4950c9ddca66855bb689620c9f6f2b6`。全部tracked修改路径不含src/include、公共API/ABI或Android;24Gate、计数与正式场景保持。
- 开发记录原HEAD的167,048字节前缀完整,原SHA=`8544702389dab5d829afe145dd5e7cd23083992a887b5fc4857a8b57f468cf8a`;本轮仅追加2,252字节/10行。
- STEP110未跟踪MD SHA=`33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0`;STEP111=`f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4`,保持原值且仍未跟踪;暂存区为空。
- 四份新文档中文正文/成对代码围栏/空白核对完成;验收计划内Python只做AST解析,没有运行其中命令。原274,693字节JSON SHA未变、仍Git忽略,120帧24Gate失败数逐帧与聚合相符,pre/post所有R的Build/Reuse=0、placementGatePassed=false。
- 静态审计脚本曾分别因Git中文路径转义和pre/post中cache字段为扁平序列化而中止。随后改为`--name-only -z`读取真实路径,并按原JSON扁平build/reuse字段复核;没有修改证据、测试断言或验收阈值。修正后的范围/证据审计完成。这两次是审计读取错误,不是C++编译/测试结果。
- 终态HEAD保持`43e5f5b91598b9c3d3f1c788052d4bbd2d616668`;status只有三个tracked修改、四份新交付文档及起点三份未跟踪任务书。未commit/push,独立RDC验收尚未开始。
以上只证明静态范围与文件完整性。安全准入仍BLOCKED,Native/Android测试未运行,真实Fast/像素/性能目标没有通过。
## RDC独立核验附录(2026-10-11)
- RDC先核对Integration main=80667d6c343411d03a760425438e3d292f452118、Particle当前particle/main=43e5f5b91598b9c3d3f1c788052d4bbd2d616668;源码与git已跟踪改动起点为空,仅两个用户保护未跟踪任务书。
- 用户本轮要求继续真实STEP113 PBF Fast优化,Codex实际GPT-6.1-Sol High。调查原STEP116实机120帧JSON,缺tide Source以及Symbol placement重算/stale循环仍无法安全创建首次GPU缓存;已有证据只能证明无新的LayerRenderItem,不能证明旧GPU drawables/已经应用的双LayerGroup删除。保留既有24Gate/生产Core,安全准入BLOCKED。
- Codex新增7个Step117Safety C++测试源码,分布于两个既有登记的test/renderer文件;RDC独立对两个最终文件以当前Android OpenGL/x86_64的NDK clang++、真实compile_commands.json C++20 flags和googletest头文件进行-fsyntax-only。retained_underlay_cache.test.cpp exit0(6.3s),render_orchestrator_error.test.cpp exit0(5.3s),RDC检查结果PASS。语法检查日志存Git忽略artifacts/maplibre-particle/step117-native-syntax/*.log。
- RDC核查原生产src/include/Renderer/Placement/Fast Gate、Android STEP113/114及Source/style均无文件改动,git diff --check通过。故**没有新的运行时改动供Android APK验证**:重复编译/安装STEP116相同行为的APK不能使Build/Reuse/Fast改善,未触发第二次高成本Android周期。GTest链接运行、GPU像素等价、iOS/Metal、真实Fast目标仍NOT RUN/BLOCKED,不能将NDK语法退出0标为GTest PASS。
- Integration正式现有RFC/ADR/Contract/status/coordination未包含所需R Symbol已提交GPU结果证书。B1缺源零贡献、B2 Symbol跨层结果证书与影子验收、B3真实tide Source身份/宿主动态注入语义须由粒子负责人/Integration按职责协调设计;本执行项目不得自行改变Shared Core公共契约或正式Style。
- 七项测试当前仍是源码语法检查级别,不等于实测; 下一步应先由粒子负责人协调Shared Core结果证书及合法真实tide来源,受控实现后再使用原STEP113 STATIC_RC APK复测。任意无法安全证明的条件继续fail-closed。
### RDC 严格 -Werror 编译参数复核修正(2026-10-11)
第一次NDK -fsyntax-only按项目真实C++20参数编译两个测试文件均exit0,但测试辅助函数遗漏可选retainedUnderlayState字段初始化,产生一条 -Wmissing-designated-field-initializers 警告;第一次探测曾附加 -Wno-error,不能直接标作正式 -Werror 验收。RDC后续对 test/renderer/retained_underlay_cache.test.cpp 增加一行显式 retainedUnderlayState={},保留原逻辑,然后从临时核验脚本中移除 -Wno-error,严格复用Android NDK OpenGL/x86_64真实compile_commands.json的 -Werror 及C++20参数重新执行两文件 -fsyntax-only:retained_underlay_cache.test.cpp退出0/5.3秒,render_orchestrator_error.test.cpp退出0/5.2秒;对应Git忽略日志均无警告输出。**严格语法检查PASS;GTest链接运行仍NOT RUN,Symbol GPU证书及Fast仍BLOCKED。**修正仅测试辅助字段初始化,不涉及生产src/renderer、Android APK和正式Gate。

View File

@@ -0,0 +1,12 @@
# STEP-119 Android 真机 Fast 首次通过阶段报告
日期:2026-10-11 JST。结论:FAST_OBSERVED / PARTIAL;不是最终全面PASS。
- 源码APK Commit:3d989a00a822989339a6d7cb4d23acc04396670e;Android OpenGL/x86_64完整Gradle Native编译+链接+打包BUILD SUCCESS;已对9个C++生产/测试单元做NDK严格-Werror语法检查PASS,发现并修复两处命名空间及测试Mutable/Immutable构造错误。
- APK SHA-256:57d68800770d431a2ce9247406f636e7a01cc19fccaf4c59f5eb3a7c99128135;APK大小66702125字节。FZG1-2(ADB 192.168.200.139:5555)安装Success且已安装base.apk SHA一致;预检PASS。
- 实测STATIC_RC_NO_TIDE(TestApp独立场景,不修改正式Style),原63基础层有效62+天气2=Native64;R[0,62)、missingSourceIds为空;场景有效。
- 同一PID=27241,真实观察90.010秒:FastDelta=1819,FullDelta=0;最近120帧Fast=119,Full=0,decisionMismatch=0。
- R缓存Build累计=1,Reuse累计=3276,placementGatePassed=True。复用增量=1819。静态结果首次真机Build、Reuse、Fast均已达到>0目标。
- 原始设备JSON:artifacts/maplibre-particle/android-validation/step113-retest-20261011_170012/static_rc_no_tide/result.json;SHA256=8718ce113820e7cc0cc2b4d5b801e6847c92e9ced79cd4fecbee58bfeef1c27c。旧STEP118真实JSON与APK保留,未覆盖。
- 其他未验:原63层缺源负对照;Camera/Style/Source/Tile/Annotation/Context变更后的Full安全撤销与重新Build;航标/文字图像与层序人工目视;13项非Jest GTest实际运行;macOS Metal/iOS。Mac Metal CMake因既有libuv缺失未配置(非新增依赖),Android链路使用原工具成功。
- 不能凭Fast计数判断静态图像和Symbol结果等价;STEP119保持PARTIAL,不可直接发行或宣称全部PASS。

View File

@@ -0,0 +1,64 @@
# STEP-119 RCRR 多场景局限性阶段验收报告 v1
日期:2026-10-11 JST
## 一、目标与结论
**结论:部分可行/阶段 PARTIAL。** Android 真机已证明一个原始四段 RCRR 场景能让后两段 R 分别 Build=1、Reuse=536;但最前 R 缺失 `tide`,同时宽 C 不满足当前 Fast 场景约束,整帧持续 Full。已新增三种区分 C 宽度及错误动态 R 的 Test App 对照入口,并据设备真实 Style 63 层清单完成静态区间核对。新增入口仍需用户手工编译 APK 后实测,不能宣称三种 GPU 场景 PASS;STEP-119 不 CLOSED。
## 二、实际基线和执行链
- RDC 设备:Mac.lan;Mac 仓库 `/Users/tei/dev/MapLibreParticleLab`,当前分支 `particle/main`,本轮起点 HEAD `46c88b0209474376225b54ae0ad701881673743d`。
- Integration `main` 已通过 RDC `git fetch` + `git merge --ff-only`,本地/远端一致 `80667d6c343411d03a760425438e3d292f452118`;已读取 `PARTICLE_PROJECT_INSTRUCTIONS.md`、README、WORKFLOW、TEAM_MODEL、contracts/rfc/adr/status/compatibility、项目 AGENTS/规范/总则。
- APK 源码锁:`3d989a00a822989339a6d7cb4d23acc04396670e`,SHA-256:`57d68800770d431a2ce9247406f636e7a01cc19fccaf4c59f5eb3a7c99128135`,ADB:`192.168.200.139:5555`。此次没有重复 Android 编译/安装。源 HEAD 与已安装 APK 的差异由先前文档提交构成;新测试源码改动后,后续新场景须重新进行用户手工 APK 构建、SHA 锁定。
- 既有 `STATIC_RC_NO_TIDE` 正向 FastDelta=1819、FullDelta=0、Build=1、Reuse=3276 和原 `STATIC_RC` 63 层缺源负向 Fast=0/Full=305,均为已验收历史证据,没有重复执行。
## 三、已执行真机 RCRR 和证据
`PARTIAL_RCRR` 原场景运行 80.007 秒、PID=28169,3 个完整 Native 摘要,设备 SHA 一致。Native 总数 66;R[0,3) / C[3,39) / R[39,64) / R[64,66)。
| 指标 | 实际结果 |
|---|---|
| R0 | Build=0 / Reuse=0 / missingSourceIds=[tide] |
| R2 | Build=1 / Reuse=536 |
| R3 | Build=1 / Reuse=536 |
| 80 秒增量 | Fast=0 / Full=596 |
| 最近 120 帧 | Fast=0 / Full=119 |
| decisionMismatch | 0 |
| 24 Gate 累计 | unsupported-continuous-layer=599、unsupported-scene=599、cache-invalid=595 |
执行进程退出码 0,表示**原生观测及取证成功**,不表示 RCRR Fast 成功。证据路径:
`artifacts/maplibre-particle/android-validation/step119-rcrr-observe-20261011_172720/partial_rcrr/result.json`、`logcat.txt`、`screen.png`。
### 关键事实校正
从设备 Debug App 的 `step113-STATIC_RC-324220201.json` 只读提取 `styleParity.baseLayerManifest`,正式 63 层索引:`tide-spots=2`、`sea-area-fill=3`、`clip-outline=31`、`special-pattern-line-754=37`。其生产 Style 指纹与当日 Fast 成功场景一致。原 R0 只有 background / base-raster / tide-spots;**天气在 C 内而不是 R0**。原 C 范围很宽,混合了普通海图图层和天气动态层。此前对 R0 图层组成的初步猜测已经被真实 Manifest 修正,未写入最终推荐结论。
## 四、新增三种 RCRR Test App 入口(未上机)
- `RCRR_C_NARROW_NO_TIDE`:R[0,31) / C[31,33) / R[33,63) / R[63,64),C 只包含两天气层。用于验证全段缓存和最终 Fast 的可能性。
- `RCRR_C_WIDE_NO_TIDE`:R[0,2) / C[2,38) / R[38,63) / R[63,64),C 混合天气与静态 PBF。用于验证白名单边界和宽 C 的实际代价。
- `RCRR_R_DYNAMIC_NO_TIDE`:R[0,33) / C[33,38) / R[38,63) / R[63,64),天气被放入错误 R。用于验证失效与 Full 安全拒绝。
代码变更严格局限于 Test App `Step113PbfGateScene.kt`、`verify_step113_fast_path.py` 和新增 `test_step119_rcrr_scenarios.py`,保留原所有场景。生产 PBF63 的定义、服务器 URL、天气实际字段及图层相对顺序不变;只在明确 NO_TIDE 测试副本排除已确认的唯一孤立层。对新场景增加精确 R 范围匹配,不降低原验证器 24 Gate 或 FAST_OBSERVED 条件。
源级校验:设备实际 Manifest 分段结构 3/3 与预期一致;Python unittest 4/4 PASS;`git diff --check` PASS。没有 Android Kotlin/NDK 编译或新 APK/设备验收,**三种新增场景为 SOURCE_READY / NOT VERIFIED**。
## 五、当前 RCRR 模式的局限性
1. **全帧 Fast 是全局准入。** R2/R3 的 Cache 能各自 Build/Reuse,但 R0 缺源时整帧仍 Full;不能按各 R Reuse 求和冒充 Fast。
2. **连续 C 不是任意图层的免责通道。** 原宽 C 下 `unsupported-continuous-layer` 持续增加,说明当前 Fast 路径不支持直接把大量普通海图层划入 C 并期望跳过完整场景处理。需要以新窄 C 实测来确认适用边界。
3. **配置高度依赖真实 Style 顺序。** 同一个 layerId 相对位置设错可能造成循环 C 混入静态层或天气落入 R;实际 PBF 63 层与 NavSea 113 层现场定义不同,不能按历史索引照搬。
4. **缓存分段独立性有限。** 即使 R 分段有缓存,Source/Style/Tile/Symbol Placement/Atlas/Opacity/Annotation 的全局结果版本可能要求跨段失效;当前 13 项源级 C++ 测试尚未真正 GTest 运行,相关安全边界待进一步真机验证。
5. **性能与显存不是必然收益。** 多个 R RenderTarget/Composite 会增大缓存资源与混合成本;当前没有同场景 RC/RCRR 同相机有效帧时间和 GPU 内存对照,不能声称多段比单 R 更快。
6. **视觉与平台仍未关闭。** 尚无强制 Full/RCRR 同 Style 静态航标、Symbol、文字、层序的人工确认;Metal CMake 仍因既有 libuv 缺失未配置成功,13 GTest 与 iOS 真机没有新验证。
## 六、风险与下一步
建议以**窄 C、其余静态 R**作为 RCRR 正向候选,保留原 63 层 RCRR 和两个新负向变体。下一次由用户按既有 Mac 标准手工只进行一轮最新源码 APK 构建后,RDC 串行执行三场景各 90 秒真实诊断和同视角视觉对照;若窄 C 仍被 unsupported gate 拒绝,应保留 Full、检查准确原因和局限边界,而不是修改 Gate 放行。
状态为 `ANDROID_RCRR_FAIL_CLOSED_OBSERVED / MULTI_RCRR_SOURCE_READY / OVERALL_PARTIAL`。更详细命令、矩阵和预期证据位于 `docs/60_测试/STEP-119_RCRR多配置验收矩阵_v1.md`。本次不是 GSRL 正式发行,没有 SOURCE_LOCK、发布 Tag 或版本覆盖;未跟踪 STEP-110/111 文档禁止纳入提交。
## 七、Git 收尾
本段由 RDC 在完成 scoped commit/push 与远端回读后补写最终提交、Push 与远端 HEAD;在此之前不得声称 Git 收尾通过。

View File

@@ -0,0 +1,65 @@
# STEP-119 RCRR 粒子独占 C 与 CPU 实机对照阶段报告 v2
日期:2026-10-11(JST)。实际执行:Mac.lan RDC,Android FZG1-2 x86_64 / `192.168.200.139:5555`,后端 OpenGL。**本报告为真实 Android 固定相机稳态验证,不是 STEP-119 全量 CLOSED/PASS。**
## 一、执行结论
**粒子独占 C 的 RCR、RCRR、RCRRR 三种分段均已在 Native 真正执行 FastPath(观测窗口 Fast 100%);同一 PBF/天气场/相机条件下,两个 R 段的 RCR 平均 CPU 工作量显著低于三 R/四 R。** 宽 C 混入海图或 Particle 放入 R 的两项负对照均安全拒绝 Fast,24 Gate 没有误判。
CPU 为 Android App **整个进程**的 utime+stime,不是某个 Native render() 调用的精确耗时;`CPU毫秒/帧`是实测进程 CPU 核秒/秒除以同段 Native 帧率的估算值,**不证明 GPU、功耗、动画视觉等价或动态天气切换安全。**
## 二、来源锁与步骤执行返回
- Integration `main` 通过 Mac RDC `fetch + --ff-only`,HEAD `80667d6c343411d03a760425438e3d292f452118`。
- Particle 分支 `particle/main`,本次真机 APK 对应源码 Commit:`32ac795fc4abd04006673bf644f87a711329e78f`,完整 SHA256:`2bbd1cb4dc9e03ec56d8404363970e56f76b03819f6f2b50c8aee6dbb6fa43d2`。
- **构建 PASS,exit0**:RDC 经用户本轮明确授权在 Mac 执行 `build_validation_apk.sh`,Java17、Android x86_64 OpenGL Kotlin/Native Build Success;APK 66,702,127 bytes,libmaplibre.so 40,138,024 bytes。只是现有弃用 API 警告。
- **传输/安装 PASS**:`adb install -r` 因网络慢无终态而终止,非编译失败;改为临时 Mac LAN HTTP→设备 `curl` 下载无损 gzip(31,582,202 bytes)→SHA-256校验→设备解压成原 APK再校验→`pm install -r` 返回 `Success` exit0。临时 HTTP 已停止。最终 `verify_step113_fast_path.py --preflight-only` exit0:源码 HEAD、build-info、Mac APK SHA、已安装设备 base.apk SHA、x86_64 全匹配。
- **采样器第一次试跑 BLOCKED,exit3**:ADB `sh -c` 参数转义阻塞读取进程 /proc;已停止,不计 RCRR 缺陷。临时忽略目录的测量器按同一 APK 修复为单个 ADB shell 命令,并在温机期保存日志避免 logcat 轮转;原记录1次 `SUMMARY_ALIGNMENT_LIMIT` exit2,明确排除 CPU 比较。
- **有效 CPU Benchmark 8 次,exit0**:2次 Full、3次 RCRR、2次 RCR、1次 RCRRR。均同一 Commit/APK SHA/正式Style指纹 `e0f9c2d1df38ae5d5953b561c52efe2f8590336de80b988e1d2935580d30b996`;每次 24 Gate、decisionMismatch 0、Native/CPU 时间边界误差≤9秒、Lifecycle不变、真实分段/无 tide / 空 Annotation 受控。
- **负例各 90 秒观察,命令 exit2(预期负例状态),安全验收 PASS**:`RCRR_C_WIDE_NO_TIDE` FastDelta0 FullDelta339,`unsupported-continuous-layer`;`RCRR_R_DYNAMIC_NO_TIDE` FastDelta0 FullDelta322,`unsupported-continuous-layer`+动态粒子 R0 的反复 `static-unstable/cache-invalid`;两项 `decisionMismatch=0`、24 Gate完整。此 exit2 是严格验证器的 `NO_FAST_OBSERVED`,**不是 GPU 测试崩溃**。
- Python 现有非 Jest 合约/CPU比较测试 **12项 PASS**;修正后的正式 CPU 工具源代码仅为测试 Python(不改 Native/Test APK)。使用原 APK 的实测仍以旧源码 SHA `32ac...` 为准;仓库后续 Git 文档/测试器提交不倒写至 APK 来源。
## 三、实测 CPU / Fast 帧效率
每次独立冷启动,35秒 warmup,再约90秒稳定 CPU 采样,间隔6秒。相机固定(33.45,129.97、zoom10.5、bearing0、tilt0),场输入/forecast暂停/目标 FPS设置相同;Native FPS 实际值不等于目标 FPS,不得使用设定值算 CPUms/帧。
| 模式 | R数量 | 有效轮数 | Native FPS(实测) | 进程 CPU / 单核 | 估算 CPU ms/完成帧 |
|---|---:|---:|---|---|---|
| Full,缓存关闭 | 3 | 2 | 9.98、10.61 | 61.6%、63.3% | 61.74、59.61;中位 60.67 |
| RCR,Particle独占C | 2 | 2 | 59.67、59.40 | 19.7%、22.0% | 3.30、3.70;中位 **3.50** |
| RCRR,Particle独占C | 3 | 3 | 53.93、55.03、54.22 | 37.6%、38.7%、39.9% | 6.96、7.04、7.37;中位 **7.04** |
| RCRRR,Particle独占C | 4 | 1 | 48.94 | 38.3% | **7.82** |
对先 Full → Fast RCRR → 后 Full 的严格同源 A-B-A:
- Full CPU ms/帧基线取中位 **60.6716**,RCRR Fast **7.0396**:估算降低 **88.40%**;Fast比率100%;Full前后 CPU/秒漂移 **2.70%**,未达15%复测阈值。
- 同基线计算 RCR **3.2996**:估算降低 **94.56%**;RCRRR **7.8212**:估算降低 **87.11%**。RCR/RCRRR 不在严格 Full-Fast-Full 的相邻三次中,属于同 Style/设备基准横向对照,不能赋予与 A-B-A 完全相同的控制力度。
- RCR 3.30/3.70 与 RCRR 7.04/7.37 的交叉重复稳定支持**本场景两 R 优于三 R**。不能据此把差距直接归因为 FBO 合成或强称“R数越多必然越慢”:四 R 一次 CPU/秒略低于三 R,且缺少 GPU 渲染链 Profiling。
- Fast组全部在稳定观察窗口 `FullDelta=0`、`FastFraction=1.0`、`windowCount=120`、最后120帧 `Fast=119/Full=0`,每个 R 原先真实 Build1,观测窗口内 `BuildDelta=0、InvalidateDelta=0、Reuse大量增长`。这验证**稳态** Cache Reuse,不验证主动变更后安全重建。
## 四、证据坐标
Git 忽略的本地设备证据目录:
`artifacts/maplibre-particle/android-validation/step119-rcrr-cpu/`
- A-B-A 综合 `compare-20261011_193224.json`。
- 包含全部三种 Fast 结构的比较 `compare-20261011_193724.json`。
- Full 有效:`20261011_184647-rcrr_full_control_no_tide/result.json`、`20261011_193006-rcrr_full_control_no_tide/result.json`。
- RCR:`20261011_193242-rcr_particle_only_no_tide/result.json`、`20261011_194206-rcr_particle_only_no_tide/result.json`。
- RCRR:`20261011_184406-rcrr_c_narrow_no_tide/result.json`、`20261011_192746-rcrr_c_narrow_no_tide/result.json`、`20261011_194427-rcrr_c_narrow_no_tide/result.json`。
- RCRRR:`20261011_193504-rcrrr_particle_only_no_tide/result.json`。
- 无效首轮 Full(仅保留排障):`20261011_184029-rcrr_full_control_no_tide/result.json`。
- 负对照:`artifacts/maplibre-particle/android-validation/step113-retest-20261011_193754/rcrr_c_wide_no_tide/result.json`、`step113-retest-20261011_194000/rcrr_r_dynamic_no_tide/result.json`。
原始数据/Logcat 没有加入 Git,以上路径仅在执行 Mac 本地工作树中。正式报告记录事实和 Hash,**不能把本报告当作已发布或 Source-Locked APK**。
## 五、性能和架构局限 / 后续验收
1. **C 单独 Particle 是当前强准入边界**:宽 C 含普通海图会拒绝 Fast;把实时粒子错放 R 会频繁失效。不得为跑分降低24 Gate。
2. **R 数量不是越多越好**:本次两 R 性能最好,RCRR/RCRRR 受更多缓存、RenderTarget和合成/上传等成本影响的原因尚需 CPU/GPU 分项 Profiling,不得越证据结论。
3. **WeatherColor 在 R**:固定 forecast 的稳态 Build1/Reuse 大量通过;field 更新、插值时间、颜色表、可见度、Opacity、Source/Tile变化是否触发正确的失效、Full、重 Build/Reuse,**未验证**。
4. **视觉等价未通过**:航标、字体、跨段 Symbol Placement/遮挡、层次和色彩需同视角 Full/RC/RCR/RCRR 人工真实屏幕验收;尤其两 R 不允许以牺牲 Symbol 语义换速度。
5. **动态地图与错误恢复未通过**:Camera移动缩放旋转、Style、Source、Tile、Annotation、Context丢失以及反复切换的 Full→Fast恢复、无旧图闪现,未经过系统测试。
6. **跨平台与低层证明未通过**:Mac Metal/iOS、13项 STEP119新增 C++ GTest 真跑、GPU 时间/温度/电量未确认。Android APK 为测试 Debug x86_64,不能外推产品 ABI 或真机温度。
7. **长期运行/网络压力未通过**:设备网络传输约 48KiB/s,使用 gzip+LAN HTTP转移大APK完成安装;不能把安装网络瓶颈归咎于 FastPath,也不能用静态场证明运行稳定性。
阶段裁定:`ANDROID_RCRR_STEADY_FAST_CPU_PASS / TWO_NEGATIVES_FAIL_CLOSED_PASS / STEP119_PARTIAL`。建议后续优先 RCR 两 R 方案做动态/视觉/生命周期长时间测试,**不修改正式生产配置,不在此报告直接决定采用**。

View File

@@ -0,0 +1,128 @@
# STEP-119 SymbolRetained 生命周期修复 Codex 完成报告 v1
日期:2026-10-12(JST)。状态:**IMPLEMENTED / WAITING_RDC_VALIDATION**。接续此前因用量限制中断的同一任务,四个既有修改经静态复审及明确缺口修正后交付。没有运行编译或测试,整体 STEP-119 仍未验收,不报告整体 PASS。
## 1. 模型、环境与 Git 基线
- 用户本轮明确指定 `GPT-6.1 Sol High`,即 `gpt-6.1-sol / high`。当前工具没有执行器模型/推理参数元数据;轻量查找 `/Users/tei/.codex/sessions/2026/10/12` 返回目录不存在,实际运行参数无法独立核验。记录用户指定值,不把它写成已从执行器日志核实。未主动升级模型、启动第二实例或子代理。
- `Mac.lan`,Darwin 24.6.0,x86_64;目录 `/Users/tei/dev/MapLibreParticleLab`。
- 开工/交付 branch=`particle/main`,HEAD=`badb515d590fbdf221751b6677de3da0a5650d7e`。基线为四个已有 tracked 修改及 STEP-110/111 两份 untracked 任务书;不从头复写,不改分支/worktree/HEAD。
- `git submodule status` 已只读检查,没有变更前缀;无子模块修改。未 commit/push/tag/fetch/pull/rebase/reset/clean,未回读远端 HEAD(本轮禁止远端操作)。
- 只读当前 v25 Source Lock:Android/RN `13.6.1-particle.25`,iOS `6.29.0-particle.25`,冻结构建源 `f32f6f45fd359007e8b3c77911b66d9e7fc93848`。发行记录与制品未修改,本轮未生成制品,不能以现有 v25 包验证本工作区修复。
- 首先读取 Astra 原文 `/tmp/fzg1_astra_guidance_20261012.md`,再读取仓库规则、当前资料和现有 diff。当前用户续作授权优先于旧任务中的历史基线/跨项目调查和 Git 收尾安排。
## 2. 实际修改文件及生命周期不变量
| 文件 | 实际变更与不变量 |
|---|---|
| `src/mln/gfx/symbol_drawable_data.hpp` | 内部数据保存强类型 atlas 与实际原生 atlas tweaker;创建时两者来自同一份 atlas,增加内部 Drawable 类型标记。它们不拥有 RenderTile 或底层 Tile。 |
| `src/mln/renderer/layers/render_symbol_layer.hpp` | 私有同源/绑定/整组复用/采集辅助方法及当前地址→ID/atlas 映射;仅保存当前代次,不建立历史容器列表。friend 测试入口不修改公共 API。 |
| `src/mln/renderer/layers/render_symbol_layer.cpp` | prepare 从同次 source 获取 raw owner 与原 Y 排序列表,先校验成员地址再读 ID/atlas,保留 fading tile。成功复用先检查 tile/pass 整组,再全部刷新当前 RenderTile/owner;Bucket、atlas、layer tweaker、数据/类型或成员失配整组移除重建,atlas 缺失移除旧绘制。Retained 从强引用 atlas 取证,旧指针只作当前地址映射查找,使用 Drawable 自己的 ID;类型未知/绑定不符/未上传立即拒绝。owner 只放入 resources,不序列化 owner 地址。 |
| `test/renderer/retained_underlay_cache.test.cpp` | 13 项 `SymbolRetainedLifetime` 源码调用生产 prepare/bind/rebind/retained 辅助路径与原生 atlas tweaker;真实类接口/签名和友元访问逐项静态核对。atlas-only TileRenderData 显式覆盖空 Bucket/LayerRenderData 查询,避免基类 assert;补组内类型失配、Text/IconRGBA/IconSDF/Halo 稳定身份、未知类型及证书释放检查。 |
| `docs/50_Step记录/STEP-119_SymbolRetained生命周期修复_v1.md`(新增) | 本阶段目标、基线、完成/待验清单及交接。 |
| `docs/报告/STEP-119_SymbolRetained生命周期修复_Codex完成报告_v1.md`(新增) | 本报告,记录事实、准确命令、风险和停止条件。 |
| `粒子开发记录.md` | 仅末尾追加本次实现与未验证事实,保留历史内容。 |
两个新文档路径创建前已确认不存在,同主题无旧版本,选择 v1;使用独占创建,未覆盖旧报告。前三个生产文件及测试均沿用开工时修改;本续作生产补丁仅增加 Retained 对未知 SymbolType 的拒绝,测试补丁只处理上述明确缺口及覆盖不足。
### 所有权与稳定性
1. Drawable 只由通过同源校验的 owner/元素绑定。复用检查不从旧 RenderTile 读取 ID/Bucket/atlas;任何一个 Text/Icon/Halo 成员不兼容时整组移除,避免 `updateTile()` 的“任一成功”留下半新半旧组。
2. 同 Bucket/atlas 保持 Drawable 和 tweaker 身份,仅刷新 owner/RenderTile 与既有动态属性;source 每次产生新容器不应自行造成每帧重建或证书字节变化。
3. 当前容器拥有 RenderTile;RenderTile 独占 renderData;真实 GeometryTileRenderData 强持有 LayoutResult/atlas,LayoutResult 负责槽位释放。证据 resources 保活容器,但不调用 `result.retain(renderTilesOwner)`,因此 owner 地址不进入比较字节。证书 candidate/completed 替换或 invalidate 后旧证据可退出,未新增无界容器。
4. Retained 先验证当前地址/保存的 ID、内部数据类型、atlas 来源、实际 tweaker 和 shader,再检查真实 glyph/icon 纹理绑定及上传状态。任何缺证立即返回 incomplete,不沿失效裸指针追读 renderData/atlas。
5. RenderTile 内 `Tile&` 的寿命独立;保活容器不保活 Tile,不允许把旧 Tile 上的 getOverscaledTileID/holdForFade 当成安全操作。测试分别计数 Tile 与 TileRenderData,旧 Tile 销毁后仅比较旧地址及读取 atlas 强引用。
6. 同 atlas 的 texture 替换经原生 tweaker 更新实际绑定,前后证据不同;原地像素更新即使上传后指针不变,仍依赖真实 content epoch。既有 Build 前/后及提交后复核、证书版本/R cache/prepared Fast 保护保留,未改变准入顺序或放宽任何判据。
## 3. 静态审查与测试源码范围
13 项生命周期源码覆盖:连续 prepare 后刷新/释放旧 renderData、owner/元素错配、atlas 替换整组清理与新纹理绑定、组内单成员失配、atlas 缺失、同 atlas texture 更换及重新 Build/Reuse 状态、未上传原地更新拒绝、owner 身份不进入完整空组结果字节、Tile 与 renderData 寿命分离、Layer 移除及证书失效释放 atlas、同名 Source 替换、tile 退出清理、Text/Icon/SDF/Halo 连续三代保持身份、未知 Symbol 类型拒绝(其中相关情形合并在同一用例)。
测试使用真实 RenderSource/Tile/TileRenderData/RenderTile、SymbolBucket、Drawable/TileLayerGroup 接口和生产辅助方法;GPU Texture/Drawable/Shader 使用 CPU 替身。真实 API 的纯虚函数、构造参数、Mutable→Immutable 转换、SymbolLayer.baseImpl 可见性及派生类 friend 访问继承 protected 成员已静态核对,没有用悬空指针解引用构造普通反例。
**覆盖限制**:atlas-only fixture 不生成真实 Placement/顶点/GPU 命令;atlasResult 是生产采集的局部证据,不能代表完整渲染证书。整组失配测试走生产移除入口,但重新创建由 fixture 添加 Drawable,仍需 RDC 实际 update/builder/GPU 链验证。tile 退出测试直接调用正常 update 使用的清理入口,没有伪造 Context 或声称执行完整 update。owner 稳定证书测试走完整生产空组采集;非空 Text/Icon/Halo 稳定性只检查绑定输入和身份。原地 image patch 上传后的 epoch、Style/context 清理引用既有 `Step119SymbolResults.RealTileGlyphImageStyleContextCallbacksRejectBuiltCertificate` 等回调测试源码,不等同真实槽位释放、Source/Style/context 完整运行验收。
## 4. 已做及未做检查
已做:必要文件阅读、开工 Git/机器/版本及 diff 审查;真实 C++ 接口/生命周期调用静态核对;确认测试文件在 `test/CMakeLists.txt` 已登记;源码静态计数 `SymbolRetainedLifetime` 为13项;新增文档版本/路径检查;STEP-110/111 SHA-256 保护检查;开发记录追加与文件范围检查。
已执行四源码路径 scoped `git diff --check -- src/mln/gfx/symbol_drawable_data.hpp src/mln/renderer/layers/render_symbol_layer.cpp src/mln/renderer/layers/render_symbol_layer.hpp test/renderer/retained_underlay_cache.test.cpp`:**exit=0,stdout/stderr 空**。最终全工作区及七任务路径空白检查由本轮落盘后再次执行,结果见下方交付记录。空白检查不证明 C++ 编译或测试通过。
未执行:CMake 配置、Native/NDK 编译、`-fsyntax-only`、链接、GTest、Metal/iOS/GPU运行、Gradle/APK、ADB/安装、Jest、Bazel/codegen、发行、Git写操作及远端读取。没有新编译失败日志、设备日志、截图、APK/SO SHA 或 Build/Reuse/Fast 数据;既有 libuv 环境失败只作为历史限制,未重试也未修改环境。
保护的未跟踪任务书 SHA-256:
- STEP-110:`33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0`。
- STEP-111:`f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4`。
## 5. RDC 准确编译与定向测试命令(Codex 未执行)
独立审查本报告和实际 diff 后,在同一工作区逐条执行;每条保存 command/exit/耗时/完整日志路径,失败立即停止下一步,回传首个真实 error、target 与必要上下文。PATH 找不到 cmake 时可使用既有 `/usr/local/opt/cmake/bin/cmake`;不得安装升级依赖或关闭宏绕过 libuv/工具链错误。
```bash
cd /Users/tei/dev/MapLibreParticleLab
cmake --preset macos-metal
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
build-macos-metal/mbgl-test-runner --gtest_filter='SymbolRetainedLifetime.*:Step119SymbolResults.*' --gtest_list_tests
build-macos-metal/mbgl-test-runner --gtest_filter='SymbolRetainedLifetime.*:Step119SymbolResults.*:RetainedUnderlayCache.*:RenderOrchestratorError.*:RenderOrchestratorTeardown.*:CrossTileSymbolLayerIndex.*' --gtest_color=no
```
必须发现本轮13项 `SymbolRetainedLifetime` 与既有13项 `Step119SymbolResults`(后者分布两个已登记文件),真实执行数量非零。零匹配、旧 runner、仅语法检查不能写 GTest PASS。Metal 与 Android OpenGL 分别保留编译及运行事实;Android 实际 compile_commands 的类型核对须保持原 `-Werror`,不加 `-Wno-error`。本轮未生成额外编译脚本。
Vulkan 至少取得生产修改的兼容性编译证据;已有 macOS Vulkan 环境可用以下真实 preset/target,环境缺失则记未验证/阻塞,不升级环境:
```bash
cmake --preset macos-vulkan
cmake --build build-macos-vulkan --target mbgl-core --parallel 3
```
Vulkan Symbol provenance 既有保守边界保持,编译成功不等于其 Symbol retained 运行通过。可在既有 sanitizer 配置下补查 UAF,不改造构建体系;Host Metal/GTest 也不替代 iOS 真机。
## 6. 后续 FZ-G1 验收命令与证据(Codex 未执行)
**前置门槛**:独立审计/Native验证完成、后续 Git 来源锁和 Android/设备操作获得授权。当前 tracked dirty,verifier 的 `SOURCE_NOT_LOCKED` 会拒绝本工作区;本轮禁止提交,故这是后续验收前置条件,不能改 verifier、伪造 build-info 或用旧 APK。本报告仅交命令,不代执行 Android/ADB,也不代授权 Git。
用户/RDC 按其阶段授权完成一次标准新 APK 构建与安装,固定 FZG1-2、serial=`192.168.200.139:5555`、OpenGL Debug/x86_64:
```bash
cd /Users/tei/dev/MapLibreParticleLab
./tools/particle_android_validation/build_validation_apk.sh
adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --preflight-only
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario STATIC_RC_NO_TIDE --seconds 90
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario RCRR_C_NARROW_NO_TIDE --seconds 90
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario STATIC_RC --seconds 90
```
正向缓存保持开启,每次稳定后须真实 Build>0、Reuse>0、FastDelta>0,完整120帧24Gate mismatch=0;原63层 STATIC_RC 缺 tide 继续作为安全拒绝负对照,验证器可能返回 NO_FAST_OBSERVED/exit2,不能当正向 PASS,也不能添加假 tide。正向静止90秒不是生命周期验收。
RDC/用户还需反复 pan/zoom/rotate、跨 tile/world wrap、Source同名替换、Style清理/重载、glyph/icon/atlas更新、后台恢复/context reset;每次变化保存失效 → Full → 新 Build → 成功 Full Reuse → Fast 的顺序证据,Text/Icon/SDF/Halo 不消失、残影或错位。相同相机的 Full/Fast 图像对照、资源随循环最终释放均须实测。保持正式 Style/Source 和公共契约,不以禁用 Retained、每帧重建或放宽24Gate替代验证。
必须保存:新锁定源码 HEAD/diff、build-info/完整构建日志、本地及安装 APK SHA-256、实际 libmaplibre.so Build ID与匹配符号、测试发现/执行数、Build/Reuse/Full/Fast/Invalidate 区间增量、完整24Gate快照、操作前后截图及视觉判断、资源数量/释放证据和全部未验证项。证据写新目录,不覆盖 ignored 历史证据。
异常先保存 crash 日志/bugreport,不先清日志;由获授权执行者执行:
```bash
STEP119_EVIDENCE_DIR=$(mktemp -d /tmp/step119-symbol-lifetime-XXXXXX)
adb -s 192.168.200.139:5555 logcat -b crash -d > "$STEP119_EVIDENCE_DIR/crash-logcat.txt"
adb -s 192.168.200.139:5555 bugreport "$STEP119_EVIDENCE_DIR/bugreport.zip"
```
## 7. 风险、证据缺口与停止门槛
- 缺原始寄存器 tombstone;原 Build ID `485459ba44f806ef0a0c08633c717bfd5d82ad8f` 和匹配符号应保留,不能断言 `0x8cdc7c` 唯一原因是本 UAF 链。
- 当前模型运行参数没有可独立回读的执行器元数据,RDC报告须补该身份事实;不伪造“已核实”。
- 真实 C++ 编译、GTest、Metal/OpenGL/Vulkan、底层 Tile 寿命及真实 atlas 槽位/像素变化、异步线程边界、释放/内存开销均未验收。静态检查只能排除部分明显问题。
- 若必须改变公共契约、重构全部 Tile 所有权、永久禁用缓存、放宽测试/证书或修改发行物才能继续,停止扩大修改并标 BLOCKED。
- 编译/链接/GTest失败、SIGSEGV、24Gate mismatch、Fast永久归零、资源循环单调增长、Symbol视觉异常均保留现场,不能通过降门槛、旧APK或隐藏失败制造 PASS。
本轮实现交接到此,后续由 RDC 独立验证;失败按 Codex 修复 → RDC 验证闭环返回,不继续后续开发阶段。
## 8. 最终交付静态检查记录
- 完整命令 `git diff --check`:exit=0,stdout/stderr 空。
- 七个任务文件显式路径的 `git diff --check -- ...`:exit=0,stdout/stderr 空。新建 Step/报告尚未跟踪,Git diff 不包含其内容;另以轻量 Python 文件检查确认两份 UTF-8 文档状态、末尾换行及无行尾空白,不借此声称 Git 已检查 untracked 内容。
- `git status --short`:仅四个接续源码、开发记录、两份新增本任务文档及两份原有保护任务书;没有其它修改。branch/HEAD 与开工一致。
- 最终 STEP-110/111 SHA-256 与开工值一致;开发记录内容以 HEAD 原文的完整字节为前缀,只有末尾追加。
- 静态计数:生命周期13项,既有 Step119SymbolResults 为9+4=13项。没有发现/执行 GTest,不能声称实际26项通过。
- 没有新的编译/测试/设备验收结论;状态维持 IMPLEMENTED / WAITING_RDC_VALIDATION。

View File

@@ -0,0 +1,88 @@
# STEP-119 Symbol缓存 Codex完成报告 v1
日期:2026-10-11(JST)。结论:**IMPLEMENTED_WAITING_ANDROID**。Shared Core实现与13项非Jest测试源码已完成;Native可编译性、测试运行、真实R Build/Reuse/Fast和视觉结果均未验收,不能写全任务PASS。现阶段立即交RDC独立审计与验证。
## 1. 真实执行环境与授权
- 仓库:/Users/tei/dev/MapLibreParticleLab;Mac.lan,Darwin24.6.0,x86_64。
- 分支:particle/main;开工/交付HEAD均为869930046b11dbeb3b147e3be952a2e847316292,本轮没有Git提交或push。
- 实际模型/推理档位:gpt-6.1-sol / high,来自本会话rollout的turn_context核实,未升级、未启动子代理或另一个Codex实例。核实文件:/Users/tei/.codex/sessions/2026/10/11/rollout-2026-10-11T15-20-51-01a1299f-0c2b-78b0-8599-e50812c108dd.jsonl。
- Native版本仍13.6.1-particle.23,未更改发行坐标。Integration本地HEAD80667d6c343411d03a760425438e3d292f452118;已读取当前全部要求的协作规则/协议,只读,无跨仓写入。
- 当前任务仅内部Renderer实现、两个已登记测试文件与四份中文文档/开发记录。禁止的构建、设备、Jest及Git写操作全部未执行。
## 2. 已实施的行为
原死锁是多个条件共同导致:新Placement令changed=true、recent令stale/hasTransitions=true、已结束fade通常>=1,并且global/perSegment/preparedScene存在独立阻断。本轮实现最终绘制输入证书,没有只改单个布尔条件。
正常Full保持原全场Placement/crossTile碰撞、updateLayerBuckets、动态/opacity/paint顶点、processChanges和原Style语义。先运行原生Symbol tweaker及上传,核查UBO容量、共享属性GPU背板与非dirty索引,再复制实际UBO、Drawable绑定属性、text/icon索引与segments/排序、wrap tile、atlas/texture/shader身份、实际混合/剔除/深度状态、全局组数/ordinal和原注册关系。精确字节比较不以placed布尔或hash代替GPU输入。
同绑定、同settled输入与上一成功提交Full相等时准入首次Build;成功present/endFrame及再次复核后,建立匹配Build revision。下一成功Full Reuse之后才Fast。Full Reuse也检查精确candidateHasBuild,防止组合revision碰撞借旧结果。普通Full替换旧prepared tree保留复制证据;真实reset仍清空。global稳定性、perSegment Gate和preparedSceneSupported全部接入同一证书。
新增内部hasActiveSymbolFade区分刷新stale与真实fade,旧hasTransitions继续用于正常刷新。GL/Metal原生opacity clamp逐顶点检查,未结束fade/异常packed值拒绝;Fast保留被认证的Full fade值。只有实际编译的原生GL/Metal Symbol源码及合法uniform宏才能登记shader来源,名字相同的未知shader拒绝。
Source/style/tile/glyph/image/atlas/camera/viewport/pixelRatio/context/Annotation/update signature、ready与异步epoch保护继续保留;补上Glyph requested/error、Tile/Symbol error。注册/绘制错误推进error/content代次;上传/编码/提交异常清旧缓存和证书后原样抛出。未知Symbol类型须原生typeInfo身份一致;pending/旧隐藏组/collision额外组/不可读或超16MiB输入均保守拒绝。原24Gate evaluator、顺序、mask、诊断公开结构和计数判据未修改。
## 3. 实际修改文件(共25个)
| 文件 | 变更用途 |
|---|---|
| src/mln/renderer/symbol_render_result.hpp(新增) | 精确输入、合法fade终点、绑定、完成Full/Build/Reuse证书 |
| src/mln/renderer/symbol_render_inputs.hpp(新增) | 实际vertex/instance属性字节与不可变buffer身份 |
| src/mln/renderer/symbol_shader_provenance.hpp(新增) | 原生shader来源及uniform宏白名单 |
| src/mln/renderer/layers/render_symbol_layer.hpp | 内部结果采集入口与原group身份 |
| src/mln/renderer/layers/render_symbol_layer.cpp | 原生Symbol实际Drawable/Bucket/Atlas/索引/绘制状态取证 |
| src/mln/renderer/layers/symbol_layer_tweaker.hpp | 实际UBO证据与执行帧记录 |
| src/mln/renderer/layers/symbol_layer_tweaker.cpp | 复制实际上传字段,覆盖consolidation两路径 |
| src/mln/text/placement.hpp | 内部真实fade查询声明 |
| src/mln/text/placement.cpp | 真实fade查询,保留原stale/placement频率 |
| src/mln/renderer/render_orchestrator.hpp | 内部采集/error代次与visitor错误失效 |
| src/mln/renderer/render_orchestrator.cpp | registry/source/global取证、真fade与异步epoch |
| src/mln/renderer/renderer_impl.hpp | 证书容器及独立完成signature |
| src/mln/renderer/renderer_impl.cpp | 上传后准入、global/perSegment/prepared/Fast、Build/Reuse/完成复核 |
| src/mln/renderer/renderer.cpp | 普通Full保留证据,逃出编码/提交异常清缓存 |
| src/mln/renderer/render_tree.hpp | Fast保留认证fade参数 |
| src/mln/shaders/gl/shader_program_gl.cpp | 原生GL编译来源薄接入 |
| src/mln/mtl/context.cpp | 原生Metal源码/entry/宏薄接入 |
| src/mln/shaders/mtl/shader_program.cpp | Metal来源生命周期撤销 |
| test/renderer/retained_underlay_cache.test.cpp | 新增9项Step119SymbolResults |
| test/renderer/render_orchestrator_error.test.cpp | 新增4项Step119SymbolResults |
| docs/20_调查/STEP-119_Symbol结果证书与R缓存Build实现依据_v1.md(新增) | 生命周期、不变量、反例、限制 |
| docs/50_Step记录/STEP-119_Symbol缓存实际实现_v1.md(新增) | 本阶段状态、已做/未验与下一步 |
| docs/60_测试/STEP-119_原PBF_FAST实机联合验收计划_v1.md(新增) | RDC精确命令、来源锁、正负场景/失效/视觉矩阵 |
| docs/报告/STEP-119_Symbol缓存_Codex完成报告_v1.md(新增) | 本报告 |
| 粒子开发记录.md | 仅末尾追加STEP119中文记录 |
没有修改include公开API头、RN/JNI/ObjC/Java/Kotlin、正式PBF/Style/URL、STEP118派生场景、Integration协议、天气层或Core-owned timeline/playback。未修改测试登记/build规范、验证器、STEP110/111任务书、历史报告与ignored原设备证据。
## 4. 已执行检查与结果
- 仓库/Integration规则、当前源码/任务与STEP112–118关键证据阅读;实际branch/HEAD/status/version、模型turn_context核实。
- 轻量静态接线复核:Full/Build/Reuse/Fast、原24Gate、SourceRevisionBook、GL/Metal source与clamp、reset/error路径、测试登记与验收命令。
- git diff --check:exit0;这是空白检查,不是C++编译或测试。
- 13项新增TEST源码静态计数,两个文件已有CMake登记;实际共享CPU vector无GPU背板/已release无背板反例纳入失败测试;未修改STEP117/118既有断言。
- 静态文件范围、开发记录仅追加、公开头/Style/设备证据及两个保护任务书哈希核查。具体保护SHA见下节。
本轮未执行CMake/Ninja/Native编译、fsyntax-only、GTest、Metal/iOS运行、Gradle/APK构建、ADB安装/设备命令、Bazel/codegen、Jest、commit/push或网络远端HEAD回读。没有新的APK/设备SHA、GPU fence、截图或计数结果;没有凭进程退出或静态检查声称PASS。未产生新的编译/测试失败日志;旧libuv环境问题仅作为历史限制。
## 5. 原证据与保护边界
STEP118旧真机事实:原63→派生62基础Layer+天气2=Native64,R[0,62)、C[62,64),missingSourceIds=[];fallback=static-placement,Build0/Reuse0;90.00806697399821秒FastDelta0/FullDelta942、24Gate mismatch0。旧APK源3051196054c7b69815c298040dae61d26499d9f2,SHA44afcfabe0cf4f1ec93f35899241bd73093aa6db4b5cb82ca2120dd531b8f3e9。这些是旧失败证据,不能改写成本轮成功。
| 只读保护文件 | SHA-256 |
|---|---|
| 未跟踪STEP110任务MD | 33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0 |
| 未跟踪STEP111任务MD | f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4 |
| artifacts/maplibre-particle/android-validation/step113-retest-20261011_140448/static_rc_no_tide/result.json | d3d20fc35bbd17e9f0ea0dd014ca4af8e0c5c5284d9f0c009bc20981d876ff10 |
| 同目录screen.png | c8b57a8432626c9209ca9a614a747728d4256bd09273b28c26a793a75a6d44d4 |
## 6. 未验收、阻塞和RDC交接
- 当前源码尚未由RDC独立审计/锁定。现有verifier要求tracked clean以及APK来源HEAD/本地与设备SHA一致,会对当前dirty报SOURCE_NOT_LOCKED;这不是本轮已运行报错,而是读取代码确认的预检。Codex无commit/push授权,不绕过保护,Git收尾由RDC在其明确阶段授权内scoped负责。
- 所有新增C++代码/13项测试仍NOT COMPILED/NOT RUN。状态机fixture的手工Build标记、模拟payload/CollisionGroups不等于真实Symbol GPU、碰撞或像素等价,必须另跑真实路径。
- Android原STATIC_RC_NO_TIDE Build>0/Reuse>0/FastDelta>0尚未证实;原63缺tide负对照仍应拒绝。Source/Style/相机/异步/Annotation/Context矩阵与Full/Fast视觉全部待证。
- Metal/iOS实际source/UBO/instancing路径尚未验证;Vulkan/WebGPU无本轮provenance登记,Symbol缓存继续保守拒绝,普通Full不变。
- 16MiB输入上限、数据量、取证CPU/内存成本、真实异步竞态与平台GPU完成仍是待测限制。超限或缺证明继续Full,不能调低验收判据制造通过。
RDC先读本报告/实际diff,按[联合验收计划](../60_测试/STEP-119_原PBF_FAST实机联合验收计划_v1.md)锁源码并执行准确命令:macos-metal配置、mbgl-test-runner编译、13项发现与定向GTest;Android GL真实编译参数核查。通过后集中一次标准APK流水线/install,以显式serial 192.168.200.139:5555验证STATIC_RC_NO_TIDE至少90秒、真实R Build/Reuse/Fast与120帧24Gate mismatch0,再做原63负对照及变更/视觉矩阵。
RDC须保留新锁定HEAD、本地APK/设备SHA、命令/exit/耗时/日志、测试数量、Layer/Source/R/C清单、Build/Reuse/Full/Fast区间增量、完整24Gate快照、截图与用户视觉结果。失败保存首个真实错误/现场,最多5轮当前Step修复,不跳后续阶段。源码交付到此停止;独立验收尚未完成。

View File

@@ -0,0 +1,51 @@
# STEP-119 粒子独占 C 与 CPU 效率阶段验收报告 v1
日期:2026-10-11(JST)。阶段结果:**SOURCE_READY / CPU_BENCH_READY / ANDROID_NEW_APK_NOT_RUN / STEP119_PARTIAL**。
## 一、可行性结论
用户确认 RCRR 的正常使用原则是 C 只放粒子,后续必须以实际 CPU 效率衡量 FastPath 而非单看 Fast 计数。源码检查确认:Native `tryRenderParticleFastFrame()` 使用 `singleContinuousSegment(plan)`;Fast 执行仅访问当前唯一 C 段的 LayerGroup,R 需全部缓存有效、无版本/内容/Placement/Annotation 等 24 Gate 安全问题。原则**有条件可行**,不等于 WeatherColor 放入 R 后已经获得 Fast 资格。
**没有取得新 APK 的 Full/Fast CPU 性能对比数值**;本报告仅完成测试场景、测量器与验收准备。不能宣称 CPU 性能提升、GTest 或 iOS 完成。
## 二、实际基线和执行状态
- 设备 Mac.lan,通过 RDC 操作 `/Users/tei/dev/MapLibreParticleLab`;原有 `particle/main` 分支,起点 `1072816740fa69b7b23cd70602a402be19e5be2a`。
- Integration `/Users/tei/dev/navsea-particle-integration` 已 `fetch main`、`merge --ff-only origin/main`,本地/远端一致 `80667d6c343411d03a760425438e3d292f452118`。已依据当前规则确认 Android/iOS/Shared Core 边界及项目 AGENTS。
- 既有设备 APK 源 commit `3d989a00a822989339a6d7cb4d23acc04396670e`、SHA-256 `57d68800770d431a2ce9247406f636e7a01cc19fccaf4c59f5eb3a7c99128135`;**未重新编译或安装,不得对新场景使用旧 APK 验收**。
- 之前的原 `PARTIAL_RCRR` 80秒真机负例保留:R0 因缺 tide Build=0/Reuse=0、R2/R3 各 Build=1/Reuse=536、FastDelta=0/FullDelta=596、decisionMismatch=0。旧 `STATIC_RC_NO_TIDE` 正向 Fast=1819,数据/图层顺序不同,不当作新 CPU 对照数据。
## 三、新 Test App 结构
不改生产 PBF、原 63 层场景或 Shared Core 的 24 Gate。Test App 在唯一无 tide 测试副本中得到 62 个基础层,插入的 WeatherColor 索引31、Particle 索引32。正常性能候选只允许 Particle C[32,33):
- `RCRR_C_NARROW_NO_TIDE`:R[0,32)、C[32,33)、R[33,63)、R[63,64)。
- `RCR_PARTICLE_ONLY_NO_TIDE`:R[0,32)、C[32,33)、R[33,64)。
- `RCRRR_PARTICLE_ONLY_NO_TIDE`:R[0,32)、C[32,33)、R[33,38)、R[38,63)、R[63,64)。
- `RCRR_FULL_CONTROL_NO_TIDE`:与第一组同源、同图层、同 RCRR Plan,但调用 `setRetainedUnderlayEnabled(false)`,目的是同屏 Full CPU 基线;预期原生 Fast=0,实际须设备证明。
老 `RCRR_C_WIDE_NO_TIDE`、`RCRR_R_DYNAMIC_NO_TIDE` 仍保留为错误划分安全负对照,不列为正常性能候选。原 `PARTIAL_RCRR` 和 `STATIC_RC` 及 v1 报告均保留历史事实。
Test App 菜单的操作 ID 从10/11/12迁移到100/101/102,避免新增 enum 场景 10/11 与 Snapshot、Mutate 冲突;Full 对照按钮显示 CACHE OFF。Native 摘要新增 `sampleElapsedMs` 与 benchmark 档 6 秒报告周期,仅用于关联 CPU 采样,不修改 Gate 或 Render 路径。
## 四、CPU 真实测量方案
新增 `tools/particle_android_validation/benchmark_step119_cpu.py`:在用户提供相同源码构建并正确安装的 APK 上,调用既有严格 `inspect()` 锁 commit、SHA、ABI/安装设备;启动目标 Activity,Warmup35秒,稳定观察90秒,按6秒读取 Android `/proc/PID/stat` 的 utime+stime 和 `/proc/uptime`。设备访问能力已只读检查:真实 `CLK_TCK=100`,`/proc/<PID>/stat` 可读取,但**CPU Benchmark 本身尚未对新 APK 运行**。
输出 `CPU Core Seconds`、`CPU Core Equivalent`(CPU%相对单核)、`Native FPS`、Fast/Full增量、FastFraction、估算 `CPU ms/完成帧` 和分段 Build/Reuse/Invalidation。CPU 为**整个 App 进程**的时间,并非 C++ renderer 独占的精准逐帧计时。Fast 稳定区间要求 FastFraction≥95%,Full 对照要求 Full>0、Fast=0,且 24 Gate mismatch=0;失败按原始事实保存 JSON 并返回非零。
新增 `tools/particle_android_validation/compare_step119_cpu.py`:读取 Full→Fast→Full A-B-A 实际结果与 RCR/RCRRR 结果,校验同设备/APK SHA/Style 指纹、场景生命周期和 CPU/Native 边界,将 Full 两轮作为 CPU/帧基线,计算各 Fast 组 CPU/帧变化比例及 CPU/秒和 FPS。若 Full 两端 CPU/秒漂移大于15%,结果标记需复测。此比较用于检验 CPU FastPath 收益,**不代表 GPU 时间、能耗或省电已经证明**;FPS 不同时必须同时呈报 CPU每秒与CPU每帧。
## 五、当前已执行验证与未执行项
- Python 非 Jest 结构/CPU 算法测试:12 项 PASS,运行退出码 0;涵盖4类有效性能结构与2类保守负例、错误分段/缺源/Annotation失败、原 STATIC_RC / 原 PBF63 边界、proc/stat解析与 CPU/fps 归一计算。
- CPU 工具 CLI 参数校验/AST:PASS;Android ADB 读设备时钟率:`100`(只读)。
- `git diff --check`:本阶段代码检查通过,最终 commit/push 信息以后续 RDC 实际状态补充。
- 新 Kotlin Test App 尚未经用户手工 Gradle 编译/打包;四种 CPU 场景 Native 真实 Full/Fast、CPU时间、帧率与图像仍为 **NOT VERIFIED**。
- Source/Style/Tile/Camera/Annotation 等失效及恢复、13项 STEP119 Native GTest、Metal/iOS 独立验证均未完成。Mac libuv 既有缺失不擅自装依赖/升级系统工具。
## 六、工程风险与下一步
只有在 C[32,33) 实际得到 Fast、视觉匹配及 CPU/帧或 CPU/秒的受控改善后,才考虑产品采用。多 R 可能提高 FBO/Composite 成本;WeatherColor 放 R 可能因当前 Core 类型/动态数据结果证书被保守拒绝;如果 Main CPU 指标改善但持续功耗/热度上升,也不等于总体有益。
详见 `docs/60_测试/STEP-119_RCRR多配置验收矩阵_v2.md`。本报告是阶段验收,不进入发行、不创建 Tag/SOURCE_LOCK,也不覆盖上轮或未跟踪的 STEP110/111。Git 以 RDC 实际新 commit/push/远端 SHA 返回为准。

View File

@@ -0,0 +1,21 @@
# V25 iOS 实体机 Bazel 构建兼容性修复与资源溯源 v1
日期:2026-10-11。范围:仅 Particle 统一发行的 iOS Metal 实体机 arm64 构建与 Bazel Core 文件声明;不改共享运行时 API,不引入 Simulator slice。
## 故障与原因
- V25 首次统一发行 `release.sh 25 build` 的 Android 四 ABI Release AAR 与 Maven Local PASS;在 iOS Bazel opt `AssetCatalogCompile` 因 Xcode `actool` 返回 `No available simulator runtimes for platform iphonesimulator` 中断。手工指定 `actool --platform iphoneos` 亦同样失败,不能将其误读为 XCFramework 需要模拟器架构。
- 后续尝试暴露 Bazel Core 声明缺失:当前源码已新增 `src/mln/renderer/symbol_shader_provenance.hpp`、`symbol_render_inputs.hpp`、`symbol_render_result.hpp`,原 `bazel/core.bzl` 中 `MLN_CORE_HEADERS` 未登记;已补齐三项。
## 实体机资源输入与溯源
- v23 正式发行:`13.6.1-particle.23` / iOS `6.29.0-particle.23`,来源 `release/maplibre-particle/13.6.1-particle.23/SOURCE_LOCK.json` 的正式发行 iOS ZIP。该 ZIP 本地 SHA-256=`9dbc477458377ab7e207bfd125d8f4106d3d83e7f923499a7bc048e39f74a76a`,与 v23 Source Lock 一致。
- 提取 ZIP 内 `MapLibre.xcframework/ios-arm64/MapLibre.framework/Assets.car`,本次纳入版本控制 `platform/ios/resources/Assets.car`,SHA-256=`997014687d17e93b9a979b4a47229d082e04a03587b01b4718ac7cb20f1a196b`。
- 核对 `git diff 87f3d1c59a52581fb8339f0ea7d106b9f4b94fef HEAD -- platform/ios/resources/Images.xcassets` 为无差异;即资源原始输入自 v23 冻结源码未改变。保留原始 `.xcassets` 文件供其他构建使用,仅 `apple_xcframework(name="MapLibre.dynamic")` 的资源输入使用上述受控、可追溯的已编译 `Assets.car`,避免当前 Xcode 环境的 `actool` 错误。
- 该预编译资源绑定 Xcode 26.3 (`17C529`) 工具链;后续原始资源改变或 Xcode 资源格式变更时,须重新生成并记录 Hash,不能无限期假设可复用。
## 验证与边界
- 原 Bazel opt Metal 真机命令:`bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic`。本阶段候选资源及头文件修正的验证结果为 `Build completed successfully`,exit 0;约 772 项,实际构建 563 actions 后的增量验证通过。
- 产生 `bazel-bin/platform/ios/MapLibre.dynamic.xcframework.zip`,仅 `ios-arm64`,无 Simulator;ZIP 内 `MapLibre.framework/Assets.car` SHA-256 与输入 `997014...` 一致。
- 以上仅证明 iOS 实体机二进制**可以构建**,不代表 iOS 真机运行通过、NavSea RN 宿主端验收、正式公网发行已通过。正式 Build Source Commit、Android/iOS/RN 同源构建、GSRL Source Lock、远端制品 SHA 需另行严格验收。

View File

@@ -0,0 +1,36 @@
# V25 统一正式发行 GSRL RDC 完成报告 v1
日期:2026-10-12(JST)
结论:**GSRL RELEASED / PASS**。跳过 v24,v23 历史 Tag/制品未覆盖。设备运行时与 Host 联调独立待验收。
## 版本及身份
- Android OpenGL AAR / React Native:`13.6.1-particle.25`;iOS Metal / SwiftPM:`6.29.0-particle.25`。
- 仓库:`https://git.eyaku.jp/tei/MapLibreParticleLab`;发行分支:`particle/main`。
- 实际统一 Build Source Commit:`f32f6f45fd359007e8b3c77911b66d9e7fc93848`,已 Push 并与远端一致。
- GSRL Source Lock 文档提交:`4b2f3c8d3874161c9cba832a4ba28d5182b05ca6`,Commit Message:`release: record v25 GSRL source lock and receipt instructions`。
- Pipeline Finalize Commit / 完成 Tag 时的 Final HEAD:`3774d0a8418daa36d7a988b2ce9650a6a1178fc4`,Commit Message:`release: finalize unified particle.25`。
- 不可变正式 Tag:`maplibre-particle/rn/13.6.1-particle.25`,远端 annotated tag peeled Commit = `3774d0a8418daa36d7a988b2ce9650a6a1178fc4`;SwiftPM 远端 `6.29.0-particle.25` 存在。
- 发布入口:`release/maplibre-particle/13.6.1-particle.25/README.md`、`SOURCE_LOCK.json`;六文件 SHA/大小/地址以 Source Lock 为权威。
## 严格串行执行及验收
1. Integration main 同步并核对跨项目发行约束;版本占用核对,v25 未占用;Prepare 提交 `d5841e6471058614e9a06bf5e27dba0df38e8a97`,远端核验 PASS。
2. 首次 Build 的 Android 四 ABI AAR、Maven Local PASS;iOS Bazel `actool` 因无 Simulator Runtime 失败。**v25 不增加 Simulator 架构**。使用未变化的 v23 `Images.xcassets` 对应、Xcode 26.3 编译的 `Assets.car`(SHA256 `997014687d17e93b9a979b4a47229d082e04a03587b01b4718ac7cb20f1a196b`),并补齐三处 Bazel Core 头文件声明,详见 `V25_iOS实体机Bazel构建兼容性修复与资源溯源_v1.md`;源修复提交 `f32f6f45fd359007e8b3c77911b66d9e7fc93848`。
3. 正式 `release.sh 25 build`:**exit 0**,Android 四 ABI 完整,Maven Local、iOS Bazel opt Metal 真机 ios-arm64 ZIP(无 Simulator slice)、RN npm pack PASS。构建日志:`/tmp/maplibre_v25_unified_release_build_final_20261011.log`。
4. `release.sh 25 publish`:**exit 0**,NAS 原子发布、六文件公网逐字节读回 PASS、SwiftPM Tag 已发布。日志:`/tmp/maplibre_v25_unified_release_publish_20261011.log`。
5. 生成 GSRL `gsrl/source-lock/v1` Source Lock;六项文件 SHA-256 与本机正式构建产物逐一比对 PASS;版本、依赖、来源、使用说明齐全,Git scoped Commit。
6. `release.sh 25 finalize`:**exit 0**,正式 Finalize Commit、发行 Tag Push 成功;再次 Fetch 确认 `origin/particle/main` 与正式 Tag 的 peeled Commit 均为 `3774d0a8418daa36d7a988b2ce9650a6a1178fc4`。日志:`/tmp/maplibre_v25_unified_release_finalize_20261012.log`。
7. 2026-10-12 独立公网二次回读:AAR、POM、Module、RN tgz、iOS Metal ZIP、ZIP.sha256 **6/6 PASS**,各 URL 下载退出码 0、size 与 SHA 均和 Source Lock 相同;SwiftPM 远端 Tag 再次核验存在。
## 三个主体制品 SHA-256
- Android AAR:`629d5d437d884ec2017f3fde007aaadfd2bc787983457f75bcaf4d4a13184a1e`
- RN tgz:`e5ed678c90bad6082dda30fb9937894cabe28732160391816b785d3c88c208e9`
- iOS Metal XCFramework ZIP:`91808cfb5559dfa050fe4d466d2c9fa2dc492021bd1a203c778450dc0dae20af`
## 未覆盖事项
- 本次 **未执行 v25 发行 AAR/RN/iOS XCFramework 的 Android/iPad 真机运行与 NavSea Host 整体验收**。早前 STEP-119 Android CPU FastPath 基准来自其他源码版本和 Validation APK,不能冒充 v25 正式制品的设备 PASS;动态 Camera、Style、Source/Tile/Annotation 更新与像素等价仍为后续专项验收。
- GSRL RELEASED/PASS 仅证明来源、版本、正式制品及公网验收链闭合,不等于功能或性能全场景认证。
- 本报告为 Tag 后的审计文档增补,**不得移动或覆盖既有发行 Tag**;后续分支文档 Commit 不更改正式 Tag 指向。

View File

@@ -0,0 +1,73 @@
# V26 Metal 混合 Segment 视觉错误——保留 Retained 的修复调查报告 v2
日期:2026-10-13
状态:**iPad F 组真机 FAIL(2026-10-13 18:51 JST);CommandBuffer 修正候选不能作为修复完成证据。Native 编译/安装/启动通过,但 Retained 视觉验收不通过。禁止发布 V27。**
前一版:`V26_Metal混合Segment视觉错误_安全降级调查报告_v1.md` **REJECTED**,禁止发布其中关闭 Metal Retained 的方案。
## 1. 用户强制约束
- 不允许关闭整个 Retained、不允许把有效 Retained Plan 隐式改成全量 Live。
- Retained 必须保持 GPU 离屏绘制、缓存 Build/Reuse、Cache Texture 合成;Continuous 执行频率保持独立。
- Android/iOS 使用同一套 JS Plan 和用户步骤,不以 `Platform.OS` 分配不同实验语义。
- 以 Style Z-order 为唯一图层顺序,不改本船、AIS 或天气业务数据。
- 不新增私有 RN/Native API,不改 Particle Contract。
- 实际执行:2026-10-13 经用户授权,ChatGPT 通过 RDC 在 Mac 远程执行 iOS Metal 构建、签名、覆盖安装与启动;Android 原生编译和安装未执行。iPad 视觉验收由用户人工确认。
## 2. 已取得的实际复现
来源:Pleiades iPad mini 5,已安装 V26;Windows NavSea Metro `192.168.200.100:8081`。
- E:全 115 层 CONTINUOUS,Retained 总开关开启,0 Retained Segment,iPad 无闪烁,图标/本船航行线/AIS 航线/色场/粒子正确。快照 `/tmp/navsea-v26-ipad-render-20261013/E-0746/start.json`。
- F:R0 仅 `background`,C1 其余 114 层,Native `valid=true`, `fallback=false`,`actualExecution=[composite,live]`,R0 build 96/reuse 2778。冷启动即图标过大、本船/AIS 航线消失;天气仍可见且不闪。快照 `/tmp/navsea-v26-ipad-render-20261013/F-0740/start.json`。
- 原始 RC:R0 层 0..30,C1 层 31..114;天气 ON 后闪烁,OFF 后持续。旧 RCR 预检的末端 Layer ID 硬编码与实际 Style 不符,现场最终跑 RC。
- A:关闭 Retained 不闪;B:Native 天气隐藏不闪;C 和 D 分别只显示色场或粒子都闪。C/D 使用相同 Native Runtime 数据提交,不代表单独 shader 根因。
F 在仅最底层缓存时也失败,说明“缩小缓存至简单图层”不能作为修复。
### 2026-10-13 新构建真机复测(候选 FAIL)
- RDC Mac Bazel `bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic`:943/943,exit 0。新候选 ZIP SHA-256:`b89bc06bf03eba20a5790da21215fe7e167da20b3fdfa6a34782420bd503a354`。
- 新 XCFramework 从本地 ZIP 替换至暂存 NavSea.app,codesign 重新签名和验证 PASS,Pleiades 覆盖安装及启动 PASS;没有发布/替换正式 SwiftPM V26。
- 用户报告新候选 E:地图、天气 ON/OFF 都正常(E 不执行 Retained Cache)。
- 用户报告新候选 F:**FAIL**,冷启动光弧等图形巨大、不闪、航向线消失、天气开关无可见效果。不能将无闪烁当作正确。
- 新 F Native 快照:`/tmp/navsea-v27-retained-ipad-stage/F-1851/start.json` 和 `end.json`,时间 `2026-10-13T09:51:04Z`、generation 3、mapReactTag 802、118 Style Layers;R0 `[0,1)` `background`、`actualExecution=composite`、`build=13`、`reuse=1069`、`invalidation=13`;C1 `[1,118)` `actualExecution=live`;`valid=true/fallback=false`。证明缓存确实工作,但画面仍错。
- 结论:CommandBuffer 原实现存在执行序不明确的风险,然而此候选未修复视觉缺陷,**无法将它认定为根因或 V27 修复**。后续重点分析 Metal Composite 的绘制状态、RenderPass、Stencil/Depth 和 Live 绘制的相互作用。
- 新 F 完成后 Windows Metro 同一 Android/iOS Debug 开关已改回 E,iOS Bundle HTTP 200;**用户于 2026-10-13 20:43 JST 确认 iPad 重新运行 E 后显示 OK**。由此 E 稳定恢复;F 明确失败。候选仍留在 iPad,仅切回 JS E,不代表回装了旧 V26。
- 后续本轮只读源代码调查:`renderer_impl.cpp` 的 F/E 共同走标准 Live `drawableOpaquePass/drawableTranslucentPass`,区别是 F 额外执行了 `RetainedUnderlayCache::beginBuild/finishBuild/composite`;`mtl::RenderPass` 和 `PaintParameters::beginRenderTarget()` 会重建 Pass 状态及清理 tile clipping mask ID。需重点验证 Cache Composite 的真实 Metal state、Stencil/Depth、顶点/纹理坐标以及 Live drawable 的状态恢复;仅从快照计数不能推断根因。
## 3. 已从实际代码确认的候选根因
V26 `src/mln/mtl/offscreen_texture.cpp` 的 `OffscreenTextureResource::bind` 以 `context.getBackend().getCommandQueue()->commandBuffer()` 单独取得离屏 CommandBuffer;`swap()` 对它独立 `commit + waitUntilCompleted`。
iOS `platform/ios/src/MLNMapView+Metal.mm` 的默认画面 `MLNMapViewMetalRenderableResource` 自建 `commandQueue` / `commandBuffer`,直到帧末 `swap` 才提交。
`renderer_impl.cpp` 先用默认画面 CommandBuffer 组织上传,再绘制 Retained 离屏缓存、调用其 `present`,之后在默认画面绘制缓存纹理及 Continuous。**这两条独立命令队列并不建立“主帧上传 → 离屏构建 → 主帧合成”的 GPU 顺序保证。** 这是确定存在的同步风险;是否是图标放大/航线丢失的唯一原因仍需真机验证。
## 4. 第二版实现(保留 Retained)
- `include/mln/mtl/context.hpp` 和 `src/mln/mtl/context.cpp`:提供帧内借用的主 CommandBuffer 指针,在 beginFrame/endFrame 清理,不持有跨帧引用。
- `src/mln/mtl/command_encoder.cpp`:默认画面 UploadPass 创建时,将它的 CommandBuffer 登记为当前帧 CommandBuffer。
- `include/mln/mtl/offscreen_texture.hpp`、`src/mln/mtl/offscreen_texture.cpp`:增加仅供 Retained Cache 显式启用的帧内排序开关。启用后其离屏 RenderPass **复用当前帧主 CommandBuffer**;离屏 `swap` 在共享模式只清理引用,**不提前提交主帧**,由默认画面帧末统一 commit。未启用此开关的离屏纹理(包括 Snapshot/其它 RenderTarget),即使同帧存在主 CommandBuffer,也维持原先的独立 commit/wait。
- `src/mln/renderer/retained_underlay_cache.cpp`:仅在 Metal Retained Cache 分配或使用 offscreenTexture 时显式启用上述开关;OpenGL 路径不变。
- `test/renderer/mtl_retained_attachments.test.cpp`:新增共享 CommandBuffer、离屏 swap 后仍允许添加同帧 RenderPass 的针对性 Metal 测试。
预期顺序:一个 Metal CommandBuffer 内 `Upload → Retained offscreen render → main composite → live Continuous → final present`。各 Pass 的独立 Color/Depth/Stencil attachment 继续保留。
**没有修改 `RetainedUnderlayCache` 的 Build/Reuse/Composite 语义,也没有跳过任意合法 Retained Segment。**
## 5. 验收和停止条件
1. 检查改动的 C++ 格式与差异;本轮不自动运行 Native 编译或设备操作。
2. 用户手工构建 Metal Candidate XCFramework(使用现有已验证命令 `bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic`),在独立测试版中接入。**不能覆盖正式 V26、不能用旧 SwiftPM binary 冒充新补丁。**
3. 先运行 MetalRetainedAttachments 的针对性单测,确认共享 CommandBuffer 不提前 commit,独立离屏路径不退化。
4. iPad 首先复测 F:保留 `[R(background),C(rest)]` 的 Native Plan,不允许 Native 临时回退全部 Continuous;要求 `build>0`、`reuse>0`、`actualExecution=[composite,live]`,并且图标大小和所有航线正常。
5. F 通过后再测 RC:天气 OFF/ON/OFF,色场、粒子、本船/AIS/海图视觉完整,无异常闪烁;对照 Android OpenGL 同一 JS Plan。
6. 检查帧率、CPU/GPU 时间、缓存重建/复用、内存;保证 Retained 的性能价值。
7. F 如果仍错误,**立即停止**,采集 Metal GPU Capture 对比两个 RenderPass 的资源绑定与混合/Depth/Stencil,不能宣布 bug 已修好,也不得重新关闭 Retained。
## 6. 当前工程和发行状态
Mac 仓库 `/Users/tei/dev/MapLibreParticleLab` branch `particle/main`、当前起点 HEAD `b0f34cc9a299a75af1f7e708c63d75eaa42e9ca2`。源码改动尚未 commit/push/tag/publish;正式 V26 SOURCE_LOCK 指向 `fce326a9009d3fc21eb91c5df362f0769163d07e`,它是旧发行身份,不代表此次候选已发布。
Windows NavSea 当前 JS 已取消 iOS 独享 E:Debug Android/iOS 均选 E,Release BASELINE。正式修复后需在新 Native 实际安装成功、用户批准测试窗口后,把共用 JS Test Mode 切到 F/RC 进行对照;不能在旧 V26 上提前切换造成已知异常。
当前明确结论:**这是保留 Retained 的候选修复,尚未通过 Native 编译与设备画面确认,不能写成 CLOSED/PASS。**

View File

@@ -0,0 +1,45 @@
# V26 Metal 混合 Segment 视觉缺陷——保留 Retained 修复调查报告 v3
日期:2026-10-13
状态:**第二版 Stencil 状态修正 F 真机 FAIL / ABORTED;图标仍异常放大,且用户报告地图无法移动。已保存证据、恢复共用 JS E,并归档/撤销失败的源码补丁。不可发布 V27。**
正式发行:**NOT RELEASED,不得标记 V27 PASS;保留 Retained 构建/复用/合成**
上版:`V26_Metal混合Segment视觉错误_Retained保留修复调查报告_v2.md`(CommandBuffer 候选 F 真机失败)
## 1. 已证实事实
- Pleiades iPad mini 5;Native 旧 V26 F,及第一版 CommandBuffer 候选 F,均真机 FAIL:背景 Retained 真实 `Build/Reuse/Composite`,但光弧或图标被放大、航向线消失、天气开关异常。旧版本 E(全 Continuous)多次由用户确认正常。
- 第一次候选 Native F 快照保留于 `/tmp/navsea-v27-retained-ipad-stage/F-1851/start.json` 与 `end.json`,其中 Retained R0 `[0,1)`、C1 `[1,118)`、`build=13`、`reuse=1069`、`actualExecution=composite`、`valid=true`、`fallback=false`。
- 不能据此认定 CommandBuffer 是根因。原候选 `MapLibre.dynamic.xcframework.zip` SHA-256 为 `b89bc06bf03eba20a5790da21215fe7e167da20b3fdfa6a34782420bd503a354`;旧候选补丁备份在 `/tmp/navsea-v27-retained-ipad-stage/patches/V27_candidate1_commandbuffer_plus_current_context.patch`。
## 2. 本次单变量改动
恢复前一版 CommandBuffer 改动的七个源码/测试文件,原 Candidate 1 不再参与当前源码编译。仅两个文件相对 `particle/main` HEAD `b0f34cc9a2` 发生功能代码修改:
- `src/mln/mtl/context.cpp`:`Context::renderTileClippingMasks` 的 Metal Stencil ID 设置改走 `mtlRenderPass.setStencilReference(...)`,不再绕过 RenderPass 缓存直接调用 `encoder->setStencilReferenceValue`。
- `src/mln/mtl/tile_layer_group.cpp`:3D Stencil Reference 也走 RenderPass 缓存,删除不再使用的局部 encoder 引用。
代码已确认存在 Metal 原先绕过 RenderPass 状态缓存的路径;其与 F 画面错误的因果关系**尚待真机结果**。没有变更 Retained Cache 或共享 RN API,未禁用 Retained,未引入 JS Android/iOS 平台分流。
## 3. 新 Native 制品与安装
- Mac `/Users/tei/dev/MapLibreParticleLab`,`bazel build -c opt --//:renderer=metal //platform/ios:MapLibre.dynamic`,exit 0,约 49 秒;`git diff --check` PASS。
- 新 Metal ZIP SHA-256:`5a8bef4e8419ae2b8c5bf22253361d40a4ed7ffd7c156ea73d5df8eae72b3393`。
- 独立保留于 `/tmp/navsea-v27-retained-ipad-stage/candidate2_stencil/MapLibre.dynamic.xcframework.zip`;最小源码 patch 同目录 `candidate2_stencil_only.patch`。
- RDC Mac 使用现有 `scripts/ios-install-test-device.sh` 的 `scripts/pass.txt` 所对应 Keychain 与签名身份,在暂存 App 替换完整 MapLibre.framework,重新签名并严格验证;覆盖安装 Pleiades `com.navsea.app` 成功,`devicectl process launch` 成功。
- 无卸载、无清数据、无 SwiftPM V26 正式仓库/Package 更改、无发布、无 Git Commit/Tag。
- 安装后 E Native 快照:`/tmp/navsea-v27-retained-ipad-stage/candidate2_stencil/E-preF/start.json`,时间 `2026-10-13T12:00:50Z`,`valid=true`,0 Retained / 1 Continuous、`[0,118)` Live。仅验证了运行时模式,并非用户视觉结果。
## 4. 第二版 F 真机结果(失败,已停止测试)
- 用户于 2026-10-13 21:09~21:10 JST 确认 **F 仍有大图标,没有改进,并补充报告“也不移动了”**。目前“无法移动”是手势没有作用,还是相机已变但视觉没有更新,仍需通过输入事件/相机指标判定,不得臆测。
- 从 Pleiades 实际取得 `Library/Caches/navsea-render-segment-start.json` 与 `end.json`,已保存在 `/tmp/navsea-v27-retained-ipad-stage/candidate2_stencil/F-failed-20261013-2110/`。其中 capturedAt `2026-10-13T12:10:12Z`、`generation=4`、`mapReactTag=804`、`styleLayerCount=118`、`valid=true/fallback=false`;R0 `[0,1)` `background`,`actualExecution=composite`、`build=14`、`reuse=6076`、`invalidation=14`;C1 `[1,118)` `actualExecution=live`;`fullCpuFrame=6231`。帧计数不等于相机更新证明。
- 用户报告画面失败,Native 配置/执行链未发生全 Live 回退,因此第二版 Stencil 修正 **FAIL**。没有进入 RC,没有发布 V27。
- Windows Metro 当前统一 Debug 开关已经恢复为 `E_CONTINUOUS_ONLY`,iOS Bundle HTTP 200,Android 同一 JS 源配置;用户需冷启动 iPad 才能加载 E。**用户 2026-10-13 21:18 JST 已再次确认本次切回 E 后正常,包括地图移动;因此 E 恢复 PASS。**
- 失败的第二版源码 diff 另存 `/tmp/navsea-v27-retained-ipad-stage/candidate2_stencil/candidate2_failed_stencil_source.patch`。已只针对两个改动的 Metal 源文件 `git restore` 回 `particle/main` HEAD;无其他功能文件变更,报告保留,iPad 上仍是已安装的 Candidate 2 二进制(仅 JS 回 E),不等于已更换 Native。
- 下一轮调查把相机/手势事件、Native 相机矩阵变化、Retained 的 `camera-or-boundary` invalidation、主画面真实绘制与 GPU 合成可视化分开验证,再制定单一变量修复;禁止无验证继续叠加补丁。
## 5. 范围与发布约束
- 仅本地候选,不代表 V27 已发行。
- 不接受关闭 Retained、全 Continuous 降级或 JS 平台分流作为最终修复。
- 若 F 依旧失败,下一轮应进一步检查 Retained Composite 顶点/纹理与 Metal Encoder 状态、Stencil/Depth、主画面 RenderPass,并用证据区分构建与合成副作用。

View File

@@ -0,0 +1,58 @@
# V26 Metal 混合 Segment 视觉错误与第一阶段安全降级调查报告 v1
**已撤回:2026-10-13 用户明确要求必须保留 Retained 实际构建/复用/合成。本文安全降级代码已在 Particle 工作区撤销,禁止按本文编译或发布。请以同主题 v2 修复调查报告为准。**
日期:2026-10-13
状态:**已撤回(REJECTED)——原代码级安全降级方案不可用;保留本文件仅作历史记录**
目标仓库:`MapLibreParticleLab`
冻结 V26 源码:`fce326a9009d3fc21eb91c5df362f0769163d07e`(SOURCE_LOCK),当前工作分支 `particle/main`,修改前 HEAD `b0f34cc9a299a75af1f7e708c63d75eaa42e9ca2`。
## 一、iPad 实测事实
iPad:Pleiades(iPad mini 5);iOS 已安装 V26 Native。实验使用 Windows CARBON7i `D:\React\NavSea` 的 Metro `192.168.200.100:8081`,而非 Mac 本地 Metro。
- 原始 RC:天气 ON 后本船/AIS/海图闪烁,天气 OFF 后仍异常。
- A(关闭 Retained,完整天气):不闪烁。
- B(保留 Retained,隐藏 Native 天气):不闪烁;色场与粒子均无显示。
- C(只有色场,保留 Retained):色场出现,闪烁。
- D(只有粒子,保留 Retained):闪烁。
- E(Retained 总开关开、实际 0 Retained + 1 Continuous):不闪、天气色场和粒子完整,本船/AIS 图标尺寸与航线正常。
- F(最底层 background 单层 Retained + 其余 114 层 Continuous):冷启动即图标异常放大、本船航行线与 AIS 航线消失;不闪;天气色场与粒子正常。**F 是失败,不得据其不闪而判 PASS**。
iPad Native 快照原始保存于 Mac:
`/tmp/navsea-v26-ipad-render-20261013/E-0746/start.json`、
`/tmp/navsea-v26-ipad-render-20261013/F-0740/start.json`;
F:`enabled=true`, `valid=true`, `fallback=false`; R0 `[0,1)` 实际 `composite`,C1 `[1,115)` 实际 `live`;
E:`enabled=true`, `valid=true`, `fallback=false`; 单 Continuous `[0,115)` 实际 `live`。
旧 RCR 实验曾因 JS 预检将最后 Layer 硬编码为 `chartDangerScanCenterLineLayer`,但实际最后 Layer 是 `route-history-dotted-line`,回退到 RC。不要把当时现场当成 RCR Native 执行证明。
## 二、已核对的 Native 代码路径
`src/mln/renderer/renderer_impl.cpp` 中,实际含 Retained Segment 的绘制在独立 RenderPass 中构建离屏缓存,调用 `RetainedUnderlayCache::composite`,然后绘制 Continuous 图层。
`src/mln/renderer/retained_underlay_cache.cpp` 的缓存创建与合成对应 `prepareResources`、`beginBuild`、`finishBuild`、`composite`。
iOS Metal `src/mln/mtl/render_pass.cpp` 已有 Color/Depth/Stencil Clear 逻辑,尚不能证明清屏本身出错。
现有证据将异常定位在 **实际执行 Retained 离屏构建/合成和连续图层共存时**,但尚未证明具体 GPU 状态或 shader 错误。
## 三、第一阶段保护性补丁(尚未发布)
1. `include/mln/renderer/render_segment.hpp`:新增 `requiresMetalRetainedCompositeSafetyFallback`,针对合法 Plan 内任何 RETAINED Segment + Metal 后端启用安全回退;全 Continuous 和非 Metal 保留原逻辑。
2. `src/mln/renderer/renderer_impl.cpp`:Native 检测到上述情形时,禁止 Retained 离屏资源构建、Reuse、合成;释放历史缓存;完整 Style 范围做常规 Live Opaque+Translucent 绘制。阻止 Particle FastFrame 在该组合误准入。Native 诊断 `globalLiveFallbackReason` 记录 `metal-retained-composite-safety`,`actualExecution` 应全部为 `live`。
3. `test/renderer/retained_underlay_cache.test.cpp`:加入 OpenGL 不启用、Metal R-C 启用、Metal全Continuous不启用、Metal全Retained启用、无效Plan不启用的安全策略单测。
**此补丁属于 correctness-first 的暂时降级方案,不是 Retained 合成根因修复,也不提供 Retained GPU 加速。** 禁止在未做真实 Metal 回归前声称问题已关闭。Native 公共 JS API 与输入 Contract 不变;Renderer 层仍以 Style Z-order 为唯一图层顺序。
## 四、待用户手工验收
1. 保留冻结 V26 artifact,不覆盖正式发行版本;从当前修改源码建立新的测试版 iOS Metal XCFramework / Test Host,不把未验证产物标成 V26 正式版本。用户手工完成 iOS 构建、安装、启动。
2. 用统一 JS 测试配置加载含 Retained Segment 的 RC/F 方案,确认 Native 诊断显示 `global-live`、`metal-retained-composite-safety`,且无 Retained texture build/composite;不能只凭 JS mode badge 判断。
3. 冷启动关天气、ON、OFF,检查本船/AIS 图标尺寸、航行线、海图层、天气色场、粒子、图层 Z-order、视觉稳定。
4. Android OpenGL 使用完全相同的 JS Plan 对照,确认没有触发 Metal 安全回退;记录 native 版本差异,版本未对齐时不得直接宣称平台差异。
5. 记录帧率、CPU、GPU、内存和耗电;补丁性能可能低于 Retained 缓存方案。
6. 后续通过 Metal GPU Capture/离屏纹理、Blend、Depth/Stencil 和 Drawables 生命周期调查根因,修复后再移除安全降级,并对 R-C/R-C-R、多 Retained 段复测。
## 五、执行边界与状态
- 本轮没有执行 iOS/Android 原生编译、APK/XCFramework 安装、ADB、真机自动操作,也没有发布 tag/制品。
- 粒子仓库工作区变更先保持未提交,避免污染冻结 V26。
- Windows NavSea Debug JS 的平台分流已移除:Android/iOS 共用 `E_CONTINUOUS_ONLY`,Release 仍为 `BASELINE`。这项改动仍需 Android 用户手工真机核验。
- 全部修复最终验收需要跨项目 Integration 按既定 Contract/Status 更新,不由 NavSea 独自宣告 Particle 发布完成。

View File

@@ -0,0 +1,54 @@
# V26 统一正式发行 GSRL RDC 完成报告 v1
日期:2026-10-13 07:38:02 +0900(JST)
结论:**GSRL RELEASED / PASS**。仅指来源锁、发行包、公网回读与 Git 发行链闭合;NavSea 产品集成及真机运行不属于本结论。
## 版本及源码身份
- Android OpenGL AAR、React Native:`13.6.1-particle.26`;iOS Metal/SwiftPM:`6.29.0-particle.26`。
- Git Remote:`https://git.eyaku.jp/tei/MapLibreParticleLab`;正式源码分支 `particle/main`。
- **冻结实际 Build Source Commit**:`fce326a9009d3fc21eb91c5df362f0769163d07e`(Android 4 ABI、iOS Metal、RN 同源),已 Push 并验证远端。
- 相关修复与文档提交:`343ca2fa68f807a2120948006dc12d052aa96059`,`fix: secure Symbol Retained drawable lifetime and record investigations`,包含用户要求保留的 STEP-110/111 两份任务书。
- Prepare Commit:`fce326a9009d3fc21eb91c5df362f0769163d07e`,`release: prepare unified particle.26`。
- GSRL Source Lock Commit:`21c4450e01c3d5b8be3743867a26374dce3c8cf3`,`release: record v26 GSRL source lock and receipt instructions`,Push PASS。
- **正式 Finalize / Tag Commit**:`e3cd9eba3cd02149d431e0061b8d32192d876f78`,`release: finalize unified particle.26`。
- Tag:`maplibre-particle/rn/13.6.1-particle.26`,已 Push,远端 annotated tag peeled SHA = `e3cd9eba3cd02149d431e0061b8d32192d876f78`;Final HEAD/远端 `particle/main` 在 Finalize 完成时均 = `e3cd9eba3cd02149d431e0061b8d32192d876f78`。
- SwiftPM Tag:`6.29.0-particle.26`,已通过远端 `ls-remote` 验证。
- 本次独立验收结束时 Git 工作区干净,未创建 Worktree,未强制覆盖/移动任何正式 Tag 或历史版本。
## 严格串行执行、日志及验收
1. Integration `main` = `80667d6c343411d03a760425438e3d292f452118`,与远端 `main` 一致;V26 公网发行位点原本未占用。
2. 将 4 个 Symbol Retained 修改源码/测试、`粒子开发记录.md`、STEP-119 两份报告与 STEP-110/111 两份历史任务书 **9 个文件全部 Commit/Push**,Git 工作区恢复干净。冻结源码 V26 版本号并 Push。
3. `release.sh 26 build` 返回 **exit 0**;Android OpenGL 四 ABI 分别完成 Release 编译,Maven Local AAR/POM/Module PASS;iOS Bazel opt Metal arm64 真机 XCFramework PASS(无 Simulator);RN npm pack PASS。
- 构建日志:`/tmp/maplibre_v26_unified_build_20261012.log`。
4. `release.sh 26 publish` 返回 **exit 0**;NAS SSH 临时路径上传、远端 SHA-256 核对、原子升级、六文件公网逐字节回读、SwiftPM 版本 Tag 全部 PASS。
- 日志:`/tmp/maplibre_v26_publish_20261013.log`。
5. 按 GSRL v1 生成独立 `release/maplibre-particle/13.6.1-particle.26/SOURCE_LOCK.json`、中文接收说明和 `release/README.md` 当前推荐入口;GSRL Commit/Push 与远端 HEAD 验证通过。
6. `release.sh 26 finalize` 返回 **exit 0**:4 项 Manifest/iOS 发布元信息 Commit、Git Tag Push PASS。日志:`/tmp/maplibre_v26_finalize_20261013.log`。
7. **独立二次验收**:公网 6 个文件单独重新下载,逐项 SHA-256/大小与 Source Lock 和本地构建物一致,结果 **6/6 PASS**;Android AAR 确认 4 个 `jni/<ABI>/libmaplibre.so`,iOS ZIP 仅 `ios-arm64`,RN tgz version/peer 正确,Git Remote HEAD/Tag/source 关系一致,工作区干净。
- 机器可读独立核验文件:`/tmp/maplibre_v26_independent_receipt_20261013.json`。
## 正式文件验收表(重新计算的公网 SHA-256)
| 交付文件 | 字节数 | SHA-256 | 公网回读 |
| --- | ---: | --- | --- |
| android-sdk-particle-opengl-13.6.1-particle.26.aar | 16640537 | `46c1cf7ff8ebc3bedc25aa38d7d445078a2301e6ca223c0307b93e830991c2b9` | PASS |
| android-sdk-particle-opengl-13.6.1-particle.26.pom | 5687 | `b1e34295c58238a45a8b14d33c801028c3cabc0f40aae6116d007b352fa25520` | PASS |
| android-sdk-particle-opengl-13.6.1-particle.26.module | 6999 | `405b3bb3732ef29a0b54e6b24b192839d07d4c28e959276d6bd92051637011c4` | PASS |
| navsea-maplibre-particle-rn-13.6.1-particle.26.tgz | 29038 | `3136316bb6508e774748b21c2095ab61b522d2cf4c3ff2e8c67774dde8549b93` | PASS |
| MapLibre.dynamic.xcframework.zip | 4181892 | `64b51c2b9eaec4adefcfb2dfc1c6338943f11db4d6c4ce2a4e1c2a39bb84bddb` | PASS |
| MapLibre.dynamic.xcframework.zip.sha256 | 99 | `ae79556c2b288a3970d6e4eba0c30a68023faa79ff55de291c9e2f30f16fd9ae` | PASS |
下载 URL、依赖和安装说明以 `release/maplibre-particle/13.6.1-particle.26/README.md` 和 `SOURCE_LOCK.json` 为准。正式版本不在 Git 存放大二进制,以 NAS PBF/Maven/npm/SwiftPM 公开发行仓库为获取入口。
## 明确未验收项
- 本次**没有把 V26 正式 Android AAR 安装到 NavSea/FZ-G1 进行产品集成验收**,也没有对 V26 正式 iOS Metal XCFramework 执行 iPhone/iPad 实机运行测试;两者均为 **NOT_TESTED**。
- STEP-119 原 FZ-G1 MapLibre Test App 开发态四轮稳定和 CPU/FastPath 测试,仅说明独立引擎测试表现,**不是 V26 正式二进制/NavSea 运行验收**。
- 13 项新增 C++ 生命周期 GTest 未运行,Mac CMake 环境缺 `libuv`,状态 **BLOCKED / NOT_TESTED**;不得将静态检查或 Android/Metal 正式编译视作 GTest 通过。
- Context 重建、完整 Atlas/Style/Source 变更压力、像素完全等价、跨平台真机及 RN Host 行为,不在本次 GSRL 身份/制品闭环内,不声明通过。
## 收尾注意
该报告属于 **Tag 完成后的审计记录追加**。记录进入 `particle/main` 的后续文档 Commit,不移动或覆盖已经完成的 V26 正式 Tag `maplibre-particle/rn/13.6.1-particle.26`;Tag 仍固定指向 `e3cd9eba3cd02149d431e0061b8d32192d876f78`。发行 Source Lock 永久记录实际构建源码 `fce326a9009d3fc21eb91c5df362f0769163d07e`,不能用之后的分支 HEAD 替换。

View File

@@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <cstdint>
#include <optional>
#include <set>
@@ -237,6 +238,72 @@ struct RenderSegmentDiagnosticsEntry {
std::optional<RenderSegmentCacheDiagnostics> cache;
};
// Platform-neutral, bounded diagnostic samples. No pointers to mutable renderer objects.
struct FastGateRetainedSample {
uint32_t segmentIndex = 0;
RenderSegmentCacheDiagnostics cache;
bool placementGatePassed = false;
std::string actualExecution;
};
struct FastGateCacheInputSample {
uint32_t segmentIndex = 0;
bool exists = false;
bool valid = false;
uint64_t buildRevision = 0;
uint64_t staticRevision = 0;
};
struct FastGatePhaseSample {
// Placement changed/fade come from the last completed render-tree snapshot
// in pre-fast. Fast tree flags do not imply a fresh Placement calculation.
// They are never substituted for the current controller gate #13.
bool hasPlacementSnapshot = false;
bool placementChanged = false;
float symbolFadeChange = 0;
bool placementTransitionActive = false;
bool retainedSamplesTruncated = false;
uint64_t contentRevision = 0;
uint64_t topologyRevision = 0;
uint64_t configRevision = 0;
uint64_t fullCpuFrame = 0;
std::optional<uint64_t> processSimulationAdvance;
std::vector<FastGateRetainedSample> retained;
};
struct FastGateFrameSample {
uint64_t sequence = 0;
uint64_t frame = 0;
int64_t updateTimeNs = 0;
uint32_t failMask = 0;
uint32_t knownMask = 0x00ffffff;
std::string firstFailure;
bool decisionConsistent = true;
bool candidate = false;
bool preFastPlacementTransition = false;
bool placementTransitionsEnabled = true;
bool completed = false;
std::string outcome{"pending"};
bool cacheInputsTruncated = false;
std::vector<FastGateCacheInputSample> cacheInputs;
FastGatePhaseSample pre;
FastGatePhaseSample post;
};
struct FastGateTraceDiagnostics {
uint32_t schemaVersion = 1;
uint64_t lifecycle = 0;
uint64_t attempts = 0;
uint64_t overwritten = 0;
uint64_t decisionMismatch = 0;
uint64_t fullRendered = 0;
uint64_t fastRendered = 0;
uint64_t noRender = 0;
std::array<uint64_t, 24> failureCounts{};
std::array<uint64_t, 24> firstFailureCounts{};
std::vector<FastGateFrameSample> frames;
};
struct RenderSegmentDiagnostics {
uint32_t schemaVersion = 1;
bool enabled = true;
@@ -274,6 +341,7 @@ struct RenderSegmentDiagnostics {
RenderMutationDiagnostics lastMutation;
std::vector<StyleLayerDiagnostics> styleLayers;
std::vector<RenderSegmentDiagnosticsEntry> segments;
std::optional<FastGateTraceDiagnostics> fastGateTrace;
};
struct ResolvedRenderSegment {

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.27")\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,21 @@
## Unreleased
## 13.6.1-particle.27(Android / iOS 统一发行)
- Mac unified release pipeline 统一生成 Android Native、RN package 与 iOS SwiftPM/XCFramework;共享 Particle revision 27。
## 13.6.1-particle.26(Android / iOS 统一发行)
- Mac unified release pipeline 统一生成 Android Native、RN package 与 iOS SwiftPM/XCFramework;共享 Particle revision 26。
## 13.6.1-particle.25(Android / iOS 统一发行)
- Mac unified release pipeline 统一生成 Android Native、RN package 与 iOS SwiftPM/XCFramework;共享 Particle revision 25。
## 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.27`。Android Native 必须配套 `cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.27`。本包是 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.27.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.27
```
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` 必须指向同一 `.27`。正常 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.27"
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.27")\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.27",
"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

@@ -63,6 +63,14 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private var step097Topology: Step097StencilFixture.Topology? = null
private var step099Scenario: Step099ReadinessFixture.Scenario? = null
private var step101Scenario: String? = null
private var step113DiagnosticsHeight: Int? = null
private var step113DiagnosticsMaxLines = 6
private var step113Scene: Step113PbfGateScene? = null
private var step113Scenario: Step113PbfGateScene.Scenario? = null
private var step114Scene: Step114FastSmokeScene? = null
private var step114Scenario: Step114FastSmokeScene.Scenario? = null
private var step114DiagnosticsHeight: Int? = null
private var step114DiagnosticsMaxLines = 6
private var step102Scenario: Step102PlacementFixture.Scenario? = null
private var step102Tick = 0L
private val step102Handler = Handler(Looper.getMainLooper())
@@ -105,6 +113,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")
@@ -327,6 +355,16 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
step093Variant = savedInstanceState?.getString(STATE_STEP093_VARIANT)
?.let { value -> Step093Variant.values().firstOrNull { it.name == value } }
?: Step093Variant.DEFAULT
val savedStep113 = if (savedInstanceState?.containsKey("step113_scenario") == true) {
savedInstanceState.getString("step113_scenario")
} else intent.getStringExtra("step113_scenario")
step113Scenario = savedStep113
?.let { value -> Step113PbfGateScene.Scenario.values().firstOrNull { it.name == value } }
val savedStep114 = if (savedInstanceState?.containsKey("step114_scenario") == true) {
savedInstanceState.getString("step114_scenario")
} else intent.getStringExtra("step114_scenario")
step114Scenario = savedStep114
?.let { value -> Step114FastSmokeScene.Scenario.values().firstOrNull { it.name == value } }
super.onCreate(savedInstanceState)
Log.i(
TAG,
@@ -339,6 +377,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
status = findViewById(R.id.validationStatus)
diagnostics = findViewById(R.id.validationDiagnostics)
mapView.addOnDidFailLoadingMapListener { errorMessage ->
step113Scene?.onStyleLoadFailed(errorMessage)
step114Scene?.onStyleLoadFailed(errorMessage)
if (navSeaPbfMode || purePbfMode) {
Log.e(TAG, "NAVSEA_PBF_STYLE_ERROR reason=$errorMessage")
} else if (realWeatherMode) {
@@ -359,7 +399,9 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
updateNavSeaPbfButtonState()
updatePbfModeButtonState()
updateRetainedCacheButtonState()
if (step102Scenario != null) loadStep102Style()
if (step114Scenario != null) startStep114Scene(step114Scenario!!)
else if (step113Scenario != null) startStep113Scene(step113Scenario!!)
else if (step102Scenario != null) loadStep102Style()
else if (step101Scenario != null) loadStep101Style()
else if (step097Topology != null) loadStep097StencilStyle()
else if (step093Mode) loadStep093Style(configureBeforeStyle = false)
@@ -668,6 +710,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
menu.add(0, 17, 17, "STEP-099 READINESS")
menu.add(0, 18, 18, "STEP-101 TEARDOWN")
menu.add(0, 19, 19, "STEP-102 PLACEMENT LOCALITY")
menu.add(0, 20, 20, "STEP-113 FZG1-PBF GATE")
menu.add(0, 21, 21, "STEP-114 独立 FAST_SMOKE(非正式 PBF)")
setOnMenuItemClickListener { item ->
when (item.itemId) {
in Step093Topology.values().indices -> applyStep093Scene(Step093Topology.values()[item.itemId])
@@ -681,6 +725,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
17 -> showStep099Menu(anchor)
18 -> showStep101Menu(anchor)
19 -> showStep102Menu(anchor)
20 -> showStep113Menu(anchor)
21 -> showStep114Menu(anchor)
}
true
}
@@ -741,6 +787,137 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
}
private fun showStep113Menu(anchor: android.view.View) {
PopupMenu(this, anchor).apply {
Step113PbfGateScene.Scenario.values().forEachIndexed { index, scenario ->
menu.add(0, index, index, "STEP-113 正式 PBF · ${scenario.name}")
}
menu.add(0, 100, 100, "EXPORT LAST SNAPSHOT")
menu.add(0, 101, 101, "MUTATE PROBE SOURCE ONCE")
menu.add(0, 102, 102, "EXIT STEP-113")
setOnMenuItemClickListener { item ->
when (item.itemId) {
in Step113PbfGateScene.Scenario.values().indices ->
startStep113Scene(Step113PbfGateScene.Scenario.values()[item.itemId])
100 -> step113Scene?.export()
101 -> step113Scene?.mutateSource()
102 -> { stopStep113Scene(); loadValidationStyle(active, enabled, "STEP113_EXIT") }
}
true
}
show()
}
}
private fun startStep113Scene(scenario: Step113PbfGateScene.Scenario) {
stopStep113Scene()
stopStep102Scenario()
stopStep099Scenario()
stopStep093DiagnosticsPolling()
step101Handler.removeCallbacksAndMessages(null)
step101Scenario = null
step097Topology = null
step093Mode = false
pureParticleMode = false
purePbfMode = false
navSeaPbfMode = false
realWeatherMode = false
weatherRuntime?.dispose()
weatherRuntime = null
styleReady = false
++styleLoadEpoch // Cancel previous asynchronous style callbacks.
step113Scenario = scenario
retainedCacheEnabled = scenario != Step113PbfGateScene.Scenario.RCRR_FULL_CONTROL_NO_TIDE
updateRetainedCacheButtonState()
step113DiagnosticsHeight = diagnostics.layoutParams.height
step113DiagnosticsMaxLines = diagnostics.maxLines
diagnostics.maxLines = 10
diagnostics.layoutParams = diagnostics.layoutParams.apply { height = (156 * resources.displayMetrics.density).toInt() }
status.text = "STEP-113 FZG1-PBF Gate · ${scenario.name} · PARTIAL"
step113Scene = Step113PbfGateScene(this, maplibreMap) { diagnostics.text = it }
.also { it.start(scenario) }
}
private fun stopStep113Scene() {
// Existing style-switch entry points all call this method; also stop the
// independent smoke before another scene takes ownership of this Map.
stopStep114Scene()
step113Scene?.stop()
step113DiagnosticsHeight?.let { originalHeight ->
diagnostics.maxLines = step113DiagnosticsMaxLines
diagnostics.layoutParams = diagnostics.layoutParams.apply { height = originalHeight }
}
step113DiagnosticsHeight = null
step113Scene = null
step113Scenario = null
}
private fun showStep114Menu(anchor: android.view.View) {
PopupMenu(this, anchor).apply {
Step114FastSmokeScene.Scenario.values().forEachIndexed { index, scenario ->
menu.add(0, index, index, "独立 smoke · ${scenario.name}")
}
listOf("导出当前 Native 快照", "单次更新 R GeoJSON", "Pan/Zoom 往返", "Reload 当前场景",
"单次 Image 变更", "禁用/恢复缓存", "单次更新真实 Annotation", "退出独立 smoke")
.forEachIndexed { index, title -> menu.add(0, 10 + index, 10 + index, title) }
setOnMenuItemClickListener { item ->
when (item.itemId) {
in Step114FastSmokeScene.Scenario.values().indices ->
startStep114Scene(Step114FastSmokeScene.Scenario.values()[item.itemId])
10 -> step114Scene?.export()
11 -> step114Scene?.mutateSource()
12 -> step114Scene?.panZoom()
13 -> step114Scene?.reload()
14 -> step114Scene?.mutateImage()
15 -> step114Scene?.toggleCache()
16 -> step114Scene?.mutateAnnotation()
17 -> { stopStep113Scene(); loadValidationStyle(active, enabled, "STEP114_EXIT") }
}
true
}
show()
}
}
private fun startStep114Scene(scenario: Step114FastSmokeScene.Scenario) {
stopStep113Scene()
stopStep102Scenario()
stopStep099Scenario()
stopStep093DiagnosticsPolling()
step101Handler.removeCallbacksAndMessages(null)
step101Scenario = null
step097Topology = null
step093Mode = false
pureParticleMode = false
purePbfMode = false
navSeaPbfMode = false
realWeatherMode = false
weatherRuntime?.dispose()
weatherRuntime = null
styleReady = false
++styleLoadEpoch
step114Scenario = scenario
retainedCacheEnabled = true
updateRetainedCacheButtonState()
step114DiagnosticsHeight = diagnostics.layoutParams.height
step114DiagnosticsMaxLines = diagnostics.maxLines
diagnostics.maxLines = 10
diagnostics.layoutParams = diagnostics.layoutParams.apply { height = (156 * resources.displayMetrics.density).toInt() }
status.text = "STEP-114 独立 smoke · $scenario · 待真实 Native 验收"
step114Scene = Step114FastSmokeScene(this, maplibreMap) { diagnostics.text = it }.also { it.start(scenario) }
}
private fun stopStep114Scene() {
step114Scene?.stop()
step114DiagnosticsHeight?.let { originalHeight ->
diagnostics.maxLines = step114DiagnosticsMaxLines
diagnostics.layoutParams = diagnostics.layoutParams.apply { height = originalHeight }
}
step114DiagnosticsHeight = null
step114Scene = null
step114Scenario = null
}
private fun showStep102Menu(anchor: android.view.View) {
PopupMenu(this, anchor).apply {
Step102PlacementFixture.Scenario.values().forEachIndexed { index, scenario ->
@@ -758,6 +935,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun loadStep102Style() {
stopStep113Scene()
val scenario = step102Scenario ?: return
stopStep093DiagnosticsPolling()
step093Mode = false
@@ -790,6 +968,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun loadStep101Style() {
stopStep113Scene()
val scenario = step101Scenario ?: return
stopStep102Scenario()
stopStep099Scenario()
@@ -816,6 +995,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun applyStep099Scenario(scenario: Step099ReadinessFixture.Scenario) {
stopStep113Scene()
stopStep102Scenario()
stopStep093DiagnosticsPolling()
step093Mode = false
@@ -951,6 +1131,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun loadStep097StencilStyle() {
stopStep113Scene()
val topology = step097Topology ?: return
styleReady = false
val epoch = ++styleLoadEpoch
@@ -1104,6 +1285,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun loadStep093Style(configureBeforeStyle: Boolean) {
stopStep113Scene()
weatherRuntime?.dispose()
weatherRuntime = null
styleReady = false
@@ -1601,6 +1783,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun loadValidationStyle(active: Boolean, enabled: Boolean, reason: String = "STYLE_CHANGE") {
stopStep113Scene()
stopStep102Scenario()
if (step099Scenario != null) stopStep099Scenario()
step097V4GoldenReady = false
@@ -1672,6 +1855,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
)
private fun loadPureParticleStyle(active: Boolean, enabled: Boolean, reason: String) {
stopStep113Scene()
weatherRuntime?.dispose()
weatherRuntime = null
this.active = active
@@ -1701,6 +1885,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun loadPurePbfStyle(reason: String) {
stopStep113Scene()
weatherRuntime?.dispose()
weatherRuntime = null
styleReady = false
@@ -1732,6 +1917,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun loadNavSeaPbfStyle(active: Boolean, enabled: Boolean, reason: String) {
stopStep113Scene()
weatherRuntime?.dispose()
weatherRuntime = null
this.active = active
@@ -2377,8 +2563,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
} else if (step093Mode || step097Topology != null) startStep093DiagnosticsPolling()
}
override fun onResume() { super.onResume(); mapView.onResume() }
override fun onPause() { mapView.onPause(); super.onPause() }
override fun onResume() { super.onResume(); mapView.onResume(); step113Scene?.resume(); step114Scene?.resume() }
override fun onPause() { step113Scene?.pause(); step114Scene?.pause(); mapView.onPause(); super.onPause() }
override fun onStop() {
step101Handler.removeCallbacksAndMessages(null)
step093Handler.removeCallbacks(step093DiagnosticsPoll)
@@ -2388,6 +2574,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
super.onStop()
}
override fun onDestroy() {
stopStep113Scene()
step102Handler.removeCallbacksAndMessages(null)
step101Handler.removeCallbacksAndMessages(null)
if (step101Scenario != null) Log.i(TAG, "STEP101_MAPVIEW_DESTROY_ENTER scenario=$step101Scenario completed=$step101Completed")
@@ -2402,6 +2589,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
override fun onLowMemory() { super.onLowMemory(); mapView.onLowMemory() }
override fun onSaveInstanceState(outState: Bundle) {
outState.putString("step113_scenario", step113Scenario?.name)
outState.putString("step114_scenario", step114Scenario?.name)
outState.putBoolean(STATE_ACTIVE, active)
outState.putBoolean(STATE_ENABLED, enabled)
outState.putInt(STATE_MASK_MODE, maskMode)

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

@@ -0,0 +1,576 @@
package org.maplibre.android.testapp.activity.particle
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.util.Base64
import android.util.Log
import org.json.JSONArray
import org.json.JSONObject
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.Style
import org.maplibre.android.maps.WeatherRuntime
import org.maplibre.android.style.layers.Layer
import org.maplibre.android.style.layers.SymbolLayer
import org.maplibre.android.style.sources.GeoJsonSource
import org.maplibre.android.testapp.BuildConfig
import org.maplibre.geojson.Feature
import org.maplibre.geojson.Point
import java.io.File
import java.security.MessageDigest
/** STEP-113: real remote chart, explicit partial parity, fixed forecast and low-frequency pull. */
internal class Step113PbfGateScene(
private val context: Context,
private val map: MapLibreMap,
private val display: (String) -> Unit
) {
enum class Scenario { PBF63, STATIC_RC, PARTICLE_OFF, PARTIAL_RCRR, SOURCE_CHANGE, STATIC_RC_NO_TIDE,
RCRR_C_NARROW_NO_TIDE, RCRR_C_WIDE_NO_TIDE, RCRR_R_DYNAMIC_NO_TIDE,
RCR_PARTICLE_ONLY_NO_TIDE, RCRRR_PARTICLE_ONLY_NO_TIDE, RCRR_FULL_CONTROL_NO_TIDE }
private val handler = Handler(Looper.getMainLooper())
private var generation = 0
private var running = false
private var paused = false
private var ready = false
private var failureReason: String? = null
private var runtime: WeatherRuntime? = null
private var scenario = Scenario.PBF63
private var parity = JSONObject()
private var latest: JSONObject? = null
private var lastLogMs = 0L
private var mutation = 0
private var exportSequence = 0L
private var commandResults = JSONObject()
private var savedGestures: List<Boolean>? = null
private var annotationStyle: Style? = null
private var detachedEmptyAnnotationLayer: Layer? = null
private val poll = object : Runnable {
override fun run() {
if (!running || paused) return
if (running) {
try {
checkAnnotationOwnership()
// One shared snapshot query per refresh; all UI/log/export uses this copy.
val native = JSONObject(map.getRenderSegmentDiagnostics())
latest = envelope(native)
val trace = native.optJSONObject("fastGateTrace")
val frames = trace?.optJSONArray("frames")
val lastFrame = if (frames != null && frames.length() > 0) frames.optJSONObject(frames.length() - 1) else null
val firstFailure = lastFrame?.optString("firstFailure") ?: native.optString("fastPathFallbackReason")
val gates = trace?.optJSONArray("gates") ?: JSONArray()
val rank = (0 until gates.length()).map { gates.getJSONObject(it) }
.sortedByDescending { it.optLong("failures") }.take(4)
val segments = native.optJSONArray("segments") ?: JSONArray()
display(buildString {
append("STEP-113 $scenario · ${if (ready) "READY/PARTIAL" else failureReason ?: "LOADING"} · actual=${native.optInt("styleLayerCount")}\n")
append("first=$firstFailure seq=${lastFrame?.optLong("sequence")} full=${trace?.optLong("fullRendered")} ")
append("window=${trace?.optInt("windowCount")} mismatch=${trace?.optLong("decisionMismatch")}\n")
append("Fast=${trace?.optLong("fastRendered")} · 空Annotation调整=${commandResults.optBoolean("emptyBuiltinAnnotationLayerRemoved")}\n")
append(rank.joinToString(" / ") { "${it.optString("reason")}=${it.optLong("failures")}" })
for (i in 0 until segments.length()) {
val s = segments.getJSONObject(i)
val c = s.optJSONObject("cache")
append("\n${s.optString("strategy")}[${s.optInt("beginIndex")},${s.optInt("endExclusive")}) ")
append("${c?.optString("cacheState") ?: "live"} B=${c?.optLong("build") ?: 0} ")
append("R=${c?.optLong("reuse") ?: 0} I=${c?.optLong("invalidation") ?: 0}")
}
})
val now = SystemClock.elapsedRealtime()
val summaryIntervalMs = if (isCpuBenchmark(scenario)) 6000L else 30000L
if (now - lastLogMs >= summaryIntervalMs) {
lastLogMs = now
logRecord("summary", summary(native))
}
} catch (error: Exception) {
display("STEP-113 诊断读取失败: ${error.message}")
Log.e(TAG, "diagnostic-read-failed", error)
}
}
handler.postDelayed(this, 3000L)
}
}
fun start(next: Scenario) {
stop()
running = true
scenario = next
ready = false
failureReason = null
latest = null
parity = JSONObject()
lastLogMs = 0L
mutation = 0
commandResults = JSONObject()
val epoch = ++generation
display("STEP-113 $scenario · 加载正式 PBF · 113层完整等价未验证")
val ui = map.uiSettings
savedGestures = listOf(ui.isScrollGesturesEnabled(), ui.isRotateGesturesEnabled(),
ui.isTiltGesturesEnabled(), ui.isZoomGesturesEnabled(),
ui.isDoubleTapGesturesEnabled(), ui.isQuickZoomGesturesEnabled())
ui.setAllGesturesEnabled(false)
map.cancelTransitions()
// CPU A/B control: exactly the same style, camera, weather and RCRR plan.
// Only the retained-cache enable bit differs.
val fullControl = scenario == Scenario.RCRR_FULL_CONTROL_NO_TIDE
map.setRetainedUnderlayEnabled(!fullControl)
commandResults.put("forceFullControl", fullControl)
if (!paused) handler.post(poll)
map.setStyle(Style.Builder().fromUri(STYLE_URL)) production@ { loaded ->
if (!running || epoch != generation) return@production
try {
val root = JSONObject(loaded.json)
val baseLayers = root.getJSONArray("layers")
val base = JSONArray(baseLayers.toString())
check(base.length() == 63) { "正式基础层数已变化: ${base.length()},保留现场" }
val ids = (0 until base.length()).map { base.getJSONObject(it).getString("id") }
check(ids.distinct().size == ids.size && "clip-outline" in ids && "sea-area-fill" in ids &&
"special-pattern-line-754" in ids) { "正式 Style 边界 ID 缺失或重复" }
// STEP-118: Keep the original 63-layer PBF baseline unchanged.
// Only the explicitly named user-approved variant excludes one
// orphaned Symbol layer whose tide Source is already absent.
val omitTideLayer = isNoTideVariant(scenario)
val tideDependent = (0 until base.length()).filter {
base.getJSONObject(it).optString("source") == TIDE_SOURCE
}
if (omitTideLayer) {
check(!root.getJSONObject("sources").has(TIDE_SOURCE)) {
"正式Style出现真实tide Source;停止本次仅移除孤立层实验"
}
check(tideDependent.size == 1 &&
base.getJSONObject(tideDependent.single()).optString("id") == TIDE_LAYER &&
base.getJSONObject(tideDependent.single()).optString("type") == "symbol") {
"tide引用结构不符合已核验的唯一孤立Symbol层;禁止扩大删除范围"
}
}
val effectiveBase = if (omitTideLayer) JSONArray().apply {
for (i in 0 until base.length()) {
if (i !in tideDependent) put(base.getJSONObject(i))
}
} else base
val effectiveIds = (0 until effectiveBase.length()).map { effectiveBase.getJSONObject(it).getString("id") }
check(!omitTideLayer || (effectiveBase.length() == 62 && TIDE_LAYER !in effectiveIds)) {
"tide移除数量不等于1;停止"
}
val source = root.getJSONObject("sources").getJSONObject("navsea_delivery")
check(root.optString("sprite") == SPRITE && root.optString("glyphs") == GLYPHS &&
source.optString("type") == "vector" && source.optJSONArray("tiles")?.optString(0) == TILES) {
"正式 Sprite/Glyph/VectorTile URI 与 NavSea URL 事实源不一致"
}
parity = JSONObject().put("evidence", "TEST-APP-STRUCTURAL")
.put("styleUrl", STYLE_URL).put("styleVersion", root.optInt("version"))
.put("styleVersionBasis", "Style specification version; 包版本未知")
.put("stylePackageVersionObserved", JSONObject.NULL)
.put("styleMetadata", root.opt("metadata") ?: JSONObject.NULL)
.put("styleName", root.optString("name"))
.put("fingerprintBasis", "SDK-returned-production-json-utf8")
.put("productionFingerprintSha256", sha256(loaded.json))
.put("baseLayerCount", base.length()).put("baseLayersPreserved", !omitTideLayer)
.put("effectiveBaseLayerCount", effectiveBase.length())
.put("tideLayerExcluded", omitTideLayer)
.put("excludedLayerId", if (omitTideLayer) TIDE_LAYER else JSONObject.NULL)
.put("excludedLayerSource", if (omitTideLayer) TIDE_SOURCE else JSONObject.NULL)
.put("excludedLayerCount", if (omitTideLayer) 1 else 0)
.put("tideOriginalSourcePresent", root.getJSONObject("sources").has(TIDE_SOURCE))
.put("full113Equivalent", false).put("missingProductionDynamicLayerDefinitions", true)
.put("dynamicParity", "测试场结构模式;缺少同版本113层完整Source/Layer/visibility/数据快照")
.put("navSeaSourceHead", "ad9aa043fde37f87462f31de3fbd8ce008f8424b")
.put("navSeaSourceSelector", "RenderSegmentHost.tsx:RC@rn-weather-color")
.put("deviceReferenceSelector", "用户STEP-112:RCRR_PREFIX[0,13)/[13,33)/[33,107)/[107,113)")
.put("baseLayerManifest", layerManifest(base))
.put("effectiveBaseLayerManifest", layerManifest(effectiveBase))
.put("sprite", root.optString("sprite"))
.put("glyphs", root.optString("glyphs")).put("vectorSource", JSONObject(source.toString()))
.put("productionSources", JSONObject(root.getJSONObject("sources").toString()))
.put("internalAnnotationPolicy", "STATIC_RC及全部NO_TIDE场景加载时移除空SDK辅助层;后续添加不再移除")
.put("styleAdjustmentScope", if (omitTideLayer)
"TEST-APP-ONLY;原生产Style63层不变;测试副本排除唯一tide-spots→tide孤立Symbol层" else
"NONE;生产Style基础63层原样")
val hasField = scenario != Scenario.PBF63
if (hasField) {
val sources = root.getJSONObject("sources")
check(!sources.has(SOURCE)) { "天气 Source ID 冲突" }
sources.put(SOURCE, JSONObject().put("type", "raster-array").put("host-field-submission", true))
val layers = JSONArray()
val prefix = isSegmentedScenario(scenario)
for (i in 0 until effectiveBase.length()) {
layers.put(effectiveBase.getJSONObject(i))
if (prefix && effectiveBase.getJSONObject(i).getString("id") == "clip-outline") addFields(layers)
}
if (!prefix) addFields(layers) // NavSea current source fragment appends to top.
if (scenario == Scenario.SOURCE_CHANGE) {
sources.put(PROBE_SOURCE, JSONObject().put("type", "geojson")
.put("data", JSONObject().put("type", "FeatureCollection").put("features", JSONArray())))
layers.put(JSONObject().put("id", PROBE_LAYER).put("type", "circle")
.put("source", PROBE_SOURCE).put("paint", JSONObject()
.put("circle-radius", 6).put("circle-color", "#ff8800")))
}
root.put("layers", layers)
}
// Prove all non-excluded production definitions, including
// visibility and order, remain byte-equivalent within the JSON.
// The original 63-layer manifest/fingerprint is kept separately.
val finalLayers = root.getJSONArray("layers")
val retainedBase = (0 until finalLayers.length()).map { finalLayers.getJSONObject(it) }
.filter { it.getString("id") in ids }
check(retainedBase.size == effectiveBase.length() &&
retainedBase.map { it.getString("id") } == effectiveIds &&
retainedBase.indices.all {
retainedBase[it].toString() == effectiveBase.getJSONObject(it).toString()
} && (!omitTideLayer || retainedBase.none { it.optString("source") == TIDE_SOURCE })) {
"修改后的基础图层集合与用户授权的单一tide排除不一致"
}
parity.put("testStyleFingerprintSha256", sha256(root.toString()))
.put("testJsonLayerCount", finalLayers.length())
.put("baseParityBasis", if (omitTideLayer)
"同次SDK生产Style63层回读;测试副本仅排除tide-spots,其余62层定义逐项比较;不等同原63层像素验收" else
"同次SDK生产Style回读;完整定义逐层比较,不是设备画面证明")
val clipIndex = effectiveIds.indexOf("clip-outline")
check(clipIndex >= 0 && clipIndex + 1 < effectiveIds.size) {
"生产Style的clip-outline之后没有基础图层;不可推断RCRR天气位置"
}
val plan = planJson(scenario, effectiveIds.last(), effectiveIds[clipIndex + 1])
parity.put("testPlanSelector", when (scenario) {
Scenario.PARTIAL_RCRR -> "旧RCRR缺源负对照:R0包含缺源tide,C1混合天气与大量静态PBF"
Scenario.RCRR_C_NARROW_NO_TIDE -> "RCRR粒子独占C:WeatherColor及海图均在R"
Scenario.RCRR_C_WIDE_NO_TIDE -> "RCRR宽C:天气及部分PBF;后续静态R"
Scenario.RCRR_R_DYNAMIC_NO_TIDE -> "RCRR负例:粒子进入R,C含普通海图"
Scenario.RCR_PARTICLE_ONLY_NO_TIDE -> "RCR粒子独占C:与RCRR同Style测试缓存分段开销"
Scenario.RCRRR_PARTICLE_ONLY_NO_TIDE -> "RCRRR粒子独占C:增加R段测试分段开销"
Scenario.RCRR_FULL_CONTROL_NO_TIDE -> "与粒子独占C的RCRR同Style/Plan;缓存禁用Full CPU基线"
else -> if (hasField) "当前NavSea源码RC:rn-weather-color" else "基础63层全RETAINED"
})
.put("requestedPlan", JSONObject(plan))
commandResults.put("planAccepted", map.setRenderSegmentPlan(plan))
map.setStyle(Style.Builder().fromJson(root.toString())) testStyle@ { style ->
if (!running || epoch != generation) return@testStyle
try {
if (scenario == Scenario.STATIC_RC || isNoTideVariant(scenario)) {
removeEmptyAnnotationLayer(style, ids)
}
map.cameraPosition = CameraPosition.Builder().target(LatLng(33.45, 129.97))
.zoom(10.5).bearing(0.0).tilt(0.0).build()
if (hasField) initializeFields()
ready = true
lastLogMs = 0L
handler.removeCallbacksAndMessages(null)
if (!paused) handler.post(poll)
} catch (error: Exception) { fail(error) }
}
} catch (error: Exception) { fail(error) }
}
}
private fun removeEmptyAnnotationLayer(style: Style, baseIds: List<String>) {
check(ANNOTATION_LAYER !in baseIds && map.annotations.isEmpty()) {
"存在生产同名层或遗留Annotation,禁止移除辅助层"
}
check(!parity.getJSONObject("productionSources").has(ANNOTATION_SOURCE)) {
"生产Style占用内部Annotation Source ID,禁止推断为空SDK依赖"
}
val layer = style.getLayer(ANNOTATION_LAYER)
check(layer is SymbolLayer && layer.sourceId == ANNOTATION_SOURCE) {
"内建Annotation层身份不符,保留现场"
}
check(style.getSource(ANNOTATION_SOURCE) != null) { "内建Annotation Source缺失" }
// The object overload preserves the detached wrapper for restoration.
check(style.removeLayer(layer)) { "空内建Annotation层移除失败" }
annotationStyle = style
detachedEmptyAnnotationLayer = layer
check(style.getSource(ANNOTATION_SOURCE) != null) { "不得移除内建Source" }
commandResults.put("emptyBuiltinAnnotationLayerRemoved", true)
.put("annotationInstancesAtRemoval", 0).put("annotationSourcePreserved", true)
parity.put("removedInternalLayer", ANNOTATION_LAYER)
.put("adjustmentScope", "STATIC_RC一次;仅SDK空辅助层;非生产63层")
logRecord("annotation-policy", JSONObject(commandResults.toString()).put("scenario", scenario.name))
}
private fun restoreEmptyAnnotationLayer(reason: String) {
val style = annotationStyle
val layer = detachedEmptyAnnotationLayer
try {
if (style != null && layer != null && map.style === style && style.getLayer(ANNOTATION_LAYER) == null) {
style.addLayer(layer)
}
commandResults.put("annotationRemovalEnded", reason)
} finally {
annotationStyle = null
detachedEmptyAnnotationLayer = null
}
}
private fun checkAnnotationOwnership() {
val style = annotationStyle ?: return
if (map.style !== style) {
restoreEmptyAnnotationLayer("style-replaced")
fail(IllegalStateException("Style已替换,空层调整停止;新Style图层原样保留"))
} else if (map.annotations.isNotEmpty() || style.getLayer(ANNOTATION_LAYER) != null) {
// Native AnnotationManager restores the point layer synchronously
// at add/update, before Map::onUpdate. Polling only reports revocation.
restoreEmptyAnnotationLayer("annotation-or-layer-added")
fail(IllegalStateException("Annotation/辅助层已添加,保留内容并撤销STATIC_RC空层验收条件"))
}
}
private fun addFields(layers: JSONArray) {
layers.put(JSONObject().put("id", COLOR).put("type", "weather-color").put("source", SOURCE)
.put("paint", JSONObject().put("weather-color-opacity", 0.62)))
layers.put(JSONObject().put("id", PARTICLE).put("type", "raster-particle").put("source", SOURCE)
.put("layout", JSONObject().put("visibility", if (scenario == Scenario.PARTICLE_OFF) "none" else "visible"))
.put("paint", JSONObject().put("raster-particle-active", true).put("raster-particle-particle-count", 1024)))
}
private fun initializeFields() {
val host = WeatherRuntime(map, WeatherRuntime.Config(SOURCE, COLOR, PARTICLE))
runtime = host
// Existing real-weather canonical assets. Explicitly not the current NavSea forecast/QBIN feed.
val results = JSONArray()
for (cycle in listOf("2026080700", "2026080706")) {
val path = "integration/particle-weatherv2/ecmwf-hres-wind-20260807/$cycle"
val meta = context.assets.open("$path/metadata.json").bufferedReader().use { it.readText() }
val data = context.assets.open("$path/field.f32").use { it.readBytes() }
val result = host.submitWindField(meta, data)
results.put(result.name)
check(result.accepted()) { "Field提交拒绝: $cycle $result" }
}
commandResults.put("fieldSubmit", results)
.put("forecastSeek", host.setForecastTime(FORECAST_MS).name)
.put("forecastPause", host.setPlaybackState(WeatherRuntime.PlaybackState.PAUSE).name)
.put("particleVisibility", host.setParticleVisible(scenario != Scenario.PARTICLE_OFF).name)
.put("targetFps", host.setTargetFps(WeatherRuntime.TargetFps.FPS_30).name)
check(host.state == WeatherRuntime.State.READY &&
commandResults.getString("forecastSeek") == "ACCEPTED" &&
commandResults.getString("forecastPause") == "ACCEPTED" &&
commandResults.getString("particleVisibility") == "ACCEPTED" &&
commandResults.getString("targetFps") == "ACCEPTED") { "固定预报/30fps命令未全部接受: $commandResults" }
}
fun mutateSource() {
if (!running || !ready || scenario != Scenario.SOURCE_CHANGE) return
val source = map.style?.getSourceAs<GeoJsonSource>(PROBE_SOURCE) ?: return
source.setGeoJson(Feature.fromGeometry(Point.fromLngLat(129.97 + (++mutation % 2) * 0.01, 33.45)))
// Single explicit mutation; polling never changes Source, Symbol, forecast or camera.
commandResults.put("sourceMutationCount", mutation)
logRecord("source-command", JSONObject().put("schemaVersion", 1).put("scenario", scenario.name)
.put("mutation", mutation).put("source", PROBE_SOURCE).put("evidence", "TEST-APP-STRUCTURAL"))
}
private fun envelope(native: JSONObject): JSONObject = JSONObject().put("schema", "step113-pbf-fast-gate")
.put("schemaVersion", 1).put("appVersion", BuildConfig.VERSION_NAME)
.put("scenario", scenario.name).put("sceneGeneration", generation)
.put("sceneReady", ready).put("blockedReason", failureReason ?: JSONObject.NULL)
.put("sampleElapsedMs", SystemClock.elapsedRealtime()).put("styleParity", parity)
.put("cameraRequested", JSONObject().put("latitude", 33.45).put("longitude", 129.97)
.put("zoom", 10.5).put("bearing", 0.0).put("pitch", 0.0).put("gesturesEnabled", false))
.put("commands", JSONObject(commandResults.toString()))
.put("forecastRequestedMs", if (runtime != null) FORECAST_MS else JSONObject.NULL)
.put("actualForecastTimeObservedMs", JSONObject.NULL)
.put("forecastStatus", runtime?.playbackState?.name ?: "NO_FIELD")
.put("fieldParity", "既有ECMWF canonical双帧测试数据;非NavSea当前业务预报")
.put("particleTick", JSONObject().put("targetFps", runtime?.targetFps?.name ?: "NONE")
.put("state", runtime?.state?.name ?: "NO_FIELD").put("particleVisible", scenario != Scenario.PARTICLE_OFF && runtime != null)
.put("simulationCounterScope", "process-debug-counter; 非本Map独占计数")
.put("processSimulationAdvance", lastPost(native)?.opt("processSimulationAdvance") ?: JSONObject.NULL))
.put("native", native)
private fun lastPost(native: JSONObject): JSONObject? {
val frames = native.optJSONObject("fastGateTrace")?.optJSONArray("frames") ?: return null
return if (frames.length() > 0) frames.optJSONObject(frames.length() - 1)?.optJSONObject("post") else null
}
private fun summary(native: JSONObject): JSONObject {
val t = native.optJSONObject("fastGateTrace") ?: JSONObject()
val gates = t.optJSONArray("gates") ?: JSONArray()
val counts = JSONArray()
for (i in 0 until gates.length()) {
val g = gates.getJSONObject(i)
counts.put(JSONArray().put(g.optString("reason")).put(g.optLong("failures")).put(g.optLong("firstFailures")))
}
val rs = JSONArray()
val segmentRanges = JSONArray()
val segments = native.optJSONArray("segments") ?: JSONArray()
for (i in 0 until segments.length()) {
val s = segments.getJSONObject(i)
segmentRanges.put(JSONObject().put("strategy", s.optString("strategy"))
.put("begin", s.optInt("beginIndex")).put("end", s.optInt("endExclusive")))
val c = s.optJSONObject("cache") ?: continue
rs.put(JSONObject().put("segment", s.optInt("segmentIndex")).put("begin", s.optInt("beginIndex"))
.put("end", s.optInt("endExclusive")).put("state", c.optString("cacheState"))
.put("build", c.optLong("build")).put("reuse", c.optLong("reuse"))
.put("invalidation", c.optLong("invalidation")).put("fallback", c.optLong("fallbackCount"))
.put("reason", c.optString("lastFallbackReason")))
rs.getJSONObject(rs.length() - 1)
.put("missingSourceIds", s.optJSONArray("missingSourceIds") ?: JSONArray())
.put("placementCapable", s.optBoolean("placementCapable"))
.put("placementGatePassed", s.optBoolean("placementGatePassed"))
}
return JSONObject().put("schema", "step113-summary").put("schemaVersion", 1).put("scenario", scenario.name)
.put("sampleElapsedMs", SystemClock.elapsedRealtime())
.put("sceneReady", ready).put("blockedReason", failureReason ?: JSONObject.NULL)
.put("sceneGeneration", generation).put("full113Equivalent", false).put("basePreserved", parity.optBoolean("baseLayersPreserved"))
.put("originalBaseLayerCount", parity.optInt("baseLayerCount"))
.put("effectiveBaseLayerCount", parity.optInt("effectiveBaseLayerCount"))
.put("tideLayerExcluded", parity.optBoolean("tideLayerExcluded"))
.put("excludedLayerId", parity.opt("excludedLayerId") ?: JSONObject.NULL)
.put("excludedLayerSource", parity.opt("excludedLayerSource") ?: JSONObject.NULL)
.put("excludedLayerCount", parity.optInt("excludedLayerCount"))
.put("fingerprint", parity.optString("productionFingerprintSha256")).put("styleUrl", STYLE_URL)
.put("actualLayers", native.optInt("styleLayerCount")).put("planValid", native.optBoolean("valid"))
.put("planReason", native.optString("lastReason")).put("lifecycle", t.optLong("lifecycle"))
.put("attempts", t.optLong("attempts")).put("windowCount", t.optInt("windowCount"))
.put("windowBegin", t.optLong("windowBeginSequence")).put("windowEnd", t.optLong("windowEndSequence"))
.put("windowFullRendered", t.optLong("windowFullRendered"))
.put("windowFastRendered", t.optLong("windowFastRendered"))
.put("processSimulationAdvance", lastPost(native)?.opt("processSimulationAdvance") ?: JSONObject.NULL)
.put("overwritten", t.optLong("overwritten")).put("decisionMismatch", t.optLong("decisionMismatch"))
.put("fullRendered", t.optLong("fullRendered")).put("fastRendered", t.optLong("fastRendered"))
.put("gateFailureCounts", counts).put("segmentRanges", segmentRanges).put("retained", rs)
.put("commands", JSONObject(commandResults.toString()))
.put("sources", native.optJSONArray("sources") ?: JSONArray())
.put("placementChanged", native.optBoolean("placementChanged"))
.put("placementTransitionActive", native.optBoolean("placementTransitionActive"))
.put("symbolFadeChange", native.opt("symbolFadeChange") ?: JSONObject.NULL)
}
fun export() {
// Export exactly the last low-frequency shared snapshot; do not request an extra render/query.
val record = latest ?: return
val file = File(context.filesDir, "step113-${scenario.name}-${SystemClock.elapsedRealtime()}.json")
try {
file.writeText(record.toString(), Charsets.UTF_8)
logRecord("snapshot", record)
display("STEP-113 已导出 ${file.name};Logcat标签 $TAG")
} catch (error: Exception) { Log.e(TAG, "export-failed", error) }
}
private fun logRecord(kind: String, record: JSONObject) {
// Logcat has a per-entry limit. UTF-8 -> Base64 ensures chunks never split Unicode.
val data = Base64.encodeToString(record.toString().toByteArray(Charsets.UTF_8), Base64.NO_WRAP)
val chunks = data.chunked(2400)
val id = "${SystemClock.elapsedRealtime()}-${generation}-${++exportSequence}"
chunks.forEachIndexed { index, chunk ->
Log.i(TAG, JSONObject().put("schemaVersion", 1).put("kind", kind).put("exportId", id)
.put("chunkIndex", index).put("chunkCount", chunks.size).put("encoding", "base64-utf8")
.put("payload", chunk).toString())
}
}
fun onStyleLoadFailed(message: String) {
if (running) fail(IllegalStateException(message))
}
private fun fail(error: Exception) {
ready = false
failureReason = "BLOCKED: ${error.message}"
display("STEP-113 BLOCKED: ${error.message}")
Log.e(TAG, "scene-blocked", error)
logRecord("blocked", JSONObject().put("schemaVersion", 1).put("scenario", scenario.name)
.put("reason", error.message).put("styleUrl", STYLE_URL).put("evidence", "NOT-VERIFIED"))
}
fun pause() { paused = true; handler.removeCallbacksAndMessages(null) }
fun resume() {
paused = false
handler.removeCallbacksAndMessages(null)
if (running) handler.post(poll)
}
fun stop() {
running = false
++generation
handler.removeCallbacksAndMessages(null)
try { restoreEmptyAnnotationLayer("scene-stopped") }
catch (error: Exception) { Log.e(TAG, "annotation-layer-restore-failed", error) }
runtime?.dispose()
runtime = null
savedGestures?.let { flags ->
val ui = map.uiSettings
ui.setScrollGesturesEnabled(flags[0])
ui.setRotateGesturesEnabled(flags[1])
ui.setTiltGesturesEnabled(flags[2])
ui.setZoomGesturesEnabled(flags[3])
ui.setDoubleTapGesturesEnabled(flags[4])
ui.setQuickZoomGesturesEnabled(flags[5])
}
savedGestures = null
ready = false
}
companion object {
const val TAG = "STEP113Gate"
const val STYLE_URL = "https://pbf.jpcn.ccwu.cc/style/style.navsea-delivery-full-semantic-extentfix.json"
private const val SPRITE = "https://pbf.jpcn.ccwu.cc/sprite/sprite"
private const val GLYPHS = "https://pbf.jpcn.ccwu.cc/fonts/{fontstack}/{range}.pbf"
private const val TILES = "https://pbf.jpcn.ccwu.cc/pbf/pbf-delivery-full-20260418-rebuild/{z}/{x}/{y}.pbf"
private const val SOURCE = "rn-weather-fields"
private const val COLOR = "rn-weather-color"
private const val PARTICLE = "rn-weather-particles"
private const val PROBE_SOURCE = "step113-source-probe"
private const val PROBE_LAYER = "step113-source-probe-circle"
private const val TIDE_SOURCE = "tide"
private const val TIDE_LAYER = "tide-spots"
private const val ANNOTATION_SOURCE = "org.maplibre.annotations"
private const val ANNOTATION_LAYER = "org.maplibre.annotations.points"
private const val FORECAST_MS = 1786060800000L
private fun isSegmentedScenario(s: Scenario): Boolean = s == Scenario.PARTIAL_RCRR ||
s == Scenario.RCRR_C_NARROW_NO_TIDE || s == Scenario.RCRR_C_WIDE_NO_TIDE ||
s == Scenario.RCRR_R_DYNAMIC_NO_TIDE || s == Scenario.RCR_PARTICLE_ONLY_NO_TIDE ||
s == Scenario.RCRRR_PARTICLE_ONLY_NO_TIDE || s == Scenario.RCRR_FULL_CONTROL_NO_TIDE
private fun isCpuBenchmark(s: Scenario): Boolean = s == Scenario.RCRR_C_NARROW_NO_TIDE ||
s == Scenario.RCR_PARTICLE_ONLY_NO_TIDE || s == Scenario.RCRRR_PARTICLE_ONLY_NO_TIDE ||
s == Scenario.RCRR_FULL_CONTROL_NO_TIDE
private fun isNoTideVariant(s: Scenario): Boolean = s == Scenario.STATIC_RC_NO_TIDE ||
s == Scenario.RCRR_C_NARROW_NO_TIDE || s == Scenario.RCRR_C_WIDE_NO_TIDE ||
s == Scenario.RCRR_R_DYNAMIC_NO_TIDE || s == Scenario.RCR_PARTICLE_ONLY_NO_TIDE ||
s == Scenario.RCRRR_PARTICLE_ONLY_NO_TIDE || s == Scenario.RCRR_FULL_CONTROL_NO_TIDE
private fun planJson(s: Scenario, finalBaseId: String, firstAfterClipId: String): String {
val segments = JSONArray().put(JSONObject().put("strategy", "RETAINED"))
if (s != Scenario.PBF63) {
val continuousStart = when (s) {
Scenario.PARTIAL_RCRR, Scenario.RCRR_C_WIDE_NO_TIDE -> "sea-area-fill"
Scenario.RCRR_R_DYNAMIC_NO_TIDE -> firstAfterClipId
else -> if (s == Scenario.RCRR_C_NARROW_NO_TIDE ||
s == Scenario.RCR_PARTICLE_ONLY_NO_TIDE ||
s == Scenario.RCRRR_PARTICLE_ONLY_NO_TIDE ||
s == Scenario.RCRR_FULL_CONTROL_NO_TIDE) PARTICLE else COLOR
}
segments.put(JSONObject().put("strategy", "CONTINUOUS").put("startLayerId", continuousStart))
if (isSegmentedScenario(s)) {
val particleOnlyC = s == Scenario.RCRR_C_NARROW_NO_TIDE ||
s == Scenario.RCR_PARTICLE_ONLY_NO_TIDE ||
s == Scenario.RCRRR_PARTICLE_ONLY_NO_TIDE ||
s == Scenario.RCRR_FULL_CONTROL_NO_TIDE
val upperRetainedStart = if (particleOnlyC)
firstAfterClipId else "special-pattern-line-754"
segments.put(JSONObject().put("strategy", "RETAINED").put("startLayerId", upperRetainedStart))
if (s == Scenario.RCRRR_PARTICLE_ONLY_NO_TIDE) {
segments.put(JSONObject().put("strategy", "RETAINED")
.put("startLayerId", "special-pattern-line-754"))
}
if (s != Scenario.RCR_PARTICLE_ONLY_NO_TIDE) {
segments.put(JSONObject().put("strategy", "RETAINED").put("startLayerId", finalBaseId))
}
}
}
return JSONObject().put("schemaVersion", 1).put("segments", segments).toString()
}
private fun layerManifest(layers: JSONArray): JSONArray = JSONArray().apply {
for (i in 0 until layers.length()) {
val l = layers.getJSONObject(i)
put(JSONObject().put("index", i).put("id", l.getString("id")).put("type", l.getString("type"))
.put("source", l.opt("source") ?: JSONObject.NULL)
.put("visibility", l.optJSONObject("layout")?.optString("visibility", "visible") ?: "visible"))
}
}
private fun sha256(text: String): String = MessageDigest.getInstance("SHA-256")
.digest(text.toByteArray(Charsets.UTF_8)).joinToString("") { "%02x".format(it.toInt() and 0xff) }
}
}

View File

@@ -0,0 +1,329 @@
package org.maplibre.android.testapp.activity.particle
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.util.Log
import org.json.JSONArray
import org.json.JSONObject
import org.maplibre.android.annotations.Marker
import org.maplibre.android.annotations.MarkerOptions
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.Style
import org.maplibre.android.maps.WeatherRuntime
import org.maplibre.android.style.layers.PropertyFactory.iconImage
import org.maplibre.android.style.layers.SymbolLayer
import org.maplibre.android.style.sources.GeoJsonSource
import org.maplibre.android.testapp.BuildConfig
import org.maplibre.geojson.Feature
import org.maplibre.geojson.Point
import java.io.File
/** 独立离线最小场景;不代表正式 PBF/NavSea。所有帧与缓存计数仅来自 Native。 */
internal class Step114FastSmokeScene(
private val context: Context,
private val map: MapLibreMap,
private val display: (String) -> Unit
) {
enum class Scenario { FAST_SMOKE, CIRCLE_C_BLOCKED, ANNOTATION_BLOCKED, SYMBOL_R_BLOCKED }
private val handler = Handler(Looper.getMainLooper())
private var generation = 0
private var running = false
private var paused = false
private var ready = false
private var blocked: String? = null
private var scenario = Scenario.FAST_SMOKE
private var runtime: WeatherRuntime? = null
private var marker: Marker? = null
private var savedGestures: List<Boolean>? = null
private var commands = JSONObject()
private var mutation = 0
private var cameraAlternate = false
private var actionSequence = 0L
private var lastLogMs = 0L
private val poll = object : Runnable {
override fun run() {
if (!running || paused) return
try {
val native = JSONObject(map.getRenderSegmentDiagnostics())
val trace = native.optJSONObject("fastGateTrace")
val segments = native.optJSONArray("segments") ?: JSONArray()
display(buildString {
append("STEP-114 $scenario · 独立 smoke · ${if (ready) "READY/待验收" else blocked ?: "LOADING"}\n")
append("Native Full=${trace?.optLong("fullRendered")} Fast=${trace?.optLong("fastRendered")} ")
append("windowFast=${trace?.optLong("windowFastRendered")} mismatch=${trace?.optLong("decisionMismatch")}\n")
for (i in 0 until segments.length()) {
val s = segments.getJSONObject(i)
val c = s.optJSONObject("cache")
append("${s.optString("strategy")}[${s.optInt("beginIndex")},${s.optInt("endExclusive")}) ")
append("B=${c?.optLong("build") ?: 0} Reuse=${c?.optLong("reuse") ?: 0} ")
append("${s.optString("actualExecution")} ${c?.optString("lastFallbackReason") ?: ""}\n")
}
append("固定预报/相机;操作前后快照保存在 files/step114-*.json")
})
val now = SystemClock.elapsedRealtime()
if (now - lastLogMs >= 30000L) {
lastLogMs = now
Log.i(TAG, "scenario=$scenario ready=$ready full=${trace?.optLong("fullRendered")} " +
"fast=${trace?.optLong("fastRendered")} mismatch=${trace?.optLong("decisionMismatch")}")
}
} catch (error: Exception) { fail(error) }
handler.postDelayed(this, 3000L)
}
}
fun start(next: Scenario = Scenario.FAST_SMOKE) {
stop()
running = true
scenario = next
ready = false
blocked = null
commands = JSONObject()
mutation = 0
cameraAlternate = false
lastLogMs = 0L
val epoch = ++generation
display("STEP-114 $scenario · 独立离线场景加载中;正式 PBF 五场景另在 STEP-113")
val ui = map.uiSettings
savedGestures = listOf(ui.isScrollGesturesEnabled(), ui.isRotateGesturesEnabled(),
ui.isTiltGesturesEnabled(), ui.isZoomGesturesEnabled(),
ui.isDoubleTapGesturesEnabled(), ui.isQuickZoomGesturesEnabled())
ui.setAllGesturesEnabled(false)
map.cancelTransitions()
map.setRetainedUnderlayEnabled(true)
val plan = JSONObject().put("schemaVersion", 1).put("segments", JSONArray()
.put(JSONObject().put("strategy", "RETAINED"))
.put(JSONObject().put("strategy", "CONTINUOUS").put("startLayerId", COLOR_LAYER)))
commands.put("requestedPlan", plan).put("planAccepted", map.setRenderSegmentPlan(plan.toString()))
if (!paused) handler.post(poll)
map.setStyle(Style.Builder().fromJson(styleJson())) loaded@ { style ->
if (!running || epoch != generation) return@loaded
try {
check(commands.getBoolean("planAccepted")) { "RenderSegmentPlan 未接受" }
check(map.annotations.isEmpty()) { "存在非本场景 Annotation,禁止移除其层" }
if (scenario != Scenario.ANNOTATION_BLOCKED) {
// 只在这个没有 Annotation 实例的独立场景移除空内建层。
// 保留内建 source;生产场景和 Native Annotation 行为均不修改。
check(style.getLayer(ANNOTATION_LAYER) != null && style.removeLayer(ANNOTATION_LAYER)) {
"空内建 Annotation 层未能移除,保留 fail-closed 现场"
}
commands.put("emptyBuiltinAnnotationLayerRemoved", true)
} else {
check(style.getLayer(ANNOTATION_LAYER) != null) { "内建 Annotation 层缺失" }
marker = map.addMarker(MarkerOptions().position(LatLng(33.45, 129.97)))
commands.put("realAnnotationAdded", true)
}
if (scenario == Scenario.SYMBOL_R_BLOCKED) {
style.addImage(IMAGE, image(Color.YELLOW))
style.addLayerBelow(SymbolLayer(SYMBOL_LAYER, PROBE_SOURCE)
.withProperties(iconImage(IMAGE)), COLOR_LAYER)
commands.put("structuralSymbolInR", true)
}
map.cameraPosition = camera(false)
initializeFields()
ready = true
handler.removeCallbacksAndMessages(null)
if (!paused) handler.post(poll)
} catch (error: Exception) { fail(error) }
}
}
private fun styleJson(): String {
val layers = JSONArray()
.put(JSONObject().put("id", "step114-background").put("type", "background")
.put("paint", JSONObject().put("background-color", "#172c40")))
.put(JSONObject().put("id", "step114-r-circle").put("type", "circle").put("source", PROBE_SOURCE)
.put("paint", JSONObject().put("circle-radius", 12).put("circle-color", "#ff8800")))
.put(JSONObject().put("id", COLOR_LAYER).put("type", "weather-color").put("source", FIELD_SOURCE)
.put("paint", JSONObject().put("weather-color-opacity", 0.62)))
.put(JSONObject().put("id", PARTICLE_LAYER).put("type", "raster-particle").put("source", FIELD_SOURCE)
.put("paint", JSONObject().put("raster-particle-active", true)
.put("raster-particle-particle-count", 1024)))
if (scenario == Scenario.CIRCLE_C_BLOCKED) {
layers.put(JSONObject().put("id", "step114-c-circle").put("type", "circle").put("source", PROBE_SOURCE)
.put("paint", JSONObject().put("circle-radius", 5).put("circle-color", "#00ffff")))
}
return JSONObject().put("version", 8).put("name", "STEP-114 independent offline smoke")
.put("sources", JSONObject()
.put(PROBE_SOURCE, JSONObject().put("type", "geojson")
.put("data", JSONObject(Feature.fromGeometry(Point.fromLngLat(129.97, 33.45)).toJson())))
.put(FIELD_SOURCE, JSONObject().put("type", "raster-array").put("host-field-submission", true)))
.put("layers", layers).toString()
}
private fun initializeFields() {
val host = WeatherRuntime(map, WeatherRuntime.Config(FIELD_SOURCE, COLOR_LAYER, PARTICLE_LAYER))
runtime = host
val submitted = JSONArray()
for (cycle in listOf("2026080700", "2026080706")) {
val path = "integration/particle-weatherv2/ecmwf-hres-wind-20260807/$cycle"
val meta = context.assets.open("$path/metadata.json").bufferedReader().use { it.readText() }
val data = context.assets.open("$path/field.f32").use { it.readBytes() }
val result = host.submitWindField(meta, data)
submitted.put(result.name)
check(result.accepted()) { "离线 Field 提交失败: $cycle $result" }
}
commands.put("fieldSubmit", submitted)
.put("forecastSeek", host.setForecastTime(FORECAST_MS).name)
.put("forecastPause", host.setPlaybackState(WeatherRuntime.PlaybackState.PAUSE).name)
.put("particleVisibility", host.setParticleVisible(true).name)
.put("targetFps", host.setTargetFps(WeatherRuntime.TargetFps.FPS_30).name)
check(host.state == WeatherRuntime.State.READY && listOf("forecastSeek", "forecastPause",
"particleVisibility", "targetFps").all { commands.getString(it) == "ACCEPTED" }) {
"固定预报/粒子控制未全部接受: $commands"
}
}
// 操作前、提交后与短延迟分别保存真实 Native 窗口;不等待、不强制 Full、不构造 Gate 输入。
private fun action(name: String, change: () -> Unit) {
if (!running || !ready || paused) return
val epoch = generation
val id = ++actionSequence
try {
capture("$id-$name-before")
change()
commands.put("lastAction", name).put("actionSequence", id)
capture("$id-$name-submitted")
for (delay in listOf(100L, 500L, 1500L)) {
handler.postDelayed({
if (running && !paused && epoch == generation) {
try { capture("$id-$name-after-${delay}ms") } catch (error: Exception) { fail(error) }
}
}, delay)
}
} catch (error: Exception) { fail(error) }
}
fun mutateSource() = action("source") {
val source = checkNotNull(map.style?.getSourceAs<GeoJsonSource>(PROBE_SOURCE))
++mutation
source.setGeoJson(Feature.fromGeometry(Point.fromLngLat(129.97 + (mutation % 2) * 0.01, 33.45)))
commands.put("sourceMutationCount", mutation)
}
fun panZoom() = action("pan-zoom") {
cameraAlternate = !cameraAlternate
map.cameraPosition = camera(cameraAlternate)
commands.put("cameraAlternate", cameraAlternate)
}
fun mutateImage() = action("image") {
val count = commands.optInt("imageMutationCount") + 1
checkNotNull(map.style).addImage(IMAGE, image(if (count % 2 == 1) Color.RED else Color.YELLOW))
commands.put("imageMutationCount", count)
}
fun toggleCache() = action("cache-toggle") {
val enabled = !commands.optBoolean("cacheDisabled", false)
map.setRetainedUnderlayEnabled(!enabled)
commands.put("cacheDisabled", enabled)
}
fun mutateAnnotation() = action("annotation") {
val annotation = checkNotNull(marker) { "请先选择 ANNOTATION_BLOCKED" }
++mutation
annotation.position = LatLng(33.45, 129.97 + (mutation % 2) * 0.01)
commands.put("annotationMutationCount", mutation)
}
fun reload() {
if (!running) return
try { capture("reload-before") } catch (error: Exception) { fail(error); return }
start(scenario)
}
private fun envelope(native: JSONObject, phase: String) = JSONObject()
.put("schema", "step114-fast-smoke").put("schemaVersion", 1).put("appVersion", BuildConfig.VERSION_NAME)
.put("scenario", scenario.name).put("sceneGeneration", generation).put("sceneReady", ready)
.put("blockedReason", blocked ?: JSONObject.NULL).put("sampleElapsedMs", SystemClock.elapsedRealtime())
.put("phase", phase).put("evidence", "INDEPENDENT-SMOKE; 非PBF/非113层等价")
.put("commands", JSONObject(commands.toString())).put("forecastRequestedMs", FORECAST_MS)
.put("actualForecastTimeObservedMs", JSONObject.NULL)
.put("fieldBasis", "既有离线 ECMWF canonical 双帧;非NavSea当前QBIN")
.put("cameraObservedFromSdk", JSONObject().put("latitude", map.cameraPosition.target?.latitude)
.put("longitude", map.cameraPosition.target?.longitude).put("zoom", map.cameraPosition.zoom)
.put("bearing", map.cameraPosition.bearing).put("pitch", map.cameraPosition.tilt))
.put("native", native)
private fun capture(phase: String) {
val record = envelope(JSONObject(map.getRenderSegmentDiagnostics()), phase)
write(record, phase)
}
private fun write(record: JSONObject, phase: String) {
val file = File(context.filesDir, "step114-${scenario.name}-${generation}-${SystemClock.elapsedRealtime()}-$phase.json")
file.writeText(record.toString(), Charsets.UTF_8)
Log.i(TAG, "snapshot=${file.name} scenario=$scenario generation=$generation phase=$phase")
}
fun export() {
if (!running) return
try { capture("manual") } catch (error: Exception) { fail(error) }
}
fun onStyleLoadFailed(message: String) { if (running) fail(IllegalStateException(message)) }
private fun fail(error: Exception) {
ready = false
blocked = "BLOCKED: ${error.message}"
display("STEP-114 $scenario $blocked")
Log.e(TAG, "scene-blocked", error)
}
fun pause() {
if (running) try { capture("pause-before") } catch (error: Exception) { fail(error) }
paused = true
handler.removeCallbacksAndMessages(null)
}
fun resume() {
paused = false
handler.removeCallbacksAndMessages(null)
if (running) handler.post(poll)
}
fun stop() {
if (running) try { capture("teardown-before") } catch (error: Exception) { Log.e(TAG, "capture-failed", error) }
running = false
++generation
handler.removeCallbacksAndMessages(null)
runtime?.dispose()
runtime = null
marker?.let { map.removeMarker(it) }
marker = null
savedGestures?.let { flags ->
val ui = map.uiSettings
ui.setScrollGesturesEnabled(flags[0]); ui.setRotateGesturesEnabled(flags[1])
ui.setTiltGesturesEnabled(flags[2]); ui.setZoomGesturesEnabled(flags[3])
ui.setDoubleTapGesturesEnabled(flags[4]); ui.setQuickZoomGesturesEnabled(flags[5])
}
savedGestures = null
ready = false
Log.i(TAG, "scene-stop generation=$generation;MapView 销毁验收由 Activity/设备日志确认")
}
companion object {
const val TAG = "STEP114Gate"
private const val ANNOTATION_LAYER = "org.maplibre.annotations.points"
private const val PROBE_SOURCE = "step114-r-geojson"
private const val FIELD_SOURCE = "step114-fields"
private const val COLOR_LAYER = "step114-color"
private const val PARTICLE_LAYER = "step114-particles"
private const val SYMBOL_LAYER = "step114-r-symbol"
private const val IMAGE = "step114-image"
private const val FORECAST_MS = 1786060800000L
private fun camera(alternate: Boolean) = CameraPosition.Builder()
.target(LatLng(33.45, if (alternate) 130.0 else 129.97))
.zoom(if (alternate) 11.0 else 10.5).bearing(0.0).tilt(0.0).build()
private fun image(color: Int): Bitmap = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888)
.apply { eraseColor(color) }
}
}

View File

@@ -1 +1 @@
13.6.1
13.6.1-particle.27

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

@@ -143,8 +143,8 @@ apple_xcframework(
bundle_name = "MapLibre",
data = glob([
"resources/*.lproj/**",
"resources/*.xcassets/**",
]) + [
"resources/Assets.car",
"PrivacyInfo.xcprivacy",
"resources/metal-cpp-ignores.txt",
],

Binary file not shown.

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 发布完成。

30
release/README.md Normal file
View File

@@ -0,0 +1,30 @@
# Particle 正式发行入口
本仓库按 GSRL v1.0.0 维护源码发行记录。**正式二进制在 NAS/PBF 公网仓库,Git 不存放 AAR、tgz 或 XCFramework。**
## 当前推荐发行
| 发行代号 | Android Native / RN | iOS Metal | 状态 | 入口 |
| --- | --- | --- | --- | --- |
| v27 | `13.6.1-particle.27` | `6.29.0-particle.27` | GSRL Source Lock 正式发行 | [v27 版本说明](maplibre-particle/13.6.1-particle.27/README.md) |
| v26 | `13.6.1-particle.26` | `6.29.0-particle.26` | GSRL Source Lock 正式发行 | [v26 版本说明](maplibre-particle/13.6.1-particle.26/README.md) |
| v25 | `13.6.1-particle.25` | `6.29.0-particle.25` | GSRL Source Lock 正式发行 | [v25 版本说明](maplibre-particle/13.6.1-particle.25/README.md) |
| 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) |
- v27 Source Lock:[SOURCE_LOCK.json](maplibre-particle/13.6.1-particle.27/SOURCE_LOCK.json)
- v26 Source Lock:[SOURCE_LOCK.json](maplibre-particle/13.6.1-particle.26/SOURCE_LOCK.json)
- v25 Source Lock:[SOURCE_LOCK.json](maplibre-particle/13.6.1-particle.25/SOURCE_LOCK.json)
- 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)
- 最新统一发布清单:[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

@@ -0,0 +1,36 @@
# MapLibre Particle v25 正式发行
Android/RN:`13.6.1-particle.25`;iOS Metal/SwiftPM:`6.29.0-particle.25`。跳过 v24,v23 不覆盖。
Build Source Commit:`f32f6f45fd359007e8b3c77911b66d9e7fc93848`。
正式发行 Git Tag:`maplibre-particle/rn/13.6.1-particle.25`。
## 公网获取入口
| 组件 | URL |
| --- | --- |
| android / android-sdk-particle-opengl-13.6.1-particle.25.aar | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.25/android-sdk-particle-opengl-13.6.1-particle.25.aar |
| android / android-sdk-particle-opengl-13.6.1-particle.25.pom | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.25/android-sdk-particle-opengl-13.6.1-particle.25.pom |
| android / android-sdk-particle-opengl-13.6.1-particle.25.module | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.25/android-sdk-particle-opengl-13.6.1-particle.25.module |
| reactNative / navsea-maplibre-particle-rn-13.6.1-particle.25.tgz | https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.25.tgz |
| ios / MapLibre.dynamic.xcframework.zip | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.25/MapLibre.dynamic.xcframework.zip |
| ios / MapLibre.dynamic.xcframework.zip.sha256 | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.25/MapLibre.dynamic.xcframework.zip.sha256 |
所有六个文件 SHA-256 与尺寸均以同目录 `SOURCE_LOCK.json` 的 `files[]` 为准。发布时使用统一流水线 `tools/particle_release_pipeline/release.sh 25`,经公网重下载逐字节比对通过。
## 安装 / 使用
- Android Maven:`https://pbf.jpcn.ccwu.cc/maven`,依赖 `cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.25`;包括四 ABI。
- RN:从表中 tgz 安装 `@navsea/maplibre-particle-rn@13.6.1-particle.25`,peer `@maplibre/maplibre-react-native@11.5.0`。
- iOS:SwiftPM `https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git`,exact tag `6.29.0-particle.25`,product `MapLibre`,Metal ios-arm64 实体机专用,无 Simulator。
## GSRL 接收与验收边界
从本发行入口读取 SOURCE_LOCK.json,核对 Build Source Commit、Tag、依赖和六项制品的 SHA-256;任何不匹配均不得按 v25 接收。
Android / iOS 真实设备发行产物、RN Host 整体联调在 v25 尚未执行,不能把 STEP-119 旧 APK 稳态 CPU 结果视作 v25 二进制真机验收。
本版对 iOS 资源编译采用受控预编译 Assets.car:原始 Images.xcassets 自 v23 未变、Xcode 26.3、SHA 已记录,详见 `docs/报告/V25_iOS实体机Bazel构建兼容性修复与资源溯源_v1.md`。
发行清单:`release/maplibre-particle/release-manifest.json`。

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.25",
"versions": {
"android": "13.6.1-particle.25",
"reactNative": "13.6.1-particle.25",
"ios": "6.29.0-particle.25"
},
"source": {
"gitRemote": "https://git.eyaku.jp/tei/MapLibreParticleLab",
"gitCommit": "f32f6f45fd359007e8b3c77911b66d9e7fc93848",
"gitRef": "refs/tags/maplibre-particle/rn/13.6.1-particle.25",
"note": "gitCommit是实际构建冻结源提交;GSRL Source Lock与最终发行清单在后续提交内记录,Tag指向包含Source Lock和发行清单的最终Commit。"
},
"files": [
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.25.aar",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.25/android-sdk-particle-opengl-13.6.1-particle.25.aar",
"sha256": "629d5d437d884ec2017f3fde007aaadfd2bc787983457f75bcaf4d4a13184a1e",
"sizeBytes": 16625344
},
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.25.pom",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.25/android-sdk-particle-opengl-13.6.1-particle.25.pom",
"sha256": "1d699fa3b0584fe73c54fec01ae2d310c25be2181f9a92445d5cf354aae685b2",
"sizeBytes": 5687
},
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.25.module",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.25/android-sdk-particle-opengl-13.6.1-particle.25.module",
"sha256": "e67ee618439561be7e410ca6a2af9594055827903f16e53f16c1ffc967735273",
"sizeBytes": 6999
},
{
"releaseUnit": "reactNative",
"name": "navsea-maplibre-particle-rn-13.6.1-particle.25.tgz",
"location": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.25.tgz",
"sha256": "e5ed678c90bad6082dda30fb9937894cabe28732160391816b785d3c88c208e9",
"sizeBytes": 29029
},
{
"releaseUnit": "ios",
"name": "MapLibre.dynamic.xcframework.zip",
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.25/MapLibre.dynamic.xcframework.zip",
"sha256": "91808cfb5559dfa050fe4d466d2c9fa2dc492021bd1a203c778450dc0dae20af",
"sizeBytes": 4176630
},
{
"releaseUnit": "ios",
"name": "MapLibre.dynamic.xcframework.zip.sha256",
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.25/MapLibre.dynamic.xcframework.zip.sha256",
"sha256": "a6e708b547600b20f7e8500f8e756be5f720b8df4c7ffa0565da99d695631469",
"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.25",
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.25",
"role": "OpenGL AAR, arm64-v8a/armeabi-v7a/x86/x86_64"
},
{
"name": "iOS SwiftPM MapLibre",
"version": "6.29.0-particle.25",
"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.25/README.md",
"releaseManifest": "release/maplibre-particle/release-manifest.json",
"status": "RELEASED",
"validation": {
"publicReadback": "PASS - release.sh 25 publish, six files public download byte-compared to build artifacts",
"androidBuild": "PASS - four ABI OpenGL Release AAR, build source f32f6f45...",
"iosMetalBuild": "PASS - Bazel opt Metal ios-arm64 XCFramework, no simulator slices",
"reactNativePack": "PASS - npm pack, peer @maplibre/maplibre-react-native 11.5.0",
"androidDevice": "NOT_TESTED - V25 release binary",
"reactNative115HostIntegration": "NOT_TESTED - V25",
"iosMetalDevice": "NOT_TESTED - V25 release binary",
"step119CPU": "PARTIAL - STEP-119 earlier validation APK source 32ac795... not V25 release binary",
"note": "GSRL PASS is source/file identity and availability only, not device runtime certification. iOS device-only build reused byte-identical Xcode 26.3 v23 Assets.car from unchanged original assets; see docs/报告/V25_iOS实体机Bazel构建兼容性修复与资源溯源_v1.md."
}
}

View File

@@ -0,0 +1,32 @@
# MapLibre Particle V26 正式发行
Android/RN:`13.6.1-particle.26`;iOS Metal/SwiftPM:`6.29.0-particle.26`。
统一真实构建源码 Commit:`fce326a9009d3fc21eb91c5df362f0769163d07e`;正式 Git Tag:`maplibre-particle/rn/13.6.1-particle.26`。
正式身份、依赖与六项文件 SHA-256 以本目录 [SOURCE_LOCK.json](SOURCE_LOCK.json) 为准。
## 公网发行文件
| 组件 / 文件 | 获取 URL |
| --- | --- |
| android / android-sdk-particle-opengl-13.6.1-particle.26.aar | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.aar |
| android / android-sdk-particle-opengl-13.6.1-particle.26.pom | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.pom |
| android / android-sdk-particle-opengl-13.6.1-particle.26.module | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.module |
| reactNative / navsea-maplibre-particle-rn-13.6.1-particle.26.tgz | https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.26.tgz |
| ios / MapLibre.dynamic.xcframework.zip | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/MapLibre.dynamic.xcframework.zip |
| ios / MapLibre.dynamic.xcframework.zip.sha256 | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/MapLibre.dynamic.xcframework.zip.sha256 |
## 安装和使用
- Android Maven:配置仓库 `https://pbf.jpcn.ccwu.cc/maven`,依赖 `cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.26`;OpenGL AAR 含 `armeabi-v7a`、`arm64-v8a`、`x86`、`x86_64`。
- RN:使用上表 tgz 安装 `@navsea/maplibre-particle-rn@13.6.1-particle.26`;peer 依赖 `@maplibre/maplibre-react-native@11.5.0`,React Native `>=0.80.0`,可选 Expo `>=54.0.0`。
- iOS:SwiftPM 仓库 `https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git`,精确版本 `6.29.0-particle.26`,product `MapLibre`;Metal、iOS arm64 真机设备专用,不含 Simulator。
## GSRL 接收要求和测试边界
按此目录 SOURCE_LOCK 核对项目、版本、Git Commit/Tag、全部交付文件的 SHA-256、依赖和安装说明;不匹配不得接收。
`release.sh 26 build` 从上述冻结源码正式构建;`release.sh 26 publish` 执行 NAS 原子发布、六项文件公网下载逐字节对比、SwiftPM Tag 核对均通过。
V26 正式 AAR/Metal ZIP 的真机验收及 NavSea Host 整体接入 **NOT_TESTED**;原 STEP-119 FZ-G1 四轮 Test App 开发态检查不是此次正式 AAR 的运行验收。新增 C++ 生命周期 GTest 因 Mac 缺少 libuv 前置依赖未执行;这些事项不虚报 PASS。
总发行 Manifest:[release-manifest.json](../release-manifest.json)。

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.26",
"versions": {
"android": "13.6.1-particle.26",
"reactNative": "13.6.1-particle.26",
"ios": "6.29.0-particle.26"
},
"source": {
"gitRemote": "https://git.eyaku.jp/tei/MapLibreParticleLab",
"gitCommit": "fce326a9009d3fc21eb91c5df362f0769163d07e",
"gitRef": "refs/tags/maplibre-particle/rn/13.6.1-particle.26",
"note": "gitCommit是本次Android/RN/iOS共用的实际冻结构建源码;发行Tag指向含SOURCE_LOCK与发行Manifest的后续Final Commit。"
},
"files": [
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.26.aar",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.aar",
"sha256": "46c1cf7ff8ebc3bedc25aa38d7d445078a2301e6ca223c0307b93e830991c2b9",
"sizeBytes": 16640537
},
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.26.pom",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.pom",
"sha256": "b1e34295c58238a45a8b14d33c801028c3cabc0f40aae6116d007b352fa25520",
"sizeBytes": 5687
},
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.26.module",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.26/android-sdk-particle-opengl-13.6.1-particle.26.module",
"sha256": "405b3bb3732ef29a0b54e6b24b192839d07d4c28e959276d6bd92051637011c4",
"sizeBytes": 6999
},
{
"releaseUnit": "reactNative",
"name": "navsea-maplibre-particle-rn-13.6.1-particle.26.tgz",
"location": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.26.tgz",
"sha256": "3136316bb6508e774748b21c2095ab61b522d2cf4c3ff2e8c67774dde8549b93",
"sizeBytes": 29038
},
{
"releaseUnit": "ios",
"name": "MapLibre.dynamic.xcframework.zip",
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/MapLibre.dynamic.xcframework.zip",
"sha256": "64b51c2b9eaec4adefcfb2dfc1c6338943f11db4d6c4ce2a4e1c2a39bb84bddb",
"sizeBytes": 4181892
},
{
"releaseUnit": "ios",
"name": "MapLibre.dynamic.xcframework.zip.sha256",
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.26/MapLibre.dynamic.xcframework.zip.sha256",
"sha256": "ae79556c2b288a3970d6e4eba0c30a68023faa79ff55de291c9e2f30f16fd9ae",
"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.26",
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.26",
"role": "OpenGL AAR, arm64-v8a/armeabi-v7a/x86/x86_64"
},
{
"name": "iOS SwiftPM MapLibre",
"version": "6.29.0-particle.26",
"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.26/README.md",
"releaseManifest": "release/maplibre-particle/release-manifest.json",
"status": "RELEASED",
"validation": {
"publicReadback": "PASS - release.sh 26 publish exit 0; AAR, POM, Module, RN tgz, Metal ZIP, ZIP.sha256 six public files byte-compared to release artifacts",
"androidBuild": "PASS - Android OpenGL Release four ABI from frozen source fce326a9009d3fc21eb91c5df362f0769163d07e",
"iosMetalBuild": "PASS - Bazel opt Metal ios-arm64 XCFramework, no simulator slices",
"reactNativePack": "PASS - npm pack @navsea/maplibre-particle-rn 13.6.1-particle.26; peer MapLibre RN 11.5.0",
"androidDevice": "NOT_TESTED - V26 final release AAR itself; STEP-119 earlier development Test App validated separately",
"reactNative115HostIntegration": "NOT_TESTED - V26 final release binary in NavSea",
"iosMetalDevice": "NOT_TESTED - V26 final release XCFramework on iPhone/iPad",
"step119CppGTest": "BLOCKED - Mac CMake prerequisite libuv unavailable, 13 lifecycle GTest sources not executed",
"note": "GSRL RELEASED verifies frozen source/files/dependency/public retrieval; it does not certify NavSea production runtime, Android/iOS device tests, or unexecuted C++ GTest."
}
}

View File

@@ -0,0 +1,35 @@
# MapLibre Particle V27 正式发行
Android/RN:13.6.1-particle.27;iOS Metal/SwiftPM:6.29.0-particle.27。
统一实际构建源码 Commit:970e3ad606e59873a32e9389e65ef257e1c259bb;正式 Git Tag:maplibre-particle/rn/13.6.1-particle.27。
正式来源、依赖和六项文件 SHA-256 以本目录 [SOURCE_LOCK.json](SOURCE_LOCK.json) 为准。
## 内容及公开下载
本次新增 Metal Retained Underlay Composite 顶点缓冲槽修复(maxUBOCountPerShader + 0),保留 V26 失败候选的历史调查记录。RCR 为当前 NavSea Host 基线选择;不以 E 全 Continuous 降级冒充本次修复。
| 组件 / 文件 | 公网 URL |
| --- | --- |
| android / android-sdk-particle-opengl-13.6.1-particle.27.aar | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.aar |
| android / android-sdk-particle-opengl-13.6.1-particle.27.pom | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.pom |
| android / android-sdk-particle-opengl-13.6.1-particle.27.module | https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.module |
| reactNative / navsea-maplibre-particle-rn-13.6.1-particle.27.tgz | https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.27.tgz |
| ios / MapLibre.dynamic.xcframework.zip | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.27/MapLibre.dynamic.xcframework.zip |
| ios / MapLibre.dynamic.xcframework.zip.sha256 | https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.27/MapLibre.dynamic.xcframework.zip.sha256 |
## 安装、依赖与兼容性
- Android:Maven https://pbf.jpcn.ccwu.cc/maven ,坐标 cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.27;OpenGL AAR 包含 armeabi-v7a、arm64-v8a、x86、x86_64。
- React Native:安装上表 tgz,对应 @navsea/maplibre-particle-rn@13.6.1-particle.27;MapLibre React Native 11.5.0、React Native >=0.80.0,Expo >=54.0.0 可选。
- iOS:SwiftPM https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git ,Exact Version 6.29.0-particle.27,Product MapLibre;Metal iOS arm64 真机专用,无 Simulator slice。
## GSRL 核验与运行测试边界
release.sh 27 build:Android 四 ABI、Maven Local、iOS Metal Bazel opt、RN pack PASS。
release.sh 27 publish:NAS 原子发布、六项公网文件逐字节回读、SwiftPM 版本 Tag 核验 PASS。
此次尚未执行 V27 正式二进制的 Android/iOS 真机安装运行、NavSea Host 整体接入及 C++ 生命周期 GTest。此前 slot-9 测试候选获用户视觉确认,不等于本次发行包通过设备验收。
接收方必须独立核对 Git Commit/Tag、全部文件 SHA-256、依赖与安装说明;校验未通过不得正式接入。
统一 [发行 Manifest](../release-manifest.json)。

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.27",
"versions": {
"android": "13.6.1-particle.27",
"reactNative": "13.6.1-particle.27",
"ios": "6.29.0-particle.27"
},
"source": {
"gitRemote": "https://git.eyaku.jp/tei/MapLibreParticleLab",
"gitCommit": "970e3ad606e59873a32e9389e65ef257e1c259bb",
"gitRef": "refs/tags/maplibre-particle/rn/13.6.1-particle.27",
"note": "gitCommit是本次Android/RN/iOS共用的实际冻结构建源码;发行Tag指向含SOURCE_LOCK与发行Manifest的后续Final Commit。"
},
"files": [
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.27.aar",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.aar",
"sha256": "2ff3ead8e5d7cb8802a4c05ba20cd7f8f0a7029f72619f9a2ec176c942ae8de4",
"sizeBytes": 16640539
},
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.27.pom",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.pom",
"sha256": "324d14d689ba7c9206298734d7f9636b95e947c7241d94ec60f8f1d57bd1b701",
"sizeBytes": 5687
},
{
"releaseUnit": "android",
"name": "android-sdk-particle-opengl-13.6.1-particle.27.module",
"location": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.module",
"sha256": "403804395f065cef75f1254403edbda67c3a885769acbf6d25e091d37c325910",
"sizeBytes": 6999
},
{
"releaseUnit": "reactNative",
"name": "navsea-maplibre-particle-rn-13.6.1-particle.27.tgz",
"location": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.27.tgz",
"sha256": "fdd67210debd8363d4bf72fcc0120ef747dc3037d55b8c0d83330dd98bb0bc5d",
"sizeBytes": 29040
},
{
"releaseUnit": "ios",
"name": "MapLibre.dynamic.xcframework.zip",
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.27/MapLibre.dynamic.xcframework.zip",
"sha256": "5dbde28600df35484a7f31e7c7c4780f27b919fc6ec59f6aa88d65d81342448c",
"sizeBytes": 4181894
},
{
"releaseUnit": "ios",
"name": "MapLibre.dynamic.xcframework.zip.sha256",
"location": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.27/MapLibre.dynamic.xcframework.zip.sha256",
"sha256": "1dba0b0a2831beeadcc022801622829d630ef0838babdfef4edc4b8bec34e08d",
"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.27",
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.27",
"role": "OpenGL AAR, arm64-v8a/armeabi-v7a/x86/x86_64"
},
{
"name": "iOS SwiftPM MapLibre",
"version": "6.29.0-particle.27",
"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.27/README.md",
"releaseManifest": "release/maplibre-particle/release-manifest.json",
"status": "RELEASED",
"validation": {
"publicReadback": "PASS - release.sh 27 publish exit 0; six public files byte-compared, SwiftPM tag checked",
"androidBuild": "PASS - OpenGL Release four ABI from frozen source 970e3ad606e59873a32e9389e65ef257e1c259bb",
"iosMetalBuild": "PASS - Bazel opt Metal ios-arm64 XCFramework, no simulator slice",
"reactNativePack": "PASS - npm pack 13.6.1-particle.27; MapLibre RN 11.5.0 peer",
"androidDevice": "NOT_TESTED - V27 final release AAR itself on device",
"reactNative115HostIntegration": "NOT_TESTED - V27 final release package in NavSea Host",
"iosMetalDevice": "NOT_TESTED - V27 final XCFramework; prior slot-9 candidate visual acceptance is not a release binary acceptance",
"step119CppGTest": "NOT_TESTED - not run in V27 GSRL release",
"note": "GSRL RELEASED certifies source, files, dependencies and public readback, not production runtime or device tests."
}
}

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.27/MapLibre.dynamic.xcframework.zip",
checksum: "5dbde28600df35484a7f31e7c7c4780f27b919fc6ec59f6aa88d65d81342448c"
),
]
)

View File

@@ -1,14 +1,14 @@
# MapLibre Particle iOS 发行
当前正式发行:6.28.0-particle.21
当前正式发行:6.29.0-particle.27
- 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.27/MapLibre.dynamic.xcframework.zip
- SHA-256:5dbde28600df35484a7f31e7c7c4780f27b919fc6ec59f6aa88d65d81342448c
- 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.27
- Source Commit:970e3ad606e59873a32e9389e65ef257e1c259bb
构建命令固定为:

View File

@@ -1,20 +1,20 @@
{
"schemaVersion": 2,
"status": "PUBLISHED_PUBLIC_READBACK_PASS",
"distributionVersion": "6.28.0-particle.21",
"distributionVersion": "6.29.0-particle.27",
"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": 4181894,
"sha256": "5dbde28600df35484a7f31e7c7c4780f27b919fc6ec59f6aa88d65d81342448c",
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.27/MapLibre.dynamic.xcframework.zip"
},
"sourceCommit": "16a5d15f164dc93d220e3fac62de66a9c9527ccc",
"sourceCommit": "970e3ad606e59873a32e9389e65ef257e1c259bb",
"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.27",
"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": 27,
"sourceCommit": "970e3ad606e59873a32e9389e65ef257e1c259bb",
"generatedAtUtc": "2026-10-14T01:50:45.060383+00:00",
"reactNative": {
"name": "@navsea/maplibre-particle-rn",
"version": "13.4.1-particle.21",
"version": "13.6.1-particle.27",
"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.27.tgz",
"sizeBytes": 29040,
"sha256": "fdd67210debd8363d4bf72fcc0120ef747dc3037d55b8c0d83330dd98bb0bc5d",
"url": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.6.1-particle.27.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.27",
"coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.6.1-particle.27",
"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.27.aar",
"sizeBytes": 16640539,
"sha256": "2ff3ead8e5d7cb8802a4c05ba20cd7f8f0a7029f72619f9a2ec176c942ae8de4",
"url": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.6.1-particle.27/android-sdk-particle-opengl-13.6.1-particle.27.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.27",
"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": 4181894,
"sha256": "5dbde28600df35484a7f31e7c7c4780f27b919fc6ec59f6aa88d65d81342448c",
"url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.29.0-particle.27/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.27",
"productName": "MapLibre"
},
"xcode": "Xcode 26.3 Build version 17C529 ",

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