diff --git a/docs/20_调查/STEP-050_正式数据链路下一阶段调查报告_v1.md b/docs/20_调查/STEP-050_正式数据链路下一阶段调查报告_v1.md new file mode 100644 index 000000000..b6d4af4ea --- /dev/null +++ b/docs/20_调查/STEP-050_正式数据链路下一阶段调查报告_v1.md @@ -0,0 +1,199 @@ +# STEP-050 正式数据链路下一阶段调查报告 + +## 1. Git / 调查范围 + +- 仓库:`MapLibreParticleLab` +- 分支:`particle/main` +- Starting HEAD:`1572b6c3b1bcc2ab0007181bad0609e7d0134575` +- `origin/particle/main`:`1572b6c3b1bcc2ab0007181bad0609e7d0134575` +- Gitea API 分支 HEAD:同上 +- 调查性质:代码与既有文档只读调查;未修改 Core、Renderer、Android、Tests 或 build files。 + +## 2. 结论摘要 + +STEP-049 的 provider-neutral contract 已经存在,但当前真正落点仍是 Core 内部的“已校验 bytes → immutable Snapshot”边界,不是生产数据源。当前实际链路为: + +`FileSource::request(Resource::source(...))` +→ `RasterArraySource` 收集 metadata/field bytes +→ `OfflineFixtureParticleAdapter::load()` 解析并校验 `fixture-v1` +→ `ParticleFieldDataAdapter::decode()` +→ `ParticleFieldSnapshot` +→ `RasterArraySource::Impl::field/nextField` +→ `RasterArrayTimelineRuntime` / `updateRuntimeFieldWindow()` +→ Renderer。 + +仓库没有 Weather Service client、provider abstraction、正式天气网络 schema、GRIB/NetCDF decoder、Particle 生产 cache 或 production feed API。MapLibre 的通用 `FileSource`/HTTP 能力不能视为 Particle Weather Service。 + +推荐 STEP-051:**Local Production-like Field Feed 与 Core Snapshot Submit Bridge**。先用本地/离线外部 binary feed 驱动正式 neutral adapter 和既有 Runtime,建立稳定的 production-like producer/submit 边界,再在后续独立阶段讨论 provider/network decode。 + +## 3. ParticleFieldDataAdapter 现状 + +### 3.1 API 与真实文件 + +- `include/mbgl/particle/field/field_data_adapter.hpp` + - `ParticleFieldDescriptor`:`fieldID`、`fieldType`、`displayDomain`、`validTime`、尺寸、地理 bounds、`noData`、`dataVersion`、`byteLength`、`stride`、`layout`、`encoding`、可选 `fieldSha256`。 + - `ParticleFieldSnapshot`:拥有自己的 `std::string` 与 `std::vector`/`std::vector`,保存 canonical Runtime 数据和 `generation`。 + - `ParticleFieldAdapterError` / `ParticleFieldAdapterResult`:结构化错误与可日志化 message。 + - `ParticleFieldDataAdapter::decode(const ParticleFieldDescriptor&, const std::vector&, uint64_t generation)`:当前唯一正式 decode API。 +- `src/mbgl/particle/field/field_data_adapter.cpp` + - 校验 identity/generation、dimensions/bounds/noData、`interleaved-uv`、`float32-le`、stride、byte length 与 finite data。 + - 将 payload copy 到 Snapshot;adapter 不保存外部 bytes、Android 对象、Timeline、Renderer resource 或 provider 对象。 + - 当前 renderer 语义仍限定 `WIND` + `GLOBAL`,Wave/Tide/Current 尚未实现。 +- `src/mbgl/particle/field/offline_fixture_particle_adapter.cpp/.hpp` + - `OfflineFixtureParticleAdapter::load()` 是当前 caller 的 validation wrapper:解析 `maplibre-particle-fixture/v1`、校验 fixture whitelist、metadata、checksum,再构造 neutral descriptor 调正式 adapter。 + +### 3.2 Caller 与 generation/identity + +当前调用者为: + +1. `src/mbgl/style/sources/raster_array_source.cpp::tryLoad()`:初始 T0/T1 使用 `nextRasterArrayFieldGeneration`,再写入 `Impl::field/nextField`。 +2. `RasterArraySource::tryLoadRuntimeField()`:runtime candidate bytes 完整后调用 fixture wrapper,candidate 在 adapter 前获得 Core generation。 +3. `src/mbgl/renderer/layers/render_particle_validation_layer.cpp`:Validation renderer 的 fixture-only fallback/validation 资源路径仍调用 fixture wrapper。 +4. `test/style/source.test.cpp`:直接 adapter、fixture wrapper、Runtime Window 与 Timeline 定向测试。 + +因此当前没有 Weather provider 或 production caller。`generation` 是 Core runtime revision,由 Source/Runtime 负责分配;adapter 只校验调用者传入的非零 generation。`fieldID`/identity 由 descriptor producer 提供,adapter 不生成业务 identity。 + +## 4. ParticleFieldSnapshot ownership 与正式输入适配性 + +Snapshot 已基本 provider-neutral:不含 URL、token、provider 名称、网络客户端、Android 对象或 GPU resource;vector ownership 稳定,适合作为 Runtime 输入。现存 fixture 语义不再进入 Snapshot,fixture ID 在 wrapper 中映射为 `fieldID`。 + +它已包含当前 Runtime 所需的最小 grid/field metadata,但存在两项生产化边界: + +- `validTime` 已存在于 neutral descriptor/Snapshot,但当前 fixture metadata 的 `timestamp` 未映射,Timeline Source 仍使用固定逻辑时间。 +- Snapshot 的 `isValid()` 目前按 Wind/Global renderer 语义判定;未来 Ocean-only field/mask 需要另行设计,不应在本阶段偷偷扩大。 + +## 5. RasterArraySource / Runtime submit 入口 + +### 5.1 当前入口 + +`include/mbgl/style/sources/raster_array_source.hpp` 提供: + +- `updateRuntimeFieldWindow(ParticleFieldSnapshot newT1)`:已校验 Snapshot 进入唯一 authoritative T0/T1 Window 的正式入口。 +- `advanceRuntimeFieldWindow()` / `repeatRuntimeFieldWindow()`:当前 Source-owned validation convenience API。 +- `controlRuntimeTimeline()`:Android 低频命令桥,Core 持有 Timeline/Playback。 + +如果未来已经拥有一个合法 `ParticleFieldSnapshot`,最短路径是直接调用 `updateRuntimeFieldWindow(std::move(snapshot))`;它会复用现有 `OLD_T1 → NEW_T0`、only `NEW_T1`、Duplicate NOOP 和 fail-closed 逻辑。当前该路径只适合已有 T0/T1 的 Source,尚未提供独立的 production feed/submit queue 或外部 source ownership bridge。 + +### 5.2 当前 Source 的 fixture 限制 + +`src/mbgl/style/conversion/source.cpp::convertRasterArraySource()` 要求 `fixture`、`metadata`、`field`,runtime candidates 要求 `fixture`、`metadata`、`field`。`RasterArraySource::loadDescription()` 通过 MapLibre `FileSource` 请求 bytes,但随后仍直接调用 fixture wrapper。 + +所以 Source 已有可复用的异步 byte transport,却不是 production-neutral source:它的 style schema、成员命名、metadata parser 和 candidate preload 仍是 fixture validation 入口。下一阶段应增加一个极薄的 neutral feed/submit bridge,或把现有 Source 输入适配到 neutral descriptor;不应复制第二套 Window。 + +## 6. Timeline snapshot 数据来源 + +`RasterArraySource::tryInitializeTimeline()` 构造 `RasterArrayTimelineRuntime::Entry`: + +- `Impl::field` 作为 index 0/T0; +- `Impl::nextField` 作为 index 1/T1; +- `runtimeFields[index].snapshot` 作为后续 candidates; +- 逻辑时间当前硬编码为 `0`、`40000`、`80000 + index * 40000`。 + +`RasterArrayTimelineRuntime` 本身只消费 immutable Snapshot shared pointers、严格递增 timestamp 和有限 cache(capacity 8),其 `play/pause/tick/seek/step/gap/invalid/recover` 不认识 fixture schema。它能够消费外部 production Snapshot,但当前 Source 的 builder 只能从 fixture candidate 生成 Snapshot,且 candidate preload 仍由 fixture wrapper 完成。 + +结论:下一阶段原则上不改 Timeline;提供新的 Snapshot producer,并在 Source 侧把 producer 结果接到现有 entries/submit 入口即可。若生产 feed 需要真实 forecast timestamp,应在 producer/Source metadata 接线中补齐 Entry timestamp,而不是重做 Playback。 + +## 7. 仓库已有数据源能力与边界 + +### 已存在但不是 Particle 生产业务 + +- MapLibre 通用 `FileSource`、`Resource::source()`、异步 `request()`:可请求 asset/file/HTTP 资源,已有于 `GeoJSONSource`、`ImageSource`、`TileSource` 等通用 Source。 +- `src/mbgl/util/io.cpp::read_file()`:通用本地文件工具。 +- `platform/default/src/mbgl/storage/offline_database.cpp`、offline download:MapLibre 离线资源能力,不是天气字段 decoder 或 Particle field cache policy。 +- `test/fixtures/resources` 和 `particle-fixtures/*/field.f32`:测试资源。 + +### 不存在 + +搜索 `weather`、`forecast`、`provider`、`grib`、`netcdf`、`windy` 等 Particle 业务能力后,没有 Weather Service client、provider registry/factory、外部天气 schema、GRIB/NetCDF decoder、production field downloader 或专用 cache。当前唯一 binary field loader 是 fixture wrapper 的 `std::vector` 读取/异步 bytes 路径;没有 production local binary source。 + +因此不能因为 MapLibre 有 HTTP/FileSource 就直接引入网络。当前最小 production-like 验证应使用本地或离线 bytes,保持 provider-specific decode 在 adapter 上游,Renderer 只消费 Snapshot。 + +## 8. 候选方案比较 + +### 候选 A:Core Production Field Feed / Snapshot Submit API + +- 目标:建立接收已校验 descriptor/payload 或 Snapshot 的极薄 Core feed bridge。 +- 原因:当前 adapter 已稳定,但 Source 仍只有 fixture style schema;A 能先解耦 producer 与 fixture schema。 +- 涉及:新建小型 Core feed 类型,最小调整 `RasterArraySource`;复用 `ParticleFieldDataAdapter` 与 `updateRuntimeFieldWindow()`。 +- 新模块/新架构:可有一个小模块,不建立 Provider Framework。 +- network/vendor:不需要;不允许进入 Renderer。 +- Timeline/Renderer:不修改 Timeline 语义,不修改 Renderer。 +- RasterArraySource:需要最小 submit/initial-window 接线。 +- Android APK:若只做 Linux Core contract,可不需要;若增加 validation entry,则和 B 合并后一次 APK。 +- 风险:初始 T0/T1 建立、generation 分配与 timestamp ownership 需要明确,不能让 feed 管理第二套 Window。 + +### 候选 B:Local Binary / File-backed Production-like Field Source + +- 目标:用真实外部 binary + neutral metadata(不含 fixture whitelist/provider 信息)驱动 adapter、Timeline 和 Runtime,替代 synthetic fixture 验证生产链路。 +- 原因:当前缺口最具体,能验证 payload ownership、时间 metadata、preload 和 fail-closed,而不提前绑定天气供应商。 +- 涉及:local/asset feed loader、neutral metadata decode、`RasterArraySource` 最小 bridge、离线 fixture/test resource。 +- 新模块/新架构:一个轻量 source/loader;不需要 Provider Framework。 +- network/vendor:不需要;provider-specific decode 留在上游边界。 +- Timeline/Renderer:原则上不修改 Timeline/Renderer;复用现有 entries 与 Window。 +- RasterArraySource:需要最小修改,使 style/source 不再强制要求 `fixture`,或增加明确 production-like feed 构造入口。 +- Android APK:建议需要一次,用同一 APK 覆盖 local feed、Timeline、Window/Playback 回归;不单独拆 build。 +- 风险:本地 metadata schema 若设计过大,会重新形成第二套业务 schema;必须只覆盖当前 descriptor 与 forecast timestamp。 + +### 候选 C:Weather Service / Network Provider 接入骨架 + +- 目标:直接建立网络 weather service/provider client。 +- 原因:最终产品需要,但当前仓库没有成熟服务边界或外部 decoder。 +- 涉及:Android/Core network、鉴权、retry、cache、provider schema 和 decoder。 +- 新模块/新架构:会引入明显新架构。 +- network/vendor:需要,且风险最高;绝不能进入 Renderer。 +- Timeline/Renderer:应不改 Timeline/Renderer,但上游数据生命周期和错误映射复杂。 +- RasterArraySource:可能需要异步 feed/preload API。 +- Android APK:需要,且会显著扩大综合验收。 +- 风险:违反第一阶段离线边界,容易把 vendor/network 业务塞进 Core/Android 或模糊 fail-closed 语义。当前不推荐。 + +### 候选 D:Timeline/Preload 通用化 + +- 目标:先把 Timeline candidate/cache 从 fixture 命名和固定逻辑时间中抽象出来。 +- 原因:当前 `RasterArrayTimelineRuntime` 的消费接口其实已 neutral,只有 Source builder 仍 fixture-specific。 +- 涉及:`RasterArraySource` builder、timestamp metadata、candidate ownership。 +- 新模块/新架构:不应新增模块;属于现有 Source 小范围整理。 +- network/vendor:不需要。 +- Timeline/Renderer:尽量不修改 Timeline state machine,可能需要 Entry timestamp 接线;Renderer 不改。 +- Android APK:只有和 A/B 合并时需要一次。 +- 风险:单独做 D 会在没有真实 production producer 的情况下提前抽象,收益小于 B;不得变成第二套 cache/state machine。 + +## 9. 推荐下一实现阶段 + +- STEP 编号:`STEP-051` +- 中文名称:**Local Production-like Field Feed 与 Core Snapshot Submit Bridge** +- 英文短名:`Local Production-like Field Feed` + +目标范围: + +1. 定义一个最小 local/external binary feed 输入,使用 neutral descriptor + bytes,不使用 fixture whitelist。 +2. 让该 feed 通过 `ParticleFieldDataAdapter` 生成 immutable Snapshot,并明确 generation、identity、validTime 和 payload ownership。 +3. 在 `RasterArraySource` 增加最小 initial-window/next-feed bridge,继续调用既有 `updateRuntimeFieldWindow()` 和唯一 Timeline。 +4. 让 Timeline 使用 producer 提供的 forecast timestamps,验证 preload、PLAY、auto MIX、boundary、Gap/Invalid fail-closed。 +5. 仅使用离线 local/asset payload;增加 C++/Core 定向测试与一次 Android 综合验证入口。 + +明确不做: + +- 不进入 iOS/Metal; +- 不重做 Timeline、Playback 或 Runtime Field Window; +- 不重做 Renderer/GPU simulation/trail; +- 不把 provider/network/vendor 逻辑放入 Renderer; +- 不实现 Weather Service、HTTP client、鉴权、retry、cache server 或真实 provider decoder; +- 不合并 WeatherColorLayer 与 RasterParticleLayer; +- 不为 MIX observability 单独触发 Android build; +- 不为 Rotation/Viewport 技术债单独触发 Android build。 + +## 10. Rotation / Viewport 与 MIX 技术债 + +当前 `ParticleCoreLayerValidationActivity.kt` 具备 `onStart/onResume/onPause/onStop/onDestroy` 对 `MapView` 的标准生命周期转发,并通过 `onSaveInstanceState()` 保存 Validation 控件状态;代码中没有独立的 Particle viewport/camera 恢复状态机。历史文档仍记录过 Rotation/Viewport 自动恢复延期与后续 control-mode 恢复,现状不足以把它当作生产数据链路已完全证明的 contract。 + +STEP-051 推荐阶段不需要修改 Android Validation Host、viewport 或 source reload,因此 Rotation/Viewport 保持独立技术债,不合并、不为其单独 build。若 STEP-051 为增加 local feed 入口必须改 UI,则只在同一 APK 中顺带回归,不扩展为独立修复。 + +MIX UI 文本滞后仍是 `NON-BLOCKING observability`;Core MIX 已通过。STEP-051 若本来增加 Android 状态入口,可顺带刷新显示;若不需要 UI 变化则继续保留,不单独触发 build。 + +## 11. Android 成本与一次验收建议 + +STEP-051 先完成 Linux Core/OpenGL 精确编译、adapter/source/timeline 定向测试、离线 local feed 资源与验证入口,再由用户用一次 APK 覆盖:local feed load、Timeline READY/PLAY、auto MIX、至少两个 Window boundary、OLD_T1 reuse、only NEW_T1 upload、Pause/Resume、Step/Seek、Gap/Invalid、Duplicate NOOP、Renderer fail-closed、rotation/lifecycle 回归(若入口有改动)。不执行 Jest;Codex 不执行 Android Gradle、APK、ADB 或 App 启动。 + +## 12. 是否可进入 STEP-051 实现 MD + +可以。调查已确定最小实现方向、边界和验收策略:先写 STEP-051 设计/任务 MD,冻结 local feed descriptor/metadata、timestamp/generation ownership 与 Source bridge,再实现。不要在 STEP-051 直接引入 Weather Service/network/provider framework。 diff --git a/docs/报告/STEP-050_正式数据链路下一阶段调查_Codex完成报告_v1.md b/docs/报告/STEP-050_正式数据链路下一阶段调查_Codex完成报告_v1.md new file mode 100644 index 000000000..ac14a687a --- /dev/null +++ b/docs/报告/STEP-050_正式数据链路下一阶段调查_Codex完成报告_v1.md @@ -0,0 +1,61 @@ +# STEP-050 正式数据链路下一阶段调查 — Codex 完成报告 + +## 基线 + +- Starting HEAD:`1572b6c3b1bcc2ab0007181bad0609e7d0134575` +- origin/particle/main 起始 HEAD:`1572b6c3b1bcc2ab0007181bad0609e7d0134575` +- 当前分支:`particle/main` +- 调查性质:代码只读、文档新增 + +## 调查结论 + +STEP-049 的 Adapter 已停在“canonical descriptor + bytes → immutable Snapshot”层。当前真实业务入口仍是 `RasterArraySource` 的 fixture style schema 和 `OfflineFixtureParticleAdapter`;没有 Particle Weather Service、provider abstraction、生产网络下载、GRIB/NetCDF decoder、production cache 或外部生产 field source。 + +推荐下一实现阶段:`STEP-051 Local Production-like Field Feed 与 Core Snapshot Submit Bridge`。先以离线 local/asset binary 验证正式 neutral producer → Adapter → Snapshot → Timeline/Runtime 链路,再决定是否进入真实 provider/network 阶段。 + +## 实际检查的核心文件 + +- `include/mbgl/particle/field/field_data_adapter.hpp` +- `src/mbgl/particle/field/field_data_adapter.cpp` +- `src/mbgl/particle/field/offline_fixture_particle_adapter.hpp/.cpp` +- `include/mbgl/style/sources/raster_array_source.hpp` +- `src/mbgl/style/sources/raster_array_source.cpp` +- `src/mbgl/style/sources/raster_array_source_impl.hpp` +- `src/mbgl/style/conversion/source.cpp` +- `src/mbgl/renderer/layers/render_particle_validation_layer.cpp` +- `src/mbgl/renderer/layers/render_raster_particle_layer.hpp` +- `test/style/source.test.cpp` +- `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt` +- `particle-fixtures/README.md` 与 `particle-fixtures/*` +- MapLibre 通用 `FileSource`/`Resource`/offline storage 与 `src/mbgl/util/io.cpp` + +## 设计判断 + +- 不需要新 Provider Framework 或复杂新架构;STEP-051 可新增一个小型 local feed/submit 模块。 +- 不需要 network;不需要 vendor-specific code。 +- 原则上不修改 Timeline state machine、Runtime Field Window 或 Renderer;只在 Source/producer 边界做最小接线。 +- Snapshot ownership 已足够承载当前生产-like 输入;generation 由 Core Source/Runtime 分配,identity/validTime 由 producer descriptor 提供。 +- 当前 Timeline 已能消费外部 Snapshot,但 Source builder、style schema 和 candidate preload 仍 fixture-specific;Timeline timestamp 还使用固定逻辑时间。 + +## 候选与推荐 + +已比较四个候选:Core Snapshot Feed API、Local Binary/File-backed Production-like Source、Weather Service/Network Provider 骨架、Timeline/Preload 通用化。推荐合并前两者形成 STEP-051;不推荐现在引入网络 provider,也不建议在缺少真实 producer 时单独抽象 Timeline/cache。 + +## Rotation/Viewport 与 MIX + +当前 Android Activity 有标准 MapView lifecycle 转发和控件状态保存,但没有独立 Particle viewport recovery state machine;历史 Rotation/Viewport 议题不应并入不触及 Android host 的 STEP-051,也不应单独触发 APK。MIX UI 百分比文本滞后继续记录为 `NON-BLOCKING observability`;只有 STEP-051 本来修改 UI 时才顺带处理。 + +## 输出与验证 + +- 调查报告:`docs/20_调查/STEP-050_正式数据链路下一阶段调查报告_v1.md` +- 完成报告:本文件 +- 未修改功能代码、Renderer、Android、Tests、Gradle、CMake、iOS 或既有 Step 文档。 +- 未执行 Android Gradle/APK/ADB/App/实机测试。 +- 未执行 Jest。 +- `git diff --check`:待文档完成后执行。 +- commit hash:待提交 +- commit message:`docs: investigate production particle data pipeline` +- push result:待执行 +- remote branch:`origin/particle/main` +- Final HEAD:待提交 +- 阻塞项:无调查阻塞;STEP-051 需在实现前冻结 local feed metadata/timestamp/generation ownership。