9.1 KiB
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、QBINreferenceTimeMs或解析 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 完成报告。