Files
MapLibreParticleLab/docs/40_任务/STEP-098_QBIN_V4单帧首显与八帧滚动缓存_Codex任务_v1.md

20 KiB
Raw Blame History

STEP-098:QBIN V4 单帧首显与按 fieldKey 独立的八帧滚动缓存 — Codex 任务 v1

状态:任务书 / 待契约闸门通过后执行。本任务书不代表代码已经修改、编译或双平台运行验收通过。

0. 目标与执行环境

本任务落实已讨论的目标模型:单帧可独立显示;第二帧到达后支持正常时间插值;后续时间片在 Native 内部进入每个 fieldKey 独立的、按 validTime 升序排序的固定 8 帧滚动缓存。 满 8 帧提交新的合法时间片时应自动淘汰,不应因正常满额返回 REJECTED_CAPACITY。

当前用户维护和修改的源码以 Mac 为准:/Users/tei/dev/MapLibreParticleLab,分支 particle/main。编写本任务书时 Mac HEAD / origin HEAD 均为 0d13552bed84f427dd43cae9856391bd26622179。执行前重新查询 Git 事实,不以该 SHA 或本文行号覆盖后续提交;不要拿 Linux 旧工作树代替 Mac 代码。任务 MD 的目标归档位置为 docs/40_任务/STEP-098_QBIN_V4单帧首显与八帧滚动缓存_Codex任务_v1.md;不要创建 docs/任务/。

本阶段以 Shared Core 公共语义和现有 RN V4 公共接口为实施目标,不在 NavSea/Weather Server 中实施下载策略;Android 与 iOS 使用同一共享语义,平台专项编译/实体机验收分别另行完成。不得因 Mac 为编辑机而建立 iOS 专属 Core 行为。

固定边界

  • RN:负责获取预报目录、QBIN 文件、本地下载缓存、用户选择的预报时间、异步命令 latest-wins 和 UI。RN 已提供 FieldRuntime.submitEncodedField(...)、FieldRuntime.setEncodedForecastTime(...),先 await 前者成功,再 await 后者;无需新增组合提交 API。
  • Shared Core/Native:负责 QBIN 校验、存储、每 fieldKey 内排序/淘汰/复用、单帧/双帧解析、颜色与粒子投影、GPU 资源正确性。
  • 不增加 Native→RN 索取数据回调;Native 不接入 provider、HTTP、manifest、下载、缓存文件管理或 NavSea 业务语义。
  • 不修改 QBIN 文件格式、Rice 解码语义、现有公开方法签名、Style Z-order 或 V3 路径,不在 JS 伪造第二帧,也不在 Host 手动清空 8 帧来实现正常拖动。

1. 首要闸门:Integration 公共契约

  1. 先经 RDC 检查并同步 /Users/tei/dev/navsea-particle-integration 的最新 origin/main(git fetch origin main + 确认可 fast-forward;尊重现有本地改动)。读取该仓库 README.md、WORKFLOW.md、coordination/TEAM_MODEL.md、contracts/、rfc/、adr/、status/、compatibility/、coordination/active/COORD-004/ 和 instructions/PARTICLE_PROJECT_INSTRUCTIONS.md。
  2. 编写本任务书时 Integration HEAD 为 318f8ccf911ac3136c3086a1d14e61f83e2d4bce;RFC-003_编码场两帧首显与增量补帧语义.md 仍处于 DRAFT,其第一条要求至少两帧、满额返回容量拒绝,与本任务新目标明确冲突。本任务书不是已经接受的跨平台 RFC。
  3. 开始改公共源码前,必须由粒子负责人/Integration 按既定流程正式更新 RFC-003、COORD/Contract 或 ADR,接受下述单帧和滚动淘汰语义,明确 Android/iOS 共用。 Codex 不得自行把 DRAFT 当 ACCEPTED,也不得静默更改 Integration 文档替代负责人决定。
  4. 如果执行时仍没有已接受且不冲突的正式依据:先在 docs/报告/STEP-098_公共契约阻塞报告_v1.md 写出当前 SHA、冲突条目、拟实施语义与所需协调决策,停止功能修改及 push,状态记为 BLOCKED_CONTRACT,交回粒子负责人。不得绕过闸门做 Android/iOS 两套实现。
  5. 若 Integration 已更新且批准,记录精确文档路径、版本与 commit SHA,后续实现以正式版本为准;如正式结论与本文不同,应停止并回报,不自行择一。

