feat(step102): isolate placement-free retained segments

This commit is contained in:
tei
2026-10-08 09:59:58 +09:00
parent 6df21dac38
commit 0ce551ad37
14 changed files with 922 additions and 27 deletions

View File

@@ -0,0 +1,262 @@
# STEP-102 RenderSegment 无 Placement 段局部复用 Codex 任务 v1
## 1. Step 编号和目的
STEP-102:修复 full CPU 多 Retained Segment 下 placement aggregate gate 的不必要扩散,使**结构上不具备 placement 能力**的 Retained Segment 能在其他 placement-bearing Segment fallback/live 时继续独立 composite/reuse。
本 Step 的设计依据:
`docs/20_调查/RenderSegment多Retained段全局Placement门槛_粒子管理员审查说明_v3.md`。
## 2. 当前已冻结基线
- 仓库:`/Users/tei/dev/MapLibreParticleLab-step102`
- branch:`step102-placement-free-retained-locality`
- start HEAD:`6df21dac38f072c5ad35b8dfc7ec5217509602bc`
- upstream base:MapLibre Native core 既有冻结基线,不升级
- V19/V20 release worktree:冻结,只读,不修改
- STEP-100 source-scoped locality:必须保持
- STEP-101 teardown 修复:必须保持
- Fast Path:本 Step 不修改
- C-001:不升级
## 3. 本 Step 目标
必须实现:
1. 增加结构性 placement-capable range 查询;
2. full CPU 每个 Retained Segment 独立计算 placement gate;
3. placement-free Retained 不再被其他 Segment 的 placementChanged/symbolFadeChange 纯 aggregate gate 拖入 fallback;
4. placement-bearing Retained 继续使用现有保守 placement gate;
5. 从 full CPU `globalLiveFallback` 移除纯 aggregate placement 条件;
6. 保持真正全局 fail-closed 条件;
7. diagnostics 增加结构 placement、per-segment gate、实际 composite/live 与全局 main-buffer execution 信息;
8. RN diagnostics TS 类型做 additive 扩展;
9. 增加对应 C++ 测试源码与 Android Validation 场景源码;
10. 不改变 Style Z-order、mixed executor、C-001 和 RN method surface。
## 4. 非目标 / 禁止事项
禁止:
- placement-bearing Segment 之间的局部碰撞缓存;
- per-segment collision graph / symbol fade / placement fingerprint;
- Fast Path 改造;
- multi-continuous Fast Path;
- 重写 `visitRenderSegmentExecution()`;
- 修改 NavSea;
- 新增 NavSea 私有 Native API;
- 修改 C-001;
- 修改 V19/V20 release worktree/tag;
- 升级 MapLibre、NDK、Gradle、AGP、Java、Node 或其他工具链;
- 为通过测试降低 fail-closed 门槛;
- Android Gradle/build、APK/AAR build、ADB、设备操作;
- Jest。用户未要求 Jest,本任务不得新增或执行 Jest。
## 5. 允许修改范围
生产代码优先限制在:
- `src/mbgl/renderer/render_orchestrator.hpp`
- `src/mbgl/renderer/render_orchestrator.cpp`
- `src/mbgl/renderer/renderer_impl.cpp`
- `include/mbgl/renderer/render_segment.hpp`
- `src/mbgl/renderer/retained_underlay_runtime_state.hpp`
- `src/mbgl/renderer/render_segment_json.hpp`
- `packages/maplibre-particle-rn/src/types.ts`
测试/Validation 允许:
- `test/renderer/retained_underlay_cache.test.cpp`
- 与现有 Particle Validation 场景直接相关的最小 Android Test App 文件
文档允许:
- 本任务书
- `docs/50_Step记录/STEP-102_RenderSegment无Placement段局部复用_v1.md`
- `docs/报告/STEP-102_RenderSegment无Placement段局部复用_Codex完成报告_v1.md`
- `粒子开发记录.md`
- v3 审查说明只读,除非发现明确事实错误;若需改,停止并先报告
如果实现必须扩大生产代码范围,先停止并报告原因,不得自行扩张架构。
## 6. 禁止修改范围
- NavSea 仓库
- `navsea-particle-integration` contract/RFC/ADR
- Fast Path 逻辑、`tryRenderParticleFastFrame()`、`singleContinuousSegment()` 语义
- `retainedRangeUsesPlacement()` 现有动态语义
- mixed executor 算法
- Source `isLoaded()` 定义
- STEP-100 source revision/locality 语义
- STEP-101 teardown 生命周期语义
- renderer/backend 架构
- release/version/tag/publish 坐标
## 7. 实施要求
### 7.1 结构性 placement-capable 查询
新增 full CPU retained 专用查询,必须基于 layer 结构,而不是当前帧 placementData。
最小保守语义:
- range 中存在 `symbol` layer → placement-capable
- 无 `symbol` layer → placement-free
- custom/custom-drawable 继续由 unsupported gate 处理
不得用 `needsPlacement()==false` 证明 placement-free。
### 7.2 per-segment placement gate
每个 Retained Segment 至少形成两个明确事实:
- structural placement capability
- current full-CPU placement gate result
placement-free Segment 不受纯 global placement gate 阻塞。
placement-bearing Segment 本 Step 继续沿用旧保守 gate,不承诺稳定后 reuse。
变量/字段命名不得把 `symbolFadeChange==0` 错称为严格 placement stable。
### 7.3 full CPU mixed execution
`segmentSupported` 改为使用对应 Segment placement gate。
`globalLiveFallback` 仅移除纯 aggregate placement 条件,其他真正 global 条件继续保留。
不得重写 `visitRenderSegmentExecution()`。
### 7.4 fallback reason
`static-placement` 必须按 Segment 自身 placement capability/gate 判定,不能从 aggregate reason 无差别传播。
### 7.5 diagnostics
每个 Segment 最少新增等价语义:
- `placementCapable`
- `placementGatePassed`
- `actualExecution = composite | live`
全局最少新增等价语义:
- `placementChanged`
- `symbolFadeChange`
- `placementTransitionActive`
- `crossSourceCollisions`
- `mainBufferExecutionMode = mixed | global-live`
- `globalLiveFallbackReason`
`actualExecution` 必须反映本帧 main-buffer 最终实际执行,不能根据 cache finalDecision 推导。
Diagnostics 查询必须无副作用、不得推进 revision。
### 7.6 RN 类型
只扩展现有 diagnostics 返回类型,不新增 method,不改变 `setRenderSegmentPlan()` 输入 contract。
Android/iOS Shared Core JSON 语义保持一致。
## 8. 构建要求
Codex 本阶段**不得执行任何 Android 编译/Gradle/APK/AAR build**,也不得启动长时间 CMake/Ninja/Bazel 构建。
Codex 完成代码后只需提供精确验证命令,由后续 RDC/用户按规则执行。其中 Android 编译由用户手工执行。
Codex 可执行的机械静态检查仅限:
- `git diff --check`
- 只读 grep/rg/git diff/status
- 不产生构建产物的格式/静态检查(若仓库已有且无需安装依赖)
不得安装或升级系统依赖来制造测试 PASS。
## 9. 测试要求
Codex 必须新增/补强测试源码,但本阶段不执行大型测试。
至少覆盖:
1. placement-free R0 + placement-bearing R1:R0 可 composite,R1 live;
2. 含 symbol 但当前 placementData 为空:仍判定 placementCapable=true;
3. 两个 placement-free Retained:持续 reuse/composite;
4. source-local fading/mutation 回归不误伤 R0;
5. placement-bearing 在 transition disabled / `symbolFadeChange=1` 等状态仍不被本 Step 放宽;
6. 真正 global gate 继续 global-live/fail-closed;
7. topology 变化释放旧 resource;
8. cache decision=reuse 但 global-live 时,diagnostics 的 actualExecution 必须为 live;
9. diagnostics JSON/TS 字段语义一致且查询无副作用。
Android Validation 场景应能在后续手工构建后明确输出 STEP-102 验收所需字段,不依赖 NavSea。
## 10. 验收标准
代码审计门槛:
- [ ] 新增结构性 placement-capable 查询
- [ ] 未改变 `retainedRangeUsesPlacement()` 旧语义
- [ ] full CPU per-segment placement gate 生效
- [ ] placement-bearing 继续保守
- [ ] `globalLiveFallback` 只移除纯 aggregate placement 条件
- [ ] 真正 global gate 保留
- [ ] mixed executor 未重写
- [ ] Fast Path 无语义修改
- [ ] per-segment fallback reason 正确
- [ ] diagnostics 有 actualExecution 与 main-buffer mode/fallback reason
- [ ] RN method surface 不变
- [ ] C-001 不变
- [ ] 测试源码覆盖设计矩阵
- [ ] `git diff --check` PASS
运行态 PASS 不在 Codex 阶段宣称。Android 手工编译与真机验证未完成前,STEP-102 状态只能写“代码完成 / 等待验证”或同等非 PASS 状态。
## 11. Git 要求
- 工作分支:`step102-placement-free-retained-locality`
- 禁止修改 particle/main 工作区
- 禁止 `git add -A`、`git add .`、`git reset --hard`、`git clean`
- 只 scoped staging 本任务文件
- 不提交无关 dirty/untracked 文件
- Codex 可在实现完成、静态检查通过后创建 scoped implementation commit,但**不得 merge 到 particle/main、不得 push release 分支/tag、不得发布制品**
- 是否 push/merge 由后续独立验收决定
## 12. 必做输出
Codex 完成后必须交付:
1. 生产代码修改;
2. C++ 测试源码;
3. Android Validation STEP-102 场景源码/日志字段;
4. `docs/50_Step记录/STEP-102_RenderSegment无Placement段局部复用_v1.md`;
5. `docs/报告/STEP-102_RenderSegment无Placement段局部复用_Codex完成报告_v1.md`;
6. 追加 `粒子开发记录.md`;
7. 精确列出后续验证命令;
8. 明确列出所有 NOT RUN / BLOCKED 项;
9. Git diff/status/commit(若创建)事实。
## 13. 执行报告完整文件名
固定:
`docs/报告/STEP-102_RenderSegment无Placement段局部复用_Codex完成报告_v1.md`
同主题后续报告只递增 `_v2/_v3`,不得另造近义文件名。
## 14. 执行报告固定内容
至少包含:
1. 执行结论:只能按实际写代码完成/等待验证、PARTIAL、BLOCKED 或 FAIL;未运行 Android 不得写运行态 PASS
2. Git 基线:branch/start HEAD/end HEAD/status
3. 实际修改文件
4. 新增文件
5. 实现内容逐条对照任务书
6. 与 v3 设计差异
7. Fast Path / mixed executor / C-001 未修改审计
8. diagnostics 新字段与语义
9. 测试源码覆盖矩阵
10. 实际执行的静态检查
11. 未执行的 build/test/Android/设备项目
12. 用户手工 Android 构建命令
13. 后续 RDC/真机验收命令与硬门槛
14. 已知风险与未完成事项
15. Git commit 信息(若有)
## 15. 阻塞与停止条件
遇到以下任一情况必须停止扩大修改并报告:
- 结构性 placement capability 不能仅靠既有 layer type 安全判定,需要新增更大公共抽象;
- 必须修改 Fast Path 才能完成本任务;
- 必须重写 mixed executor;
- 必须改变 collision/Placement 核心算法;
- 必须修改 C-001 / RN method surface;
- 必须修改 NavSea;
- 必须扩大到 renderer/backend 架构重构;
- 发现 global revision 仍有未预期的 placement 推进,需要新设计;
- 需要升级工具链/依赖;
- 需要删除或弱化既有测试/安全门槛。
若实现范围保持在 v3 冻结边界内,可直接完成代码并停在“等待手工构建/运行验证”。

