Files
MapLibreParticleLab/docs/30_设计/STEP-098_QBIN_V4单帧与八帧滚动缓存设计_v1.md

9.1 KiB
Raw Blame History

STEP-098 QBIN V4 单帧与八帧滚动缓存设计

日期:2026-09-26 状态:已按接受的设计契约实现源码;Native 构建、定向测试与平台运行验收待 RDC 执行。

1. 权威契约

  • RFC-004:/Users/tei/dev/navsea-particle-integration/rfc/RFC-004_QBIN_V4单帧首显与八帧滚动缓存.md
  • ADR-001:/Users/tei/dev/navsea-particle-integration/adr/ADR-001_QBIN_V4单帧与滚动缓存语义.md
  • Contract C-002:/Users/tei/dev/navsea-particle-integration/contracts/C-002_QBIN_V4单帧与滚动缓存.md
  • COORD-005:/Users/tei/dev/navsea-particle-integration/coordination/active/COORD-005/决议.md
  • 读取时 Integration main / origin/main 均为 1dd576d3304e267e53ed64d31f9711d2350ce10a。四份文件为 ACCEPTED / DESIGN ONLY 或 IMPLEMENTATION_PENDING,不代表实现、设备或 V15 发布通过。
  • 原任务 v1 的旧 RFC-003 闸门和 docs/报告/STEP-098_公共契约阻塞报告_v1.md 属于此前状态。用户已确认旧 RFC-003/COORD-004 B 作废删除;本次不恢复、不改写任务书或旧报告。

八帧时间示例按 C-002 为 13:00–20:00。提交 21:00 后删除最早端 13:00。等距离时固定删除原最早端。

2. Core 存储与身份

EncodedFieldStore 按 fieldKey 保存独立 FieldSeries,默认每个序列容量 8。每个序列按 validTimeMs 严格升序存储;插入新帧使用 lower-bound 定位,不假定时间间隔。重复时间继续走 Duplicate、revision、stale、identity conflict 现有判定,不触发容量淘汰。

为同时支持新预报批次单帧首显和防止跨批次混插,分开处理两类兼容性:

  • 同 key 共存兼容性:grid、validity plane schema、所有 data plane 的数量与 schema 相同。不同 fieldID、QBIN referenceTimeMs 或解析 profile 的批次可以在序列中共存;否则有效的新日/新批次单帧可能无法进入序列。
  • 时间插值兼容性:除共存 schema 相同外,fieldID、referenceTimeMs、profileID 和 profileVersion 必须全部相同。resolve 只在相邻时间端点满足这些条件时返回双帧;不同批次之间的时间返回 unavailable。Ingress 会把已验证的 profile 身份保留到解码帧,避免校验后丢失身份。

满额插入先复制现有有序序列,在候选序列上执行淘汰和新增端点 schema 校验。候选有效后才 swap 到正式序列;grid/plane schema 不兼容时返回既有 RejectedIncompatible 路径,不淘汰现有帧。对容量 8 的正常有效插入返回既有 AcceptedInserted;RejectedCapacity 枚举与 RN 字符串继续保留兼容,不用于正常满额滚动。

对提交时间 n 和原序列最早/最晚端点 e/l,淘汰较远端:|n-e| >= |l-n| 时淘汰原最早端,否则淘汰原最晚端。距离计算避免无符号时间相减下溢。该规则不读取 forecast clock,所以仍符合 RN 先 submit、后 setTime 的顺序。

3. 单帧、双帧与投影

  • 精确匹配某个 validTimeMs 时 resolver 返回同一个不可变 frame 作为 T0/T1,mix=0。颜色和有合法 U/V 或方向分量的粒子可从该帧物化;标量帧仍不能通过向量绑定校验。
  • 非精确时间只在合法的相邻双帧 bracket 内插值,mix 按两端实际时间比例计算。落在窗口外、跨 batch/profile 边界或只有一帧但时间不匹配时返回 unavailable;不回退为最近帧。
  • 后续帧到达只更新实际变化的投影。新帧位于当前 bracket 外时保留 Source 的不可变物化 projection 与样本时刻;当前端点被淘汰后重新 resolve,原投影不可用则清除。
  • 单帧 materialization 只创建一个 ParticleFieldSnapshot 并让 T0/T1 共用它。Particle 和 WeatherColor 的首次资源安装也共享相同 endpoint 的 GPU texture/image/scale;GL 与 Metal 共用这段 renderer 代码及语义。设备资源实际行为仍须各平台独立验证。
  • 淘汰仅释放 Store 对已移除 frame 的引用。已发布 projection 或 GPU 更新仍持有共享 snapshot/texture 引用时,按 shared ownership 生命周期释放。

4. 内存观测范围

新增 DecodedFieldFrame::decodedByteSize()、FieldSeries::decodedByteSize() 与 EncodedFieldStore::decodedByteSize(),返回 validity bitset 加所有非 validity 原始 plane storage 的字节总和;共享 validity plane storage 不重复计数。该计数不包含 profile/容器元数据、物化 Float32 snapshot、图像、GL/Metal texture、粒子 state 或 trail。