2. 开始前必须核对的 Mac 实际基线

先读根项目规范、PARTICLE_BASELINE.md(如存在)、docs/00_项目规范/、docs/00_项目总则/README.md 指定总则、与 STEP-097 的正在实施范围。读取并建立文件/函数对照表:

  • include/mbgl/particle/field/field_runtime.hpp、src/mbgl/particle/field/field_runtime.cpp:FieldSeries、EncodedFieldStore、resolve、容量及 revision/identity。
  • src/mbgl/particle/field/field_binding.cpp、src/mbgl/particle/field/field_projection_materialization.cpp:单/双帧投影、独立绑定和物化。
  • src/mbgl/style/sources/raster_array_source.cpp 及相关头文件:submitEncodedField、setRequestedForecastTime、refreshV4RendererState、现有 projection equality/notification。
  • src/mbgl/renderer/layers/render_weather_color_layer.cpp、render_particle_validation_layer.cpp 及 shader/texture 资源:单帧/双帧下颜色、粒子、轨迹和 GPU 复用。
  • packages/maplibre-particle-rn/src/FieldRuntime.ts、NativeWeatherRuntimeBridge.ts、Android Kotlin bridge/JNI、iOS WeatherRuntimeBridge.mm/MLNParticleFieldRuntime.mm:确认两个现有公开调用完整传递结果。
  • test/particle/field_runtime.test.cpp、test/particle/field_binding.test.cpp、test/source/raster_array_v4_host_api.test.cpp 及现有 Renderer 定向验证入口。

编写任务书时源码事实:FieldSeries::submit 对第 9 个不同时间直接 RejectedCapacity;resolve 对 frames.size()<2 返回空;submitEncodedField 已会刷新投影但不修改 forecast time。以上仅供定位,若新 HEAD 有变化应以实际代码为准。若 STEP-097 仍在改相同的 runtime/projection/Renderer 文件,先确认依赖顺序,不得并行覆盖或回滚已完成的修复;尤其保留 STEP-097 的输入校验、forecast clock、GPU 身份、资源生命周期等新修正。

3. 正式目标语义

3.1 缓存所有权、容量与排序

  • EncodedFieldStore 按 fieldKey 分别持有 FieldSeries。wind、wave、rain、tide 使用不同 key 时分别最多 8 帧;同 key 的多个 Layer/Binding 共享该序列。不能把四类天气合为“全局 8 帧”。
  • 每帧按 frameContentIdentity.validTimeMs 升序保存,提交顺序不影响排序;不得假设固定一小时/三小时间隔。
  • 已有相同 validTime:先走现有 Duplicate、identity conflict、stale、合法 revision 替换语义;不额外占一个槽,不因为满额淘汰别的帧。
  • 不同时间且不足 8 帧:先完整校验,按时间有序插入。
  • 不同时间且已满 8 帧:在成功通过解码、profile、grid/plane、身份和相邻兼容性校验后,选择淘汰一帧并原子提交新有序集合。正常满额替换成功返回现有 ACCEPTED_INSERTED(或正式合同要求的既有成功语义),不返回 REJECTED_CAPACITY。为了 TypeScript/ABI 向后兼容可以保留旧结果枚举,但正常容量=8的有效插入路径不再产生它;不可偷偷更名公共结果。
  • 淘汰只作用于正在提交的 fieldKey,不影响其他天气种类、Bindings、requestedForecastTime、V3 数据或 Style。

3.2 双向淘汰,按新提交帧时间取中心

由于既定调用顺序是先 submitEncodedField()、后 setEncodedForecastTime(),提交阶段不能依赖尚未下发的新目标时间。本版使用“新提交帧 validTime 为中心,淘汰时间距离最远的端点”的确定性规则;新帧本身必须留下。对于已升序的满额序列,只在最早端或最晚端候选之间选择;若距离相同,按正式合同规定稳定决胜,允许优先避免淘汰当前显示端点,但不可牺牲新提交帧。不得永远只删除最旧帧。