View File

@@ -0,0 +1,56 @@
# STEP-102 RenderSegment 无 Placement 段局部复用记录 v1
状态:**进行中(代码完成,等待独立构建与运行验证)**
日期:2026-10-08
## 目标与基线
- 工作区:`/Users/tei/dev/MapLibreParticleLab-step102`
- 分支:`step102-placement-free-retained-locality`
- 起点 HEAD:`6df21dac38f072c5ad35b8dfc7ec5217509602bc`
- 工作合同:`docs/40_任务/STEP-102_RenderSegment无Placement段局部复用_Codex任务_v1.md`
- 设计依据:`docs/20_调查/RenderSegment多Retained段全局Placement门槛_粒子管理员审查说明_v3.md`
- 发布坐标:V19/V20 工作区与发行内容冻结;本 Step 不创建版本。
目标是让结构上没有 symbol layer 的 Retained Segment 在其他段 placement 活跃时继续独立缓存并经 main-buffer composite。
## 必做项
- [x] 基于 style layer type 查询结构性 placement capability;不使用当前帧 `needsPlacement()` 证明 placement-free。
- [x] full CPU 每段计算 placement gate;含 symbol 的段仍受 changed、fade 与 transition 保守门槛约束。
- [x] `segmentSupported` 和 fallback reason 按段生成;full CPU global-live 只移除纯 aggregate placement 条件。
- [x] 保留 3D、debug、MapMode、sprite/image、light 等真实全局门槛和既有 revision/key 失效。
- [x] diagnostics 增加结构、逐段 gate、实际 main-buffer 执行及全局 placement/execution 字段;RN TS 只增加诊断字段。
- [x] 新增 C++ 源码测试与 Android STEP-102 Validation fixture/日志场景。
- [x] 静态核对 Fast Path、mixed executor、C-001、RN method surface 未修改。
- [ ] C++ 编译与定向测试、Android 用户手工构建、设备场景、iOS/Metal 验证:尚未执行。
## 允许与禁止范围
代码修改仅在任务书允许的 renderer、RN diagnostics 类型、`retained_underlay_cache.test.cpp` 和 Android Particle Validation 文件。未修改 `visitRenderSegmentExecution()`、`tryRenderParticleFastFrame()`、`retainedRangeUsesPlacement()`、Placement 算法、NavSea、C-001、发布文件或总则。既有未跟踪 v2/v3 审查说明及任务书保持原样。
## 构建、测试与当前结果
只读源码/diff 审计及 `git diff --check` 已执行,后者通过;新增未跟踪文件另用 `git diff --no-index --check` 检查,无空白错误(差异本身使退出码为 1)。CMake/Ninja/Bazel、GTest、Android Gradle、APK/AAR、ADB、设备、Jest、NavSea、发布全部 **NOT RUN**。静态检查不构成运行态 PASS。
## 必做输出
- STEP-102 生产代码、C++ 测试源码、Android Validation 场景源码。
- `docs/报告/STEP-102_RenderSegment无Placement段局部复用_Codex完成报告_v1.md`。
- 本记录与 `粒子开发记录.md` 追加。
- 报告中列出精确后续验证命令、硬门槛、NOT RUN 与剩余风险。
## 阻塞与下一步
目前没有已观察到的编译或运行失败;真正结果仍未知。由 RDC/用户按报告完成 Native focused tests、用户手工 Android APK 构建和 T1/T2 设备场景,核对 R0/R1 的 `placementCapable`、`placementGatePassed`、`actualExecution` 与 build/reuse/invalidation。构建或设备失败时保留首个真实错误与日志,再按任务边界修复;本 Step 未进入后续发行阶段。
## RDC 独立验证补记(2026-10-08)
- 从现有工作树、任务书、v3 审查依据和项目总则恢复审查;原 Codex 阶段的 NOT RUN 记录保留,以下为新增 RDC 事实。
- `git diff --check`:退出码 0,PASS;无关的 `platform/windows/vendor/vcpkg` 子模块状态变化不纳入本任务。
- 以 AppleClang 对 `test/renderer/retained_underlay_cache.test.cpp` 执行 `-std=c++20 -fsyntax-only`:退出码 0,语法检查 PASS;不是 GTest 运行结果。
- 本地临时独立 C++ helper smoke 测试:结构性 symbol 判定、placement gate、全局 live reason 共 10 个断言,10/10 PASS;测试文件位于 `/tmp/step102-placement-smoke.cpp`,不是正式 GTest。
- Mac CMake `cmake --preset macos-metal`:配置退出码 1,阻塞于缺少 `libuv`;未进入 GTest 编译或运行,未安装/升级依赖。日志 `/tmp/step102-cmake-config.log`。
- 额外审查并修复 Android STEP-102 Validation 切换入口:进入该场景时停止 STEP-093 诊断轮询、清除 STEP-093/097 标志,避免旧场景轮询污染 STEP-102 证据。
- Metal Bazel Core 构建 `bazel build -c opt --//:renderer=metal //:mbgl-core` 已完成:退出码 0,642/642 actions,耗时 875.118s,产物 `bazel-bin/libmbgl-core.a`,构建 PASS;日志 `/tmp/step102-bazel-core.log`。该 PASS 仅表示 Native Core 编译,不代表 GTest/Android/运行态 PASS。
- Android Gradle/APK/ADB 与设备验证依任务规则均未执行;STEP-102 继续保持“进行中 / 待 Android 实际验收”,不得标记 CLOSED / PASS。

