Files
MapLibreParticleLab/docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md
OpenAI Codex 44999edb25
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
docs: freeze V3 scalar vector field contract
2026-09-10 21:53:56 +08:00

9.5 KiB
Raw Blame History

MapLibreParticleLab V3 通用 Scalar / Vector Field 运行时架构设计

状态:冻结(V3.0 Contract,尚未实现) 决策:新 canonical schema 为 maplibre-particle-field/v2;RN package 首个 V3 实现版至少 13.4.1-particle.3,Native AAR 独立升版,绝不覆盖 .2/.1。

边界与目标

V3 从 NavSea canonical Field submission 开始,是 provider-neutral、business-neutral 的二维时序 Scalar / Vector runtime。它不解析 NSFT/PMTiles,不做 HTTP、manifest、time_index、缓存、业务单位转换,也不以 WIND/WAVE/RAIN/TIDE 决定能力。V3.0 固定为 0/1 scalar + 0/1 vector + 1 shared validity,拒绝 neither;不设计 N-channel graph。

WeatherV2 → NSFT → NavSea JS/TS(解码、拼接、单位/业务 canonical)
                         │ metadata + local scalar/vector file reference
                         ▼
MapLibreParticleLab V3: validate → Snapshot → Source Timeline T0/T1/MIX
                                      ├─ Generic Field Renderer (config.valueSource)
                                      └─ Particle Renderer (config.vectorSemantics)

当前实现为 v1 vector(U/V)+valid → Timeline → 两个各自 RGBA U/V texture renderer;V3 仍是一份 Source snapshot,两 renderer 独立显示、独立 GPU resource,而非两份 host field。

冻结数据 Contract

FieldSnapshot
  descriptor: fieldId, generation, validTime, grid, bounds, dataVersion, content hash
  scalar?: Float32[width*height]                    // scalar plane
  vector?: Float32[width*height*2], interleaved U/V // vector plane
  valid: UInt8[width*height]                        // shared validity
  vectorSemantics?: VELOCITY | DIRECTION            // only when vector exists
  • 所有 plane 必为 float32-le、NW-origin row-major;vector U=east、V=north。
  • scalar-only、vector-only、scalar+vector 合法;neither、尺寸/byteLength/finite/validity 不一致均 reject。
  • valid 是一个 snapshot-wide shared logical mask;V3.0 不加 channel mask。任何无效 cell 对全部现有 channel 无效。
  • validTime 是实际时刻,不假设固定间隔;T0/T1 必须相同 grid/bounds/channel presence/vector semantics/encoding,且都递增。
  • fieldId 是 opaque identity,删除 fieldType / displayDomain 对能力、adapter validation 和 compatibility 的强制依赖。领域/mask 选择若仍需要,作为 renderer config 的 business-neutral spatial policy,不能作为天气 enum。

v2 JSON 与 binary plane

{
  "schema":"maplibre-particle-field/v2", "fieldId":"opaque-id", "generation":1,
  "validTime":1760000000000, "dataVersion":1,
  "grid":{"width":257,"height":257,"origin":"north-west","rowOrder":"north-to-south"},
  "bounds":{"west":0,"south":0,"east":1,"north":1},
  "validity":{"encoding":"uint8","byteLength":66049},
  "scalar":{"encoding":"float32-le","byteLength":264196,"noData":-9999},
  "vector":{"layout":"interleaved-uv","encoding":"float32-le","byteLength":528392,"noData":-9999,"semantics":"DIRECTION"}
}

提交 API 冻结为 submitField(metadataJson, payloadRefs),其中 payloadRefs={validityFileUri, scalarFileUri?, vectorFileUri?};每个 URI 独立 local file、独立限额、先验证存在性与总大小再同步读入。metadata 仍小,绝不采用 JS number[]、JSON array、Base64。v2 validity 采用显式 uint8 plane,避免让任一 channel 的 noData 充当隐式共享 mask;Kotlin/JNI 最小修改为从单 byte[] 改为三个 nullable byte[](或等价 native struct),Core parser/adapter 接收 plane bundle。若实施时 JNI 参数数量/ABI 风险高,可在 Kotlin 读三文件后封装为有明确 offsets/lengths 的 v2 binary container;不能复用 v1 的单 payload 产生歧义。

v1/v2 版本决策

选择 v2,而非 v1 additive:v1 单 vector、单 payload、velocity snapshot 和 WIND/GLOBAL validation 都是结构性约束;在同 schema 内重载 payload 会令 scalar-only 与 scalar+vector 的 bytes、validity、noData、compatibility ambiguous。

  • v1 vector-only 继续由旧 parser/adapter 可读,作为 compatibility ingress;最薄兼容层将 v1 decode 规范化成 v2 内部 snapshot(vector=present、shared validity 从 v1 U/V noData 派生、semantics=VELOCITY)。
  • Runtime 内只使用一个 V3 snapshot/timeline 实现;允许 v1 首帧与 v2 vector-only 共同进入同 Runtime,前提是 normalize 后 descriptor/semantics/channel presence 完全 compatible。否则 fail-closed,不维护两套 runtime。
  • v1 不增加 scalar 字段;发布 .3 才公开 v2 payloadRefs,旧 .2 ABI 可继续作为 legacy ingress,Native/AAR coordinate 独立推进。

Renderer Contract(与数据分离)

FieldSnapshot 只回答“有什么”;renderer config 显式回答“怎样画”:

FieldRendererConfig.valueSource = SCALAR | VECTOR_MAGNITUDE
ParticleRendererConfig.vectorSemantics = VELOCITY | DIRECTION
ParticleRendererConfig.advectionScale (finite, >0; display/config policy)

SCALAR 要求 scalar present;VECTOR_MAGNITUDE 要求 vector present;不满足即该 renderer unavailable/reject,绝不猜。FieldColorStop.value 保持 generic public semantics。Native 内部 speedMps、shader default 0..30m/s 和 speed-color 命名将在 V3-03 统一改为 generic fieldValue/value; legacy JNI ABI 可薄转换,不能改变 RN .2。

SnapshotGpuResources 为每一个 T0/T1 快照包含 scalarTexture?、vectorTexture?、validityTexture 与各 plane scale/format。rollover 把整个 bundle(而不是单 texture)从 OLD_T1 原子复用为 NEW_T0;只上传 NEW_T1 bundle。style reload/context loss 清空 renderer-owned resource,Source timeline 保留;下一帧从当前 immutable window 重建。Field ON/Particle OFF、Field OFF/Particle ON、双 ON 都继续合法;无 vector 时 Particle 不可用,不让 scalar 伪装 U/V。

Vector semantics

VELOCITY 保持当前 Wind/Tide 行为:T0/T1 component-wise mix,m/s 地理推进,advectionScale=1 默认。

DIRECTION 用 unit U/V 表示方向,不代表速度。先 component-wise mix,再 normalize;若长度非 finite 或小于冻结阈值(建议 1e-5)则 cell invalid、粒子不移动/respawn,fail-closed。反向邻帧在 mix 约 0.5 归零亦遵此规则,不能任选方向。实际视觉推进量只由 particle renderer 的 advectionScale(及现有 visual clock)控制,必须明确由宿主/renderer 配置提供,不能从 Wave height/period 或业务名称推算。本任务未定义 Wave physics。

WeatherV2→NavSea→V3 映射

业务输入 NavSea canonical V3 Field renderer Particle
Wind vector U/V, VELOCITY VECTOR_MAGNITUDE VELOCITY
Tide/Current vector U/V, VELOCITY VECTOR_MAGNITUDE VELOCITY
Rain scalar rainRate + shared valid SCALAR unavailable
Wave scalar height + unit direction-to U/V, DIRECTION SCALAR DIRECTION

Wave period 留在 NavSea model/cache,不增加第二 scalar;Rain precipitation type 未写入 NSFT,V3 不推断或补造。NavSea 在拼接 257 tile 时保持 (tiles*256)+1,Core 不理解 tile。

受影响文件矩阵与风险

区域 预计修改
Core field_data_adapter.*、local_particle_field_feed.cpp、raster_array_source.*、fixtures/tests
shader/renderer render_weather_color_layer.cpp、render_particle_validation_layer.cpp、particle_gpu_validation.fragment.glsl、UBO/manifest/generator 后端产物
Android WeatherRuntime.java、MapLibreMap.java、native_map_view.*、JNI registration
RN types.ts、FieldRuntime.ts、NativeWeatherRuntimeBridge.ts、Kotlin TurboModule/codegen、package version
Test Host style fragment、v1 regression 与 Wind/Rain/Wave/synthetic fixtures;不引入业务逻辑到 package

风险:plane 增加 GPU memory、3 URI read 后 Java/C++ copy 峰值、texture precision/scale、v1/v2 ABI 共存、direction opposite interpolation、style/context lifecycle 和 timeline compatibility。缓解是 bounded total bytes、明确 channel-presence compatibility、bundle-level reuse、fail-closed 和先 Linux/Core verification。

实施批次与门槛

批次 修改范围 / 不修改范围 完成门槛 / 下一入口
V3-02 Core Contract + Scalar Adapter v2 descriptor/snapshot/parser、v1 normalize;不改 GPU/JNI 单元/fixture覆盖三组合、invalid/compatibility;进入 V3-03
V3-03 GPU + Generic Field scalar/vector/validity bundle、FieldValueSource;不改 particle semantics/JNI OpenGL Core fixture:scalar 与 magnitude 不混读;进入 V3-04
V3-04 Vector Semantics DIRECTION normalize、advectionScale;不改 Wave physics VELOCITY Wind 回归、opposite/zero fail-closed;进入 V3-05
V3-05 Android/JNI/RN ingress v2 plane refs、新 .3,保留 v1 thin ingress payload limits/ownership/static checks;进入 V3-06
V3-06 fixtures 真实 Wind/Rain/Wave,Tide 回归、wave synthetic T0/T1/MIX/reuse/style/visibility/gap coverage;进入 V3-07
V3-07 Linux freeze Core/OpenGL 精确验证与文档 双 renderer/lifecycle/perf evidence;进入唯一 Android 周期
V3-08 Android 综合验收 一次 AAR/APK/device;不分散小周期 Wind/Rain/Wave/Tide、三 visibility 组合、seek/play/pause/rollover/style reload、hard-gate log 与 memory/perf evidence

Wave synthetic fixture 固定:scalar west-low→east-high、vector 全场 north;色场必须只东西渐变,粒子必须只向北。所有 fixture还覆盖 T0/T1 scalar、T0/T1 vector、共同 MIX、OLD_T1→NEW_T0、only NEW_T1 upload、invalid/gap fail-closed。