必须覆盖:

  • 原有示意 13:00 ... 21:00 共 8 帧;提交 22:00 → 删除最早端 13:00,插入 22:00,最终仍 8 帧。
  • 已向后滚动后提交 12:00 → 删除最远的最晚端,重新保留 12:00;向过去拖动同样成立。
  • 已满时提交位于内部的新时刻 → 仍保持有序、8 帧和确定的端点淘汰。
  • 今天跳到后天某一时刻 → 接收该时刻 QBIN,而不是要求 RN 先清空旧窗口或填齐 8 帧。之后逐帧提交,Native 自行滚动。
  • 乱序提交、Duplicate、同时间新 revision 不应造成意外淘汰。
  • 插入不兼容数据或解码失败时,零淘汰、零 Store/Projection/Clock 修改;不得先删除旧帧再验证。

3.3 单帧首显与双帧升级

  • 当请求时间精确对应某个已验证 validTime,即使整个 Store 只有一帧,或它与其余 7 帧相隔很远无法形成合理双帧窗口,也应能够显示该帧:颜色为这一帧的值;有有效 U/V(或规范允许的方向向量)时粒子可持续运动,天气时间不变不等于粒子停止;仅标量帧不能伪装成可平流向量。
  • 单帧在运行时具有真实单端点语义。可在内部使用同一 Snapshot 作为 T0/T1、mix=0 来复用现有双纹理 shader,但不得向 RN 要求伪造第二个 QBIN 或捏造第二个不同 validTime;优先共享解码结果、物化 Snapshot 和 GPU 纹理,避免同一单帧无意义地双份常驻。
  • 第二帧补入后,如果目标时间被合法的相邻端点覆盖,则自动升级为双帧插值,保持既有 T0/T1/MIX,端点、mix 与实际预报时间对应;如果并未覆盖,保持合法单帧或 fail-closed,不能强行把不相关的旧帧插值进去。
  • 目标时间落在两帧之间且有合法覆盖时执行正常时间插值;目标时间既不精确匹配可用单帧、也没有合法双帧覆盖时返回明确 unavailable,不使用“最近旧帧”冒充该时刻。RN 若选择最近预报帧首显,必须以该帧实际 validTime 作为已显示时刻;用户日历选中时间与实际显示时间不可混称。例如用户初始选择 13:30,只有最近的 12:00 QBIN 时,RN 可先提交该帧并调用现有 setEncodedForecastTime(12:00) 显示 12:00 静态预报,仍将 13:30 保留为业务层待完成目标;等 15:00 帧到达后再设置 setEncodedForecastTime(13:30) 进入插值。不得在 Native 悄悄修改 requestedForecastTime,也不得将临时 12:00 画面标成 13:30 的插值天气。
  • 不能把跨 dataset/reference/Profile 的帧混合成一个时间序列;沿用已有权威身份契约。不凭固定时间间隔猜测有效帧,也不能用任意远离的旧时间片推断新的天气。若当前元数据不足以明确区分“同一批次的合法双端点”与“跨批次伪兼容”,作为契约阻塞项报告,不能自行新造隐含协议。
  • 明确 fieldKey 之间可有不同 validTime 覆盖。颜色与粒子在同一全局请求时刻分别 resolve,各自可处于单帧、双帧或 unavailable;不得因一个 fieldKey 无数据清空其他 key 的合法投影。

3.4 两个现有 RN API 的行为不变

正常时间轴操作为:

  1. RN 从本地缓存或服务器取得对应 QBIN,先 await FieldRuntime.submitEncodedField(...)。
  2. 仅在 ACCEPTED_INSERTED、ACCEPTED_REPLACED_REVISION 或可确认的 DUPLICATE 等成功结果后,await FieldRuntime.setEncodedForecastTime(reactTag, targetTimeMs)。
  3. Native 的 submit 不擅自修改目标时间,setTime 不触发网络下载。已缓存时间直接 setTime 即可;无需每次重新 submit。
  4. 两调用非原子,可能在两次调用之间短暂显示旧时间、单帧或暂时不可用,不可对外保证无闪烁。不得把旧时刻天气伪标为新目标。RN 侧 latest-wins、目标版本和展示状态属于后续 Host 接入范围;本任务不私改 NavSea。
  5. 不新增 submitAndSetTime、Native→RN 数据请求事件、逐帧删除 RN API 或新的 provider 参数;保留现有 clearEncodedField() / clearAllEncodedFields() 作为显式重置能力,而不是正常滚动手段。