View File

@@ -0,0 +1,121 @@
# STEP-102 RenderSegment 无 Placement 段局部复用 Codex 完成报告 v1
## 1. 执行结论
**代码完成,等待独立构建与运行验证;STEP-102 尚非 PASS。** 已按任务书实现 full CPU placement-free Retained Segment 局部复用、逐段 fallback reason 和实际 main-buffer 执行诊断。C++ 与 Android 测试均只交付源码,未运行。没有编译、设备或视觉结果,不能据此宣称无 stale、Z-order 错乱或 crash。
## 2. Git 基线与工作树
- 工作区:`/Users/tei/dev/MapLibreParticleLab-step102`
- 当前机器:macOS 15.7.7 / Darwin x86_64;未连接或操作设备。
- 分支:`step102-placement-free-retained-locality`
- start HEAD:`6df21dac38f072c5ad35b8dfc7ec5217509602bc`
- end HEAD:仍为 `6df21dac38f072c5ad35b8dfc7ec5217509602bc`;本阶段未 commit、push、merge。
- 子模块:`git submodule status` 均以 `-` 开头,本 worktree 未初始化;未更改。
- 工作树:仅下列任务文件有本阶段差异;任务开始时已经未跟踪的 v2/v3 审查说明及 STEP-102 任务书未修改、未暂存。
- V19/V20 release worktree、`particle/main`、tag 与发布坐标未修改;无新 artifact。
## 3. 实际修改与新增文件
生产代码:
1. `include/mbgl/renderer/render_segment.hpp`:结构查询、placement gate、全局 live reason 辅助函数及 diagnostics 字段。
2. `src/mbgl/renderer/render_orchestrator.hpp`、`src/mbgl/renderer/render_orchestrator.cpp`:对 full CPU 提供结构查询和 placement transition 查询;旧动态查询保持原样。
3. `src/mbgl/renderer/renderer_impl.cpp`:逐段 gate/fallback、全局 live reason、实际执行记录。
4. `src/mbgl/renderer/retained_underlay_runtime_state.hpp`、`src/mbgl/renderer/render_segment_json.hpp`:只读快照与 JSON 字段。
5. `packages/maplibre-particle-rn/src/types.ts`:现有 diagnostics 返回类型 additive 扩展。
测试/Validation:
6. `test/renderer/retained_underlay_cache.test.cpp`:新增 `RenderSegmentPlacementFree.*` C++ 测试源码。
7. `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt`:STEP-102 场景菜单、Intent 入口、轮询及审计日志。
8. 新增 `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step102PlacementFixture.kt`:离线 R-C-R symbol/placement-free 两组 fixture。
文档:新增 `docs/50_Step记录/STEP-102_RenderSegment无Placement段局部复用_v1.md`、本报告;追加 `粒子开发记录.md`。
## 4. 实现与任务书逐项对照
- **结构能力**:扫描同一 style-layer 元数据中的 `layerType == "symbol"`;越界 range 返回 placement-capable 以 fail-closed。`retainedRangeUsesPlacement()` 的 `needsPlacement()` 动态语义未变。custom/custom-drawable 仍由原 unsupported gate 处理。
- **逐段 gate**:placement-free 返回通过;placement-bearing 要求 `!placementChanged && symbolFadeChange == 0 && !placementTransitionActive`。`symbolFadeChange==0` 只是沿用保守 gate 的条件,不命名为严格 stable。transition disabled 且 fade=1 仍不能让含 symbol 段复用。
- **逐段 fallback**:优先明确 source、global resource、placement、layer transition、legacy/unsupported 等本段理由。`static-placement` 只会由本段 gate 失败产生;不再拷贝 aggregate placement/transition 理由给邻段。
- **full CPU main-buffer**:全局 live 条件只删掉 aggregate `!retainedPlacementStable`,其他既有 3D、debug、非 Continuous MapMode、sprite、global resources、light transition 条件保留。原 `visitRenderSegmentExecution()` 和 Style 顺序不变。camera/viewport/pixelRatio、topology、content/global revision、resource/compositor failure 的原有失效路径未放宽。
- **兼容/安全边界**:aggregate `retainedStaticUsesPlacement`、`retainedPlacementStable`、prepared-tree/Fast Path eligibility 与 revision 观察继续原用;含 symbol 的 retained 仍 fail-closed;C-001、RN method surface、`setRenderSegmentPlan()` 输入未变。
与 v3 审查设计无架构差异。逐段 gate 额外显式查询当前 placement transition,属于对 placement-bearing 的更保守判定;原 `retainedSegmentIsStable()` 内仍保留既有 transition 检查。测试和 Android 场景均不修改 Placement 核心算法。
## 5. diagnostics 字段与真实执行语义
每段新增 `placementCapable`、`placementGatePassed`、`actualExecution`。前者来自 style-layer 结构,第二项表示本帧 full CPU placement cache gate;Continuous 段的 gate 为非阻塞值。`actualExecution` 在 main-buffer 全局 live 分支或 mixed executor 的 composite 回调实际发生后记录。即使 `cache.finalDecision=reuse`,全局门槛触发时仍记录 `actualExecution=live`。
全局新增 `placementChanged`、`symbolFadeChange`、`placementTransitionActive`、`crossSourceCollisions`、`mainBufferExecutionMode`、`globalLiveFallbackReason`。`mixed` 表示进入 Style 顺序的 mixed executor,可以包含某些 Retained live;`global-live` 表示整帧 live 分支。尚未渲染时 `mainBufferExecutionMode/actualExecution=not-rendered`。查询只拷贝已记录快照,不推进 revision;Android/iOS Shared Core 使用同一 JSON writer,RN TS 只补类型。
## 6. 测试源码覆盖矩阵
| 合同场景 | 新增/现有测试源码 | 尚待运行证明 |
| --- | --- | --- |
| A:R0 free、C live、R1 symbol live;Style 顺序 | `RenderSegmentPlacementFree.MixedExecutionKeepsFreeRangesInStyleOrder` | Android T1 真实 GPU composite/live 与视觉 Z-order |
| B:symbol 当前 placementData 为空仍 capable | `StructuralCapabilityAndConservativeGate` 直接输入 symbol style 结构,不依赖 placementData | Android 空 bucket/placementData 条件仍需设备审计 |
| C:两个 free Retained 复用 | `MixedExecutionKeepsFreeRangesInStyleOrder` 中 R0/R2;Android T2 | 60 秒 build/reuse/invalidation 窗口 |
| D:source-local unloaded/fading/mutation | 既有 `RenderSegmentLocality.ReadinessAndFadeFollowOnlyDependencies` 与本轮两 free cache 断言;Android T1 动态 symbol source | 设备 R0 计数不误增 |
| E:bearing 保守 gate | `StructuralCapabilityAndConservativeGate` changed/fade=1/transition 活跃组合 | transition disabled 场景的 GPU 行为 |
| F:真实全局门槛 | `RealGlobalGatesStillForceGlobalLive`;既有 revision/cache tests | style/context/image/glyph/light 等设备失效 |
| G:拓扑与 key | `TopologyAndCameraChangesCannotReuseOldRetainedKey`;既有 `OldCacheReleaseCannotChargeReplacementAtSameIndex` | 真正 GPU resource 释放及无 stale texture |
| H:reuse 但 global-live | `DiagnosticsRecordActualMainBufferAndDoNotMutateOnRead` | 设备日志中 cache/actual 分离 |
测试源码只验证可隔离的策略、cache 决策、diagnostics 和 mixed traversal;无法单凭这些单元测试替代真实 renderer/GPU 场景。
## 7. Android Validation 场景
Test App 的 STEP-093 菜单新增 `STEP-102 PLACEMENT LOCALITY`,也可用 Intent `step102_scenario` 启动:
- `T1_FREE_SYMBOL`:R0 background/fill(无 symbol)→ C0 持续粒子 → R1 symbol;每 500ms 更新 R1 GeoJSON point,让 placement/source 活跃。
- `T2_TWO_FREE`:相同 R0/C0,R1 改为 fill;两个 Retained 均结构上 placement-free。
每个 `STEP102_SAMPLE` 记录固定 camera、placement 四个全局字段、main-buffer mode/reason、每段 capability/gate/actual、cache decision、build/reuse/invalidation/fallback。tick 8/130 打窗口标记。日志字段用于验收,不在 App 中自行宣告 PASS。
## 8. 实际检查与 NOT RUN
- 已执行:`git status --short`、`git diff`/`rg` 只读审计;`git diff --check` **PASS**。三个新增未跟踪文件另以 `git diff --no-index --check /dev/null <文件>` 检查,无空白错误;该命令因有差异正常退出 1。
- **NOT RUN**:CMake/Ninja/Bazel configure/build/link、C++ GTest、Android Native TestRunner、Android Gradle、APK/AAR、codegen、Jest、ADB、设备/截图/logcat、iOS/Metal、NavSea、发布、commit、push。
- 无 APK/AAR、大小、SHA-256、运行日志或最终性能结果。没有编译失败或测试失败的事实;缺少编译/运行证据。
## 9. 后续精确验证命令与硬门槛
先由 RDC 在本工作区运行 host 定向测试;若当前 Mac `libuv` 环境仍阻塞,应记录首个真实 configure 错误,而非改动依赖:
```bash
cmake --preset macos-metal
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
build-macos-metal/mbgl-test-runner --gtest_filter='RenderSegmentPlacementFree.*:RenderSegmentLocality.*:RenderSegmentDiagnostics.*'
```
**Android Validation APK 只由用户手工构建**,使用项目固定入口:
```bash
cd /Users/tei/dev/MapLibreParticleLab-step102
./tools/particle_android_validation/build_validation_apk.sh
```
构建成功后由 RDC/用户核对 APK ZIP、ABI、`libmaplibre.so`、SHA-256、branch/commit/dirty 状态,再在选定设备上独立运行。下列 `<SERIAL>` 需替换为实际 `adb devices` 返回的 serial;每场景单独清日志、force-stop、启动并留存完整 logcat:
```bash
adb -s <SERIAL> install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
adb -s <SERIAL> logcat -c
adb -s <SERIAL> shell am force-stop org.maplibre.android.testapp
adb -s <SERIAL> shell am start -n org.maplibre.android.testapp/.activity.particle.ParticleCoreLayerValidationActivity --es step102_scenario T1_FREE_SYMBOL
adb -s <SERIAL> logcat -v threadtime -d > /tmp/step102-t1-logcat.txt
rg 'STEP102_(SCENARIO_START|STYLE_READY|SAMPLE|WINDOW_MARKER)|Fatal signal|ANR' /tmp/step102-t1-logcat.txt
```
对 `T2_TWO_FREE` 重复 `logcat -c`、force-stop、启动、保存到 `/tmp/step102-t2-logcat.txt`。每组至少等待 tick 130(约 65 秒)后取最终日志。T1 硬门槛:固定 camera、`mainBufferExecutionMode=mixed` 的稳定窗口中 R0 `placementCapable=false`、`actualExecution=composite`、buildDelta=0、invalidationDelta=0、reuseDelta>0;R1 `placementCapable=true`,placement gate 失败期间 `actualExecution=live`,C0 live,Style Z-order 正确,无 stale/crash/ANR。T2 两个 Retained 均无 symbol,build 一次后持续 reuse/composite。若窗口内出现真实 global mutation,应结合 `globalLiveFallbackReason` 与 lastMutation 重新选择稳定窗口,不把合法 global invalidation 算作误伤。
还需独立做 STEP-100 source locality 与 STEP-101 teardown 回归,以及 global light/style/context/camera、topology 变动资源释放验证。iOS/Metal 只可在相同源码提交的 Mac/iOS 验收后声明通过。本阶段均未执行。
## 10. 风险、未完成事项与交接
- Android symbol glyph asset 与 placement 活跃窗口尚未由构建/设备核实;T1 若未出现 placement gate 失败,不构成主场景验收。
- 现有 C++ topology 测试验证 key/revision 无旧 cache reuse,不能证明真实 GPU 资源已释放;需设备/后端证据。
- `placementChanged` 与 `symbolFadeChange` 在 Fast Path 帧来自既有 prepared render tree;本 Step 没有改变 Fast Path 判定或语义。主验收只使用 `fullCpuFrame` 增长的 full CPU 窗口。
- 构建或运行失败应保留现场、回传首个真实错误及必要上下文;不得放宽测试或安全门槛。没有触及任务书要求 BLOCKED 的架构扩张条件。
本阶段未创建 Git commit,故无 implementation commit SHA;也未执行 merge、push 或远端 HEAD 回读。后续独立验收决定 scoped commit/merge 与发行流程。