15 KiB
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.hppParticleFieldDescriptor:fieldID、fieldType、displayDomain、validTime、尺寸、地理 bounds、noData、dataVersion、byteLength、stride、layout、encoding、可选fieldSha256。ParticleFieldSnapshot:拥有自己的std::string与std::vector<float>/std::vector<uint8_t>,保存 canonical Runtime 数据和generation。ParticleFieldAdapterError/ParticleFieldAdapterResult:结构化错误与可日志化 message。ParticleFieldDataAdapter::decode(const ParticleFieldDescriptor&, const std::vector<char>&, 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 尚未实现。
- 校验 identity/generation、dimensions/bounds/noData、
src/mbgl/particle/field/offline_fixture_particle_adapter.cpp/.hppOfflineFixtureParticleAdapter::load()是当前 caller 的 validation wrapper:解析maplibre-particle-fixture/v1、校验 fixture whitelist、metadata、checksum,再构造 neutral descriptor 调正式 adapter。
3.2 Caller 与 generation/identity
当前调用者为:
src/mbgl/style/sources/raster_array_source.cpp::tryLoad():初始 T0/T1 使用nextRasterArrayFieldGeneration,再写入Impl::field/nextField。RasterArraySource::tryLoadRuntimeField():runtime candidate bytes 完整后调用 fixture wrapper,candidate 在 adapter 前获得 Core generation。src/mbgl/renderer/layers/render_particle_validation_layer.cpp:Validation renderer 的 fixture-only fallback/validation 资源路径仍调用 fixture wrapper。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<char> 读取/异步 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。 - 涉及:
RasterArraySourcebuilder、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
目标范围:
- 定义一个最小 local/external binary feed 输入,使用 neutral descriptor + bytes,不使用 fixture whitelist。
- 让该 feed 通过
ParticleFieldDataAdapter生成 immutable Snapshot,并明确 generation、identity、validTime 和 payload ownership。 - 在
RasterArraySource增加最小 initial-window/next-feed bridge,继续调用既有updateRuntimeFieldWindow()和唯一 Timeline。 - 让 Timeline 使用 producer 提供的 forecast timestamps,验证 preload、PLAY、auto MIX、boundary、Gap/Invalid fail-closed。
- 仅使用离线 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。