公式为单帧 ceil(cellCount / 8) + Σ(plane payload bytes);序列与 Store 分别对帧及 key 求和。Native 定向测试代码覆盖滚动时每 key 保持 8 帧及此原始字节计数回落/清空,但本轮没有运行测试,也没有 CPU/GPU 峰值、32 帧总量或长期泄漏实测。8 帧不构成全局内存保证;不新增固定 MiB 上限或公开容量错误码。

5. API、职责与平台

RN TypeScript、Android Kotlin/JNI、iOS Objective-C++/Facade 保持 submitEncodedField(...) 与 setEncodedForecastTime(...) 两步调用及原有方法名、参数和结果名称;Codex 静态核对现有传递路径,本轮没有改 bridge。submit 不修改目标时钟,setTime 不下载数据。两调用仍非原子,不承诺无闪烁。

DecodedFieldFrame 增加内部解析 profile 身份字段及 decoded-byte 计数方法,改变该 C++ 数据结构的二进制布局;不改变 RN/Android/iOS bridge 的公开方法签名或结果枚举。Core 与两个平台必须使用同一源码重编;不得把既有 .13 AAR 或旧发布物当作新 ABI 消费证据。

NavSea、Server、下载/cache/manifest、日期 UI、QBIN 格式、Rice 解码、V3、Style Z-order、provider、公开 RN API 均未改。

6. 定向测试映射

以下是本轮新增或扩展的测试代码覆盖,不代表已执行:

项目 测试入口 本轮代码覆盖
T01 ParticleFieldBinding.SingleFrameProjectsAtItsExactTimeAndUpgradesToInterpolation;Source.RasterArrayV4SingleExactFrameMaterializesAndSecondFrameEnablesInterpolation 单帧精确颜色/向量 projection、T0=T1、mix=0、第二帧中点升级;标量-only 无向量绑定
T02 同上 第二帧到达后返回双帧与 mix=0.5
T03 ParticleFieldRuntime.UnorderedSubmissionsAreSortedAndReferenceCyclesStaySeparate 乱序排序;wind/wave 分 key;跨 reference/profile 只可单帧解析
T04 ParticleFieldRuntime.FullWindowRollsBothDirectionsAndBreaksTiesAtEarliest;Source.RasterArrayV4RollingWindowPreservesAnUnchangedActivePair 满 8 后向未来提交、淘汰最早端并保持活动 pair
T05 ParticleFieldRuntime.FullWindowRollsBothDirectionsAndBreaksTiesAtEarliest;Source rolling 测试 满额向过去提交并淘汰最晚端
T06 ParticleFieldRuntime.FullWindowRollsBothDirectionsAndBreaksTiesAtEarliest 中部等距插入固定淘汰最早端、有序且 8 帧
T07 Source.RasterArrayV4StoresNewReferenceAndProfileAsSingleFrameWithoutCrossBatchMix 新 reference/profile 批次精确首显、补帧后同批插值、回到早批次单帧
T08 ParticleFieldRuntime.RevisionIdentityAndIndependentCadence;既有 Source stale/revision cases Duplicate、identity conflict、revision;既有 stale 路径保留。满额下 revision 不占槽由同一 Store 分支处理
T09 Source.RasterArrayV4HostApiRejectsKeepPublicProjectionStable、...RejectsNumericOverflowBeforeReplacingAcceptedRevision、ParticleFieldRuntime.RejectedFullWindowInsertDoesNotEvictAndKeysRemainIndependent 解码/profile/数值和 grid schema 拒绝路径、有效帧/投影/clock 零副作用。数据帧入 Store 前仍由现有 ingress 完成解码与 profile 校验
T10 ParticleFieldBinding.MixChangesSampleStateButNotContentIdentityOrStorage、单帧升级 Source 测试 合法 bracket 的中间比例;精确端点以单帧语义表示
T11 既有 binding WindowUnavailable assertions;新单帧测试 非覆盖时间 unavailable,不用最近帧顶替
T12 Source.RasterArrayV4RollingWindowPreservesAnUnchangedActivePair 外围补帧/双向滚动不改变当前物化 projection 与 requested time
T13 Source.RasterArrayV4EvictedActiveFrameClearsItsMaterializedProjection 当前单帧端点淘汰后 fail-closed 清除物化 projection
T14 ParticleFieldRuntime.RejectedFullWindowInsertDoesNotEvictAndKeysRemainIndependent 两个 key 各自 8 帧,独立计数与字节
T15 单帧 binding/materialization 测试;GL/Metal renderer 资源安装源码 T0/T1 共用物化 snapshot;renderer 首装共用 endpoint texture/image。GPU/context-loss 运行验证未覆盖
T16 ParticleFieldRuntime.RepeatedBidirectionalRollingAndClearStayBounded 120 次双向提交、每 key 8 帧边界、clear/clearAll 和 decoded bytes 归零
T17 现有 FieldRuntime.ts、NativeWeatherRuntimeBridge.ts、Android/iOS bridge 源码静态核对 旧 API、类型、方法名和两步语义未变;RN 自动化/双平台 consumer build 未运行

所有测试代码的执行状态均为 NOT RUN。完整执行状态与 RDC 命令见 STEP-098 完成报告。