3.5 Renderer 与资源安全

  • 正常向前/向后补帧且当前已选 pair 和 mix 未变化时,避免无条件 rebuild、重复 GPU 上传、清空 trail 或发布无意义 Source revision;复用 existing sameProjection/GPU content identity。
  • 单帧→双帧、双帧窗口变化和 revision 更新必须正确刷新,shader 无非法 T1/null;若相同 Snapshot 对应 T0/T1,避免重复上传同一内容。支持 GL/Metal 共享语义,不能只修 Android Renderer。
  • 淘汰旧帧不能销毁仍被当前 projection/GPU 更新流程持有的不可变 Snapshot;通过正常引用计数和资源所有权安全释放。切换不同窗口时遵守现有 trail/particle reset 规则,不错误承诺跨任意远跳跃保留轨迹。
  • 坏帧不允许覆盖已验证的好投影;生命周期关闭、clear、style reload、map 销毁与异步提交有明确所有权,不使旧请求重新点亮已 OFF 的画面。

3.6 内存边界

  • 8 帧是每 fieldKey 的数量上限,不是无限制内存保证。每个 decoded frame 由原始 Plane 字节、validity bitset 和元数据构成;参与显示的端点另有 Float32 物化数组、图像、纹理和粒子/trail 资源。不得错误地用压缩 QBIN 大小表示运行内存。
  • 不在本任务未经批准添加任意固定全局 MiB 上限或第二套容量公开错误码。为风/浪/雨/潮各 8 帧、单端点/双端点和跳跃替换提供 CPU/GPU 常驻及峰值测量入口或现有计数;记录可计算的 decoded byte 占用、资源复用/释放及最坏边界风险。
  • 评估单帧 T0=T1 情况下重复物化/重复上传;保证淘汰与资源 release 后无持续增长。若测量显示 32 帧和显示资源会触及移动端内存风险,报告下一步“全局字节预算/多 fieldKey 优先级”设计建议,不可把内存申请失败伪装成正常 REJECTED_CAPACITY。

4. 实施顺序与测试矩阵

阶段 A:只读复核与契约闸门 → 阶段 B:Core Store 滚动淘汰及单帧 resolver → 阶段 C:Binding、物化、Renderer 单/双帧和资源复用 → 阶段 D:已有 RN Bridge 端到端语义检查 → 阶段 E:文件、报告、Git 收尾。前一阶段未明确通过不进入后一阶段;命令 timeout/进程未知不能算通过。发生公共 ABI、QBIN、profile、dataset 身份或无法证实跨平台一致性的阻塞时停止扩大范围。

至少具备以下定向验证;优先扩展既有 C++ 源级测试和可复用 Test Host 场景,不新建无关测试框架:

编号 场景 必须得到的结果
T01 只有一帧,target=validTime 投影合法;色场可显示;有 U/V 时粒子可动;实际时刻正确
T02 首帧后补入第二帧,target 在两者中间 单帧→双帧,MIX 正确;不用伪造第二帧
T03 8 帧未满乱序提交 按 validTime 升序;各 key 相互隔离
T04 满 8 帧,向未来提交新帧 淘汰最早端、保留新帧,计数仍 8,无正常容量拒绝
T05 满 8 帧,向过去提交新帧 淘汰最晚端、保留新帧,计数仍 8
T06 满 8 帧,提交中间时刻 确定性端点淘汰;排序/唯一性不变
T07 今天→后天单帧→后续补帧→返回今天 新时刻单帧可用,后续升级双帧;旧数据需 RN 重交时可正常滚动,不越界、不混批
T08 Duplicate、同时间合法新 revision、stale、identity conflict 保留现有判定;重复/替换不淘汰其他时刻
T09 错 QBIN、Profile/Grid/Plane 不兼容、无效向量 拒绝且 Store、forecast clock、原投影不被破坏
T10 目标时间位于已缓存相邻两帧之间 正常插值,mix 首/中/末精确
T11 目标不被合法帧覆盖也不精确匹配 unavailable,不把最近帧冒充目标时间
T12 新帧在当前 pair 外围、当前投影不变 requestedTime、当前 pair/mix 保持;避免无意义资源重建
T13 新帧改变当前 bracket,或当前端点被淘汰 正确更新;不沿用失效旧 GPU 数据、错误轨迹
T14 wind 满额后提交 wave;同 key Color+Particle 独立 8 帧;其他 key 不受影响;同 key 不复制 Store
T15 单/双端点 Materialization/GL/Metal 资源 单帧无需两份独立 QBIN/CPU/GPU 内容;复用、release 与上下文丢失恢复正确
T16 连续 100+ 次双向滚动、反复 clear、切 Runtime 帧上限不破、无持续内存增长和悬挂引用
T17 RN 两步调用成功与失败分支 既有 API/TypeScript/Android/iOS bridge 类型兼容,失败不继续 setTime;不新增公开 API

