118 lines
9.5 KiB
Markdown
118 lines
9.5 KiB
Markdown
# 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。
|
||
|
||
```text
|
||
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
|
||
|
||
```text
|
||
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
|
||
|
||
```json
|
||
{
|
||
"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 显式回答“怎样画”:
|
||
|
||
```text
|
||
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。
|