feat: prepare STEP-114 guarded fast smoke validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
78
docs/20_调查/STEP-114_FastPath安全准入与闭环判定_v1.md
Normal file
78
docs/20_调查/STEP-114_FastPath安全准入与闭环判定_v1.md
Normal 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/用户待执行项。
|
||||||
41
docs/40_任务/STEP-114_FastPath安全准入与TestApp闭环_Codex任务_v1.md
Normal file
41
docs/40_任务/STEP-114_FastPath安全准入与TestApp闭环_Codex任务_v1.md
Normal 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 已修复。正式发行独立处理。
|
||||||
77
docs/50_Step记录/STEP-114_FastPath安全准入与TestApp闭环_v1.md
Normal file
77
docs/50_Step记录/STEP-114_FastPath安全准入与TestApp闭环_v1.md
Normal 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。
|
||||||
172
docs/60_测试/STEP-114_独立FastSmoke场景与RDC验收_v1.md
Normal file
172
docs/60_测试/STEP-114_独立FastSmoke场景与RDC验收_v1.md
Normal 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或正式发行。
|
||||||
134
docs/报告/STEP-114_FastPath安全准入与TestApp闭环_Codex完成报告_v1.md
Normal file
134
docs/报告/STEP-114_FastPath安全准入与TestApp闭环_Codex完成报告_v1.md
Normal 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。
|
||||||
@@ -67,6 +67,10 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
private var step113DiagnosticsMaxLines = 6
|
private var step113DiagnosticsMaxLines = 6
|
||||||
private var step113Scene: Step113PbfGateScene? = null
|
private var step113Scene: Step113PbfGateScene? = null
|
||||||
private var step113Scenario: Step113PbfGateScene.Scenario? = 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 step102Scenario: Step102PlacementFixture.Scenario? = null
|
||||||
private var step102Tick = 0L
|
private var step102Tick = 0L
|
||||||
private val step102Handler = Handler(Looper.getMainLooper())
|
private val step102Handler = Handler(Looper.getMainLooper())
|
||||||
@@ -356,6 +360,11 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
} else intent.getStringExtra("step113_scenario")
|
} else intent.getStringExtra("step113_scenario")
|
||||||
step113Scenario = savedStep113
|
step113Scenario = savedStep113
|
||||||
?.let { value -> Step113PbfGateScene.Scenario.values().firstOrNull { it.name == value } }
|
?.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)
|
super.onCreate(savedInstanceState)
|
||||||
Log.i(
|
Log.i(
|
||||||
TAG,
|
TAG,
|
||||||
@@ -369,6 +378,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
diagnostics = findViewById(R.id.validationDiagnostics)
|
diagnostics = findViewById(R.id.validationDiagnostics)
|
||||||
mapView.addOnDidFailLoadingMapListener { errorMessage ->
|
mapView.addOnDidFailLoadingMapListener { errorMessage ->
|
||||||
step113Scene?.onStyleLoadFailed(errorMessage)
|
step113Scene?.onStyleLoadFailed(errorMessage)
|
||||||
|
step114Scene?.onStyleLoadFailed(errorMessage)
|
||||||
if (navSeaPbfMode || purePbfMode) {
|
if (navSeaPbfMode || purePbfMode) {
|
||||||
Log.e(TAG, "NAVSEA_PBF_STYLE_ERROR reason=$errorMessage")
|
Log.e(TAG, "NAVSEA_PBF_STYLE_ERROR reason=$errorMessage")
|
||||||
} else if (realWeatherMode) {
|
} else if (realWeatherMode) {
|
||||||
@@ -389,7 +399,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
updateNavSeaPbfButtonState()
|
updateNavSeaPbfButtonState()
|
||||||
updatePbfModeButtonState()
|
updatePbfModeButtonState()
|
||||||
updateRetainedCacheButtonState()
|
updateRetainedCacheButtonState()
|
||||||
if (step113Scenario != null) startStep113Scene(step113Scenario!!)
|
if (step114Scenario != null) startStep114Scene(step114Scenario!!)
|
||||||
|
else if (step113Scenario != null) startStep113Scene(step113Scenario!!)
|
||||||
else if (step102Scenario != null) loadStep102Style()
|
else if (step102Scenario != null) loadStep102Style()
|
||||||
else if (step101Scenario != null) loadStep101Style()
|
else if (step101Scenario != null) loadStep101Style()
|
||||||
else if (step097Topology != null) loadStep097StencilStyle()
|
else if (step097Topology != null) loadStep097StencilStyle()
|
||||||
@@ -700,6 +711,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
menu.add(0, 18, 18, "STEP-101 TEARDOWN")
|
menu.add(0, 18, 18, "STEP-101 TEARDOWN")
|
||||||
menu.add(0, 19, 19, "STEP-102 PLACEMENT LOCALITY")
|
menu.add(0, 19, 19, "STEP-102 PLACEMENT LOCALITY")
|
||||||
menu.add(0, 20, 20, "STEP-113 FZG1-PBF GATE")
|
menu.add(0, 20, 20, "STEP-113 FZG1-PBF GATE")
|
||||||
|
menu.add(0, 21, 21, "STEP-114 独立 FAST_SMOKE(非正式 PBF)")
|
||||||
setOnMenuItemClickListener { item ->
|
setOnMenuItemClickListener { item ->
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
in Step093Topology.values().indices -> applyStep093Scene(Step093Topology.values()[item.itemId])
|
in Step093Topology.values().indices -> applyStep093Scene(Step093Topology.values()[item.itemId])
|
||||||
@@ -714,6 +726,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
18 -> showStep101Menu(anchor)
|
18 -> showStep101Menu(anchor)
|
||||||
19 -> showStep102Menu(anchor)
|
19 -> showStep102Menu(anchor)
|
||||||
20 -> showStep113Menu(anchor)
|
20 -> showStep113Menu(anchor)
|
||||||
|
21 -> showStep114Menu(anchor)
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -777,7 +790,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
private fun showStep113Menu(anchor: android.view.View) {
|
private fun showStep113Menu(anchor: android.view.View) {
|
||||||
PopupMenu(this, anchor).apply {
|
PopupMenu(this, anchor).apply {
|
||||||
Step113PbfGateScene.Scenario.values().forEachIndexed { index, scenario ->
|
Step113PbfGateScene.Scenario.values().forEachIndexed { index, scenario ->
|
||||||
menu.add(0, index, index, "STEP-113 ${scenario.name}")
|
menu.add(0, index, index, "STEP-113 正式 PBF · ${scenario.name}")
|
||||||
}
|
}
|
||||||
menu.add(0, 10, 10, "EXPORT LAST SNAPSHOT")
|
menu.add(0, 10, 10, "EXPORT LAST SNAPSHOT")
|
||||||
menu.add(0, 11, 11, "MUTATE PROBE SOURCE ONCE")
|
menu.add(0, 11, 11, "MUTATE PROBE SOURCE ONCE")
|
||||||
@@ -826,6 +839,9 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun stopStep113Scene() {
|
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()
|
step113Scene?.stop()
|
||||||
step113DiagnosticsHeight?.let { originalHeight ->
|
step113DiagnosticsHeight?.let { originalHeight ->
|
||||||
diagnostics.maxLines = step113DiagnosticsMaxLines
|
diagnostics.maxLines = step113DiagnosticsMaxLines
|
||||||
@@ -836,6 +852,72 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
step113Scenario = 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) {
|
private fun showStep102Menu(anchor: android.view.View) {
|
||||||
PopupMenu(this, anchor).apply {
|
PopupMenu(this, anchor).apply {
|
||||||
Step102PlacementFixture.Scenario.values().forEachIndexed { index, scenario ->
|
Step102PlacementFixture.Scenario.values().forEachIndexed { index, scenario ->
|
||||||
@@ -2481,8 +2563,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
} else if (step093Mode || step097Topology != null) startStep093DiagnosticsPolling()
|
} else if (step093Mode || step097Topology != null) startStep093DiagnosticsPolling()
|
||||||
}
|
}
|
||||||
override fun onResume() { super.onResume(); mapView.onResume(); step113Scene?.resume() }
|
override fun onResume() { super.onResume(); mapView.onResume(); step113Scene?.resume(); step114Scene?.resume() }
|
||||||
override fun onPause() { step113Scene?.pause(); mapView.onPause(); super.onPause() }
|
override fun onPause() { step113Scene?.pause(); step114Scene?.pause(); mapView.onPause(); super.onPause() }
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
step101Handler.removeCallbacksAndMessages(null)
|
step101Handler.removeCallbacksAndMessages(null)
|
||||||
step093Handler.removeCallbacks(step093DiagnosticsPoll)
|
step093Handler.removeCallbacks(step093DiagnosticsPoll)
|
||||||
@@ -2508,6 +2590,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
outState.putString("step113_scenario", step113Scenario?.name)
|
outState.putString("step113_scenario", step113Scenario?.name)
|
||||||
|
outState.putString("step114_scenario", step114Scenario?.name)
|
||||||
outState.putBoolean(STATE_ACTIVE, active)
|
outState.putBoolean(STATE_ACTIVE, active)
|
||||||
outState.putBoolean(STATE_ENABLED, enabled)
|
outState.putBoolean(STATE_ENABLED, enabled)
|
||||||
outState.putInt(STATE_MASK_MODE, maskMode)
|
outState.putInt(STATE_MASK_MODE, maskMode)
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <mln/renderer/render_segment.hpp>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace mln {
|
namespace mln {
|
||||||
|
|
||||||
|
// A stale controller alone cannot affect pixels in a scene with no Symbol layers.
|
||||||
|
// Check the complete structural layer table, including hidden/empty Symbols and
|
||||||
|
// Annotation Symbols. Never substitute this for per-R placement/build evidence.
|
||||||
|
inline bool particleFastPathHasPlacementTransition(const std::vector<StyleLayerDiagnostics>& layers,
|
||||||
|
bool controllerTransition) noexcept {
|
||||||
|
return controllerTransition && (layers.empty() || renderSegmentHasPlacementCapableLayer(
|
||||||
|
layers, 0, static_cast<int32_t>(layers.size())));
|
||||||
|
}
|
||||||
|
|
||||||
enum class ParticleFastPathMissReason : uint8_t {
|
enum class ParticleFastPathMissReason : uint8_t {
|
||||||
None,
|
None,
|
||||||
NoPreparedTree,
|
NoPreparedTree,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <mln/renderer/render_orchestrator.hpp>
|
#include <mln/renderer/render_orchestrator.hpp>
|
||||||
#include <mln/renderer/continuous_layer_range.hpp>
|
#include <mln/renderer/continuous_layer_range.hpp>
|
||||||
|
#include <mln/renderer/particle_fast_path.hpp>
|
||||||
|
|
||||||
#include <mln/annotation/annotation_manager.hpp>
|
#include <mln/annotation/annotation_manager.hpp>
|
||||||
#include <mln/layermanager/layer_manager.hpp>
|
#include <mln/layermanager/layer_manager.hpp>
|
||||||
@@ -1179,7 +1180,13 @@ RenderOrchestrator::RetainedSegmentSourceState RenderOrchestrator::retainedSegme
|
|||||||
// Keep its ordinary renderer active and fail closed only for cache reuse.
|
// Keep its ordinary renderer active and fail closed only for cache reuse.
|
||||||
for (const auto& sourceId : dependencies.sourceIds) {
|
for (const auto& sourceId : dependencies.sourceIds) {
|
||||||
const auto source = renderSources.find(sourceId);
|
const auto source = renderSources.find(sourceId);
|
||||||
if (source != renderSources.end() && source->second->baseImpl->type == style::SourceType::CustomMVTVector) {
|
if (source == renderSources.end()) {
|
||||||
|
// A missing source also has no revision-book entry. Report the
|
||||||
|
// concrete dependency failure before revisionFor collapses it to unknown.
|
||||||
|
result.reason = "missing-source";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (source->second->baseImpl->type == style::SourceType::CustomMVTVector) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1278,7 +1285,10 @@ ParticleFastPathDynamicState RenderOrchestrator::particleFastPathDynamicState(Ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.placementTransition = placementController.hasTransitions(timePoint);
|
const bool controllerTransition = placementController.hasTransitions(timePoint);
|
||||||
|
result.placementTransition = controllerTransition &&
|
||||||
|
(retainedStyleLayers.size() != renderLayers.size() ||
|
||||||
|
particleFastPathHasPlacementTransition(retainedStyleLayers, controllerTransition));
|
||||||
for (const auto& entry : renderSources) {
|
for (const auto& entry : renderSources) {
|
||||||
result.fadingTiles = result.fadingTiles || entry.second->hasFadingTiles();
|
result.fadingTiles = result.fadingTiles || entry.second->hasFadingTiles();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -526,6 +526,86 @@ TEST(ParticleFastPath, ParticleOnlyStableRevisionIsCandidate) {
|
|||||||
EXPECT_EQ(decision.reason, ParticleFastPathMissReason::None);
|
EXPECT_EQ(decision.reason, ParticleFastPathMissReason::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Step114FastPathSafety, PlacementFreeSceneIgnoresControllerStaleOnly) {
|
||||||
|
EXPECT_TRUE(particleFastPathHasPlacementTransition({}, true)); // Missing evidence stays closed.
|
||||||
|
const std::vector<StyleLayerDiagnostics> layers{
|
||||||
|
{0, "background", "background", true, std::nullopt, std::nullopt},
|
||||||
|
{1, "probe", "circle", true, "probe-source", std::nullopt},
|
||||||
|
{2, "color", "weather-color", true, "field", std::nullopt},
|
||||||
|
{3, "particle", "raster-particle", true, "field", std::nullopt}};
|
||||||
|
auto input = fastPathInput();
|
||||||
|
input.hasPlacementTransition = particleFastPathHasPlacementTransition(layers, true);
|
||||||
|
EXPECT_FALSE(input.hasPlacementTransition);
|
||||||
|
EXPECT_TRUE(evaluateParticleFastPath(input).candidate);
|
||||||
|
// This narrow change does not override any source/tile/resource/camera gate.
|
||||||
|
input.contentRevisionMatches = false;
|
||||||
|
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::ContentMutation);
|
||||||
|
input.contentRevisionMatches = true;
|
||||||
|
input.hasPendingChanges = true;
|
||||||
|
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::PendingChanges);
|
||||||
|
input.hasPendingChanges = false;
|
||||||
|
input.cameraMatches = false;
|
||||||
|
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::CameraChanged);
|
||||||
|
input.cameraMatches = true;
|
||||||
|
input.unsupportedContinuousLayer = true;
|
||||||
|
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::UnsupportedContinuousLayer);
|
||||||
|
input.unsupportedContinuousLayer = false;
|
||||||
|
input.annotationMutation = true;
|
||||||
|
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::AnnotationMutation);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Step114FastPathSafety, HiddenEmptyAndAnnotationSymbolsKeepPlacementProtection) {
|
||||||
|
for (const bool visible : {false, true}) {
|
||||||
|
for (const char* id : {"symbol-in-R", "symbol-in-C", "org.maplibre.annotations.points"}) {
|
||||||
|
const std::vector<StyleLayerDiagnostics> layers{
|
||||||
|
{0, "background", "background", true, std::nullopt, std::nullopt},
|
||||||
|
{1, id, "symbol", visible, "symbols", std::nullopt}};
|
||||||
|
EXPECT_TRUE(particleFastPathHasPlacementTransition(layers, true));
|
||||||
|
EXPECT_FALSE(particleFastPathHasPlacementTransition(layers, false));
|
||||||
|
auto input = fastPathInput();
|
||||||
|
input.hasPlacementTransition = particleFastPathHasPlacementTransition(layers, true);
|
||||||
|
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::PlacementOrFade);
|
||||||
|
EXPECT_FALSE(renderSegmentPlacementGatePassed(true, true, 0.0f, false));
|
||||||
|
EXPECT_FALSE(renderSegmentPlacementGatePassed(true, false, 0.0f, true));
|
||||||
|
EXPECT_FALSE(renderSegmentPlacementGatePassed(true, false, 1.0f, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Step114FastPathSafety, SourceRevisionRequiresRegistrationAndAsyncCompletionInvalidatesBuild) {
|
||||||
|
RenderSegmentRevisionBook book;
|
||||||
|
const RenderSegmentDependencies dependency{{"local-geojson"}, {}, false};
|
||||||
|
EXPECT_FALSE(book.revisionFor(dependency));
|
||||||
|
const auto missing = assessRenderSegmentSources(dependency, [](const std::string&)
|
||||||
|
-> std::optional<RenderSegmentSourceStatus> { return std::nullopt; });
|
||||||
|
EXPECT_FALSE(missing.ready);
|
||||||
|
EXPECT_FALSE(missing.stable);
|
||||||
|
EXPECT_STREQ(missing.reason, "missing-source");
|
||||||
|
book.advanceSource("local-geojson");
|
||||||
|
const auto initial = book.revisionFor(dependency);
|
||||||
|
ASSERT_TRUE(initial);
|
||||||
|
RetainedUnderlayCache cache;
|
||||||
|
EXPECT_EQ(cache.evaluate(key(), *initial, true), RetainedUnderlayDecision::Build);
|
||||||
|
cache.markBuildSucceeded();
|
||||||
|
EXPECT_EQ(cache.evaluate(key(), *initial, true), RetainedUnderlayDecision::Reuse);
|
||||||
|
book.advanceSource("local-geojson"); // Immutable source data change.
|
||||||
|
const auto submitted = book.revisionFor(dependency);
|
||||||
|
ASSERT_TRUE(submitted);
|
||||||
|
EXPECT_NE(initial, submitted);
|
||||||
|
EXPECT_EQ(cache.evaluate(key(), *submitted, false, "source-not-loaded"), RetainedUnderlayDecision::Fallback);
|
||||||
|
EXPECT_FALSE(cache.isValid());
|
||||||
|
book.advanceSource("local-geojson"); // Models onTileChanged after asynchronous layout.
|
||||||
|
const auto completed = book.revisionFor(dependency);
|
||||||
|
ASSERT_TRUE(completed);
|
||||||
|
EXPECT_NE(submitted, completed);
|
||||||
|
EXPECT_EQ(cache.evaluate(key(), *completed, true), RetainedUnderlayDecision::Build);
|
||||||
|
cache.markBuildSucceeded();
|
||||||
|
EXPECT_EQ(cache.evaluate(key(), *completed, true), RetainedUnderlayDecision::Reuse);
|
||||||
|
book.advanceGlobal(); // Image/glyph/style/context change remains global.
|
||||||
|
EXPECT_NE(completed, book.revisionFor(dependency));
|
||||||
|
EXPECT_EQ(cache.evaluate(key(), *book.revisionFor(dependency), true), RetainedUnderlayDecision::Build);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ParticleFastPath, RevisionCameraAndViewportMismatchReject) {
|
TEST(ParticleFastPath, RevisionCameraAndViewportMismatchReject) {
|
||||||
auto input = fastPathInput();
|
auto input = fastPathInput();
|
||||||
input.revisionMatches = false;
|
input.revisionMatches = false;
|
||||||
|
|||||||
11
粒子开发记录.md
11
粒子开发记录.md
@@ -1144,3 +1144,14 @@ Android V3-08 单次综合验收已完成并关闭。Native `13.4.1-particle.2`
|
|||||||
- 2026-10-11 STEP-113 RDC:标准Android Validation APK构建exit=0;FZG1-2安装成功,五场景稳定Logcat摘要(Native层64/66/66/66/67)均decisionMismatch=0;STATIC_RC Full875/Fast0,PARTIAL_RCRR Full861/Fast0,发现cache-invalid外#22/#23同时阻断。完整113层、手动Source变化、性能和回归未验收,整体PARTIAL;详细证据追加于STEP与报告。
|
- 2026-10-11 STEP-113 RDC:标准Android Validation APK构建exit=0;FZG1-2安装成功,五场景稳定Logcat摘要(Native层64/66/66/66/67)均decisionMismatch=0;STATIC_RC Full875/Fast0,PARTIAL_RCRR Full861/Fast0,发现cache-invalid外#22/#23同时阻断。完整113层、手动Source变化、性能和回归未验收,整体PARTIAL;详细证据追加于STEP与报告。
|
||||||
|
|
||||||
- 2026-10-11 STEP-113第二轮:FZG1-2已保存Source变更前后各120帧完整Snapshot,decisionMismatch=0;单次Source mutation=1,Native最后变更为step113-source-probe、影响C1;稳定119帧同时命中#3/#4/#22/#23。已确认内建annotation symbol进入C,且#22/#23可共享根因;Mac Native GTest因缺libuv未运行,NavSea完整113层/性能仍PARTIAL。详见STEP记录追加段落。
|
- 2026-10-11 STEP-113第二轮:FZG1-2已保存Source变更前后各120帧完整Snapshot,decisionMismatch=0;单次Source mutation=1,Native最后变更为step113-source-probe、影响C1;稳定119帧同时命中#3/#4/#22/#23。已确认内建annotation symbol进入C,且#22/#23可共享根因;Mac Native GTest因缺libuv未运行,NavSea完整113层/性能仍PARTIAL。详见STEP记录追加段落。
|
||||||
|
|
||||||
|
### 2026-10-11 STEP-114 Fast Path 安全准入与独立 Test App(源码交付 PARTIAL)
|
||||||
|
|
||||||
|
- 实际模型已核实 GPT-6.1-Sol(gpt-6.1-sol)/ high;Mac particle/main 起点 b02e54de3d45fcf612126ae8f037cba204f590ea。无模型切换、子代理、commit/push/发行。
|
||||||
|
- Shared Core 仅修正完整无 Symbol 场景被 controller stale 误挡的 #13 输入;隐藏/空/Annotation Symbol、空表/数量不一致继续保守。CustomMVTVector、非粒子 C、Symbol R Build/scene/资源/碰撞保护均保留 fail-closed。
|
||||||
|
- 回读旧 STEP-113 完整 JSON:navsea_delivery 实际是 Vector;R 依赖 tide-spots→tide 但 Native source 表缺 tide。缺源提前明确报 missing-source,不补假源、不放行缓存、不改原 PBF 五场景。
|
||||||
|
- 新增独立 FAST_SMOKE 与 Circle C/真实 Annotation/Symbol R 三个反向场景;只在独立空 Annotation 场景移除空内建 layer,使用 Native 真实 Full/Fast/Build/Reuse/24Gate,单次操作自动保存前后短延迟快照。新增3个非 Jest C++用例源码。
|
||||||
|
- 中文调查、场景/RDC精确验收命令、Step与完成报告已实际落盘;仅静态检查,未编译/测试/Gradle/ADB/Jest/设备。真实 Fast 未验收,Symbol R 结果证书 BLOCKED,不标 Fast Path 已修复。
|
||||||
|
- 下一步 RDC 独立审计/执行 Mac Native 定向检查;用户一次手工 Android/设备闭环并保存源码diff/制品SHA/逐帧/截图证据。详见 docs/报告/STEP-114_FastPath安全准入与TestApp闭环_Codex完成报告_v1.md 与 docs/60_测试/STEP-114_独立FastSmoke场景与RDC验收_v1.md。
|
||||||
|
|
||||||
|
- 2026-10-11 STEP-114 RDC:独立核对安全Gate改动、3项C++测试源码、Android Test App接线及STEP-113/102冻结SHA,git diff --check PASS。Mac缺libuv,单独C++ header检查因Xcode找不到标准头array环境阻塞;Android/真机/Fast未执行,PARTIAL,后续用户一次手工构建验收。
|
||||||
|
|||||||
Reference in New Issue
Block a user