注意:Native 静态/定向测试只能证明对应源码路径,不等于手机 GPU 首帧或 NavSea UI 通过。对尚未执行的 Android/iOS 真机、滚动拖动、跨天回退、内存峰值必须标 NOT RUN / WAITING_USER_VALIDATION。

5. 修改范围与明确禁止项

允许根据当前真实源码在上述 Shared Core/Source/Renderer 及对应现有测试文件做必要的最小修改;仅在证明是既有 API 传递障碍时修改 Android/iOS bridge,保持方法签名和结果语义。可在 docs/30_设计/ 记录淘汰不变量与单帧/双帧选择表,在 docs/50_Step记录/ 记录实施进度。

禁止:修改 NavSea、Server、天气 provider、下载缓存、Manifest、日历 UI;重新生成 PBF、修改 Style Z-order、强行更改 QBIN/QBV3 格式;新建第二套 FieldStore/Timeline、额外 JS Normalizer/Wrapper、私有 Native 通道、自动请求 RN 数据、提交单帧伪造第二份不同时间、给四类天气合用 8 帧、让正常满额仍返回容量拒绝。不允许顺带完成 STEP-097 的未授权项,也不允许覆盖现有发布 tag/manifest 或把源码通过称为正式发布通过。

6. 构建、交付与 Git

  • 本次编译、Gradle/Pod/Xcode 构建、APK/IPA 安装、ADB/设备操作及实际 GPU 验收由用户人工执行。Codex 可做源码、git diff --check、格式/静态检查及环境许可的低成本 C++ 定向测试;不得擅自启动完整 Android/iOS 编译或设备操作,也不得把未运行项目写成 PASS。
  • 准备一份 Windows 11 可直接执行的 Android 手动验证清单,ADB 命令如需涉及当前虚拟机必须显式 adb -s 192.168.200.139 ...;iOS 只列 Mac 用户手工构建/设备验收条件。合并一轮验证 T01–T17,不为单个按钮或日志要求反复重装。
  • 必做输出:docs/30_设计/STEP-098_QBIN_V4单帧与八帧滚动缓存设计_v1.md、docs/50_Step记录/STEP-098_QBIN_V4单帧与八帧滚动缓存_v1.md、docs/报告/STEP-098_QBIN_V4单帧与八帧滚动缓存_Codex完成报告_v1.md。目录不存在先创建;同主题报告已存在则递增版本,不覆盖旧报告。全部中文。
  • 报告明确记录:当前 HEAD、获接受的 Integration 协议版本/commit、真实改动列表、API/ABI 是否变化、每项 T01–T17 执行状态、REJECTED_CAPACITY 新语义、单帧显示实际范围、CPU/GPU 内存测量与未验证项、下一步 Android/iOS/Host 责任。最多只能报告 IMPLEMENTED / WAITING_PLATFORM_RUNTIME_VALIDATION;契约闸门未过则 BLOCKED_CONTRACT。
  • 完成可实施阶段且检查通过后,只提交本次任务相关变更到 Mac 当前项目 particle/main,并 push 到公开 origin/particle/main。提交前检查未跟踪/用户既有文件,不得 git add -A 混入无关变更;先核验 origin 是否前进,冲突时停下处理而非强推。完成报告必须写最终 commit hash、commit message、push 结果和远端 branch/HEAD;push 失败不能声称完成。若被契约闸门阻塞,不提交或 push 功能代码。

7. 最终验收口径

用户能在 RN 维持两个现有 API 和顺序:第一帧即显示其真实有效时间;第二帧具备合法覆盖时升级插值;后续任意顺序提交,Native 每 key 自动保持最多 8 帧。向未来提交淘汰最早,向过去提交淘汰最晚;正常已满不会 REJECTED_CAPACITY;四类天气互不挤占。无效帧零副作用、时间语义不造假、Core 不掌握下载/日历。Renderer 不因正常补帧无谓清屏或持续泄漏。Android 和 iOS 分别通过用户实际设备验证后,才能由 Integration 更新兼容性及正式状态。