geopro/docs/superpowers/plans/2026-06-23-gpr-volume-poc.md

23 KiB
Raw Blame History

GPR 三维体 POCB & C 双方案)实现计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 用真实 13G 雷达数据为 B整卷上 GPU与 C分块+金字塔+核外)两套对等方案做 POC验证技术可行性并挖出 spec 未预见的阻塞POC 代码即生产地基与接口实现,不返工。

Architecture: 共用地基(解析/几何/结构化建体/int16 量化体/分块存储)+ IVolumeRenderSource 渲染接缝;WholeVolumeSource(B) 与 OutOfCoreSource(C) 是接口下两个永久并存实现用户运行时按数据规模切换。落盘从第一天就分块B 的裸分块格式是 C 金字塔/核外的基座。

Tech Stack: C++17, Qt6, VTK 9.6RenderingVolumeOpenGL2 GPU ray cast自带 vtkzlibGoogleTestnlohmann-jsonsidecar现有 src/{core,data,render,app} 分层。

Global Constraints

  • dtype:雷达体走 int16vtkShortArray),不污染反演剖面的 double 主路径(ScalarVolume 保持 std::vector<double>src/core/model/Field.hpp:8-26)。
  • 量化:物理值 ↔ int16 经 scale/offset必须贯穿传递函数采样、色阶 LUTsrc/render/interact/SliceTool.cpp:37)、取值/详情反量化(见 spec B §3.5)。
  • 落盘不用 vtkHDFWriterVTK 9.6 写不了 vtkImageData,记忆 vtk96-hdfwriter-no-imagedata)。用裸 int16 分块 + sidecar(json) + 逐块 vtkzlib
  • 渲染接缝:上层(场景/切片)只面向 IVolumeRenderSourceB/C 是其两个实现。
  • 结构化建体X(沿线)/Z(深度) 规则落格,仅 Y(14 通道) 向 1D 插值;对雷达用全 3D 散点 IDW现有 IdwInterpolator 无空间索引暴力,src/core/algo/IdwInterpolator.cpp:15-33)。
  • 真实数据判定POC 用 D:\Downloads\明星路450MHz/14通道/821采样/~45306道/线/20线/int16/13.6GB。POC 过 = 在该真实数据上跑通并达标,不许避重就轻。
  • 测试数据头实证.iprh 文本键值(SAMPLES/LAST TRACE/CHANNELS/TIMEWINDOW/SOIL VELOCITY/DISTANCE INTERVAL.iprb = int16[samples × traces]samples×traces×2 == 文件大小.ord = 通道横向偏移14 有效)。

文件结构(决定分解与复用)

src/io/gpr/                         ← 新增:雷达 IO共用地基
  IprHeader.{hpp,cpp}               解析 .iprh → 结构体
  IprbReader.{hpp,cpp}              读 .iprb int16 B-scanmmap/分块读)
  GprGeometry.{hpp,cpp}             .ord 通道偏移 + .gps/.cor 逐道经纬 + 深度轴
  GprSurvey.{hpp,cpp}               一个工区 = 线[]×通道[] + 几何(建体输入)
src/core/model/
  ScalarVolumeI16.hpp               int16 体 + Quant{scale,offset} (新增,与 ScalarVolume 并列)
src/core/algo/
  GprVolumeBuilder.{hpp,cpp}        结构化建体X/Z 落格 + Y 向 1D 插值 → ScalarVolumeI16
src/data/store/
  ChunkedVolumeStore.{hpp,cpp}      分块 int16 + zlib + sidecar读/写/按块取B/C 共用C 加金字塔)
src/render/source/
  IVolumeRenderSource.hpp           渲染接缝(接口)
  WholeVolumeSource.{hpp,cpp}       B读全块 → 1 个 vtkImageData
  OutOfCoreSource.{hpp,cpp}         C金字塔 + brick 分页 → 工作集
src/render/actors/
  VoxelActor.cpp                    ModifybuildVoxel 增 int16 重载 + 量化域传函
tests/io/gpr/  tests/core/  tests/data/store/   ← 对应测试
tools/gpr_poc/                      ← POC 度量台(建体/加载/显存/fps 探针 + CLI

POC 度量统一进 tools/gpr_poc(建体耗时、输出维度、落盘体积/压缩比、加载耗时、显存、切片/体绘制 fpsB/C 用同一套指标对照。

POC vs 生产Task 16地基+ 78、1011接口/存储)是生产代码,走 TDD、有完整代码。Task 9、1213 是可行性探针:给出明确实验、被测未知、通过/失败判据与度量,不预先杜撰我们正要验证的 VTK 核外内部实现——这是 POC 的本质,强行写"完整代码"等于造假。


Phase 0 — 地基(共用,生产级 TDD

Task 1: .iprh 头解析

Files:

  • Create: src/io/gpr/IprHeader.hpp, src/io/gpr/IprHeader.cpp
  • Test: tests/io/gpr/test_ipr_header.cpp

Interfaces:

  • Produces: struct IprHeader { int samples; long lastTrace; int channels; double timeWindowNs; double soilVelocity; double distanceInterval; }; + IprHeader parseIprHeader(const std::string& text);

  • Step 1: 写失败测试

#include "io/gpr/IprHeader.hpp"
#include <gtest/gtest.h>
using geopro::io::gpr::parseIprHeader;
TEST(IprHeader, ParsesKeyFieldsFromRealSample) {
  const std::string t =
    "SAMPLES: 821\nLAST TRACE: 45305\nCHANNELS: 14\n"
    "TIMEWINDOW: 160.352\nSOIL VELOCITY: 100.000000\nDISTANCE INTERVAL: 0.049084\n";
  auto h = parseIprHeader(t);
  EXPECT_EQ(h.samples, 821);
  EXPECT_EQ(h.lastTrace, 45305);
  EXPECT_EQ(h.channels, 14);
  EXPECT_DOUBLE_EQ(h.timeWindowNs, 160.352);
  EXPECT_DOUBLE_EQ(h.soilVelocity, 100.0);
  EXPECT_NEAR(h.distanceInterval, 0.049084, 1e-9);
}
  • Step 2: 运行确认失败ctest -R IprHeader,预期 编译/链接失败(未定义)。
  • Step 3: 最小实现parseIprHeader 逐行 key: value 拆分,按字段名填结构体;缺字段抛 std::runtime_error
  • Step 4: 运行确认通过ctest -R IprHeader,预期 PASS。
  • Step 5: 提交git commit -m "feat(gpr): parse .iprh header fields"

Task 2: .iprb B-scan 读取

Files:

  • Create: src/io/gpr/IprbReader.{hpp,cpp}
  • Test: tests/io/gpr/test_iprb_reader.cpp

Interfaces:

  • Consumes: IprHeader

  • Produces: struct BScan { int samples; long traces; std::vector<int16_t> data; /* [trace*samples + s] */ }; + BScan readIprb(const std::string& path, const IprHeader& h);(校验 samples*traces*2 == fileSize

  • Step 1: 写失败测试(用临时文件造 4 道×3 采样 int16

TEST(IprbReader, ReadsInt16AndValidatesSize) {
  // 写 tmpsamples=3, traces=4 → 24 bytes
  std::vector<int16_t> raw{0,1,2, 10,11,12, 20,21,22, 30,31,32};
  auto path = writeTmp(raw); // helper
  geopro::io::gpr::IprHeader h{}; h.samples=3; h.lastTrace=3; // traces=lastTrace+1=4
  auto b = geopro::io::gpr::readIprb(path, h);
  EXPECT_EQ(b.samples, 3); EXPECT_EQ(b.traces, 4);
  EXPECT_EQ(b.data[1*3 + 2], 12); // 第1道第2采样
}
  • Step 2: 运行确认失败
  • Step 3: 最小实现traces = lastTrace+1;读全文件为 int16不匹配大小抛错。
  • Step 4: 通过
  • Step 5: 提交git commit -m "feat(gpr): read .iprb int16 b-scan with size check"

Task 3: 几何(通道偏移 + 逐道经纬 + 深度轴)

Files:

  • Create: src/io/gpr/GprGeometry.{hpp,cpp}
  • Test: tests/io/gpr/test_gpr_geometry.cpp

Interfaces:

  • Produces:

    • std::vector<double> parseChannelXOffsets(const std::string& ordText);(取第 4 列==1 的有效通道横偏,明星路应得 14 个 -0.686..+0.686
    • double depthOfSample(int s, const IprHeader& h);= s * (timeWindowNs/(samples-1)) * soilVelocity*1e-9/2单位米soilVelocity 100 m/µs = 1e8 m/s
  • Step 1: 写失败测试

TEST(GprGeometry, ParsesActiveChannelOffsets) {
  const std::string ord = "0 -0.686000 -1.5 1\n1 -0.581000 -1.5 1\n14 0 -1.5 0\n";
  auto xs = geopro::io::gpr::parseChannelXOffsets(ord);
  EXPECT_EQ(xs.size(), 2u);          // 仅 2 个有效(末列=1
  EXPECT_NEAR(xs[0], -0.686, 1e-6);
}
TEST(GprGeometry, DepthOfLastSampleMatchesPhysics) {
  geopro::io::gpr::IprHeader h{}; h.samples=821; h.timeWindowNs=160.352; h.soilVelocity=1e8;
  EXPECT_NEAR(geopro::io::gpr::depthOfSample(820, h), 8.0, 0.05); // ~8m
}

注:soilVelocity 单位换算在 Task 1 读入时统一成 m/s100 m/µs = 1e8 m/s在此基础上测试。

  • Step 2: 失败
  • Step 3: 实现.ord 按空白拆列、末列=="1" 收集第 2 列;depthOfSample 按公式。
  • Step 4: 通过
  • Step 5: 提交git commit -m "feat(gpr): channel offsets + depth axis geometry"

Task 4: int16 量化体类型

Files:

  • Create: src/core/model/ScalarVolumeI16.hpp
  • Test: tests/core/test_scalar_volume_i16.cpp

Interfaces:

  • Produces:
struct Quant { double scale = 1.0; double offset = 0.0;
  int16_t toQ(double v) const;        // round((v-offset)/scale),钳到[INT16_MIN+1,INT16_MAX]
  double  toPhys(int16_t q) const; }; // q*scale+offset
class ScalarVolumeI16 { // 行优先 idx=((k*ny+j)*nx+i),与 vtkImageData 一致
  ScalarVolumeI16(int nx,int ny,int nz);
  int16_t& at(int i,int j,int k); int nx()const; ...; std::vector<int16_t>& data();
  static constexpr int16_t kBlank = INT16_MIN; }; // 空值哨兵→透明
  • Step 1: 写失败测试(量化往返 + 索引布局 + blank
TEST(ScalarVolumeI16, QuantRoundTripAndLayout) {
  geopro::core::Quant q{0.5, -10.0};
  EXPECT_EQ(q.toQ(-10.0), 0); EXPECT_NEAR(q.toPhys(q.toQ(3.0)), 3.0, 0.25);
  geopro::core::ScalarVolumeI16 v(2,2,2);
  v.at(1,0,1) = 7; EXPECT_EQ(v.data()[(1*2+0)*2+1], 7);
}
  • Step 2: 失败Step 3: 实现Step 4: 通过
  • Step 5: 提交git commit -m "feat(core): int16 scalar volume + quantization"

Task 5: 结构化建体 GprVolumeBuilder

Files:

  • Create: src/core/algo/GprVolumeBuilder.{hpp,cpp}
  • Test: tests/core/test_gpr_volume_builder.cpp

Interfaces:

  • Consumes: GprSurvey(线×通道 BScan + 几何)、GridSpec(复用 src/core/algo/IInterpolator.hpp:7-13)

  • Produces: struct BuiltI16 { ScalarVolumeI16 vol; Quant quant; std::array<double,3> origin, spacing; double vminPhys, vmaxPhys; }; BuiltI16 buildGprVolume(const GprSurvey& s, const GridSpec& spec);

  • 算法X(沿线)/Z(深度) 最近邻或线性落格(道已规则);Y 向对落在该 (x,z) 的 14 通道值做 1D 线性插值填充横向网格maxDist/无覆盖 → kBlank。量化 scale/offset 由全体 min/max 定。

  • Step 1: 写失败测试2 通道、各 1 道×2 采样的人造 survey验横向中点插值 + 维度)

TEST(GprVolumeBuilder, InterpolatesAcrossChannelsOnly) {
  auto s = makeTwoChannelSurvey(/*ch0 val=0, ch1 val=100, 横偏 0 和 1m*/);
  geopro::core::GridSpec spec{/*nx=*/3,/*ny=*/1,/*nz=*/1, 0,0,0, 0.5,1,1, 2.0, 9.9};
  auto b = geopro::core::buildGprVolume(s, spec);
  EXPECT_NEAR(b.quant.toPhys(b.vol.at(1,0,0)), 50.0, 1.0); // 横向中点≈50
}
  • Step 2: 失败Step 3: 实现(先单线程,循环结构留可并行)→ Step 4: 通过
  • Step 5: 提交git commit -m "feat(core): structured GPR volume builder (Y-only interp)"

Task 6: 分块存储 ChunkedVolumeStoreB/C 共用基座)

Files:

  • Create: src/data/store/ChunkedVolumeStore.{hpp,cpp}
  • Test: tests/data/store/test_chunked_volume_store.cpp

Interfaces:

  • Produces:
struct StoreMeta { int nx,ny,nz; int brick; // e.g. 64
  std::array<double,3> origin, spacing; Quant quant; double vminPhys,vmaxPhys; };
class ChunkedVolumeStore {
  static void write(const std::string& dir, const BuiltI16& b, int brick=64); // 分块+zlib+sidecar.json
  static StoreMeta readMeta(const std::string& dir);
  std::vector<int16_t> readBrick(int bx,int by,int bz) const; // 解压单块
  // Task 10 追加pyramid 层Task 11 用 readBrick 做工作集
};
  • 格式meta.json(StoreMeta + 分块索引/偏移/压缩长度) + data.bin(逐块 zlib 压缩流)。zlib 用 VTK 自带 vtkzlib 或直接 zlib C API。

  • Step 1: 写失败测试write→readMeta→readBrick 往返 + 压缩后小于原始)

TEST(ChunkedVolumeStore, RoundTripBrickAndCompresses) {
  auto b = makeBuilt(128,128,128, /*可压缩模式*/);
  geopro::data::ChunkedVolumeStore::write(tmpDir, b, 64);
  auto m = geopro::data::ChunkedVolumeStore::readMeta(tmpDir);
  EXPECT_EQ(m.nx, 128); EXPECT_EQ(m.brick, 64);
  geopro::data::ChunkedVolumeStore s(tmpDir);
  auto blk = s.readBrick(0,0,0);
  EXPECT_EQ(blk.size(), 64u*64*64);
  EXPECT_LT(fileSize(tmpDir+"/data.bin"), 128u*128*128*2); // 压缩生效
}
  • Step 2: 失败Step 3: 实现Step 4: 通过
  • Step 5: 提交git commit -m "feat(data): chunked int16 volume store (zlib + sidecar)"

Task 7: VoxelActor int16 重载 + 量化域传递函数

Files:

  • Modify: src/render/actors/VoxelActor.cpp(增 int16 重载,参照现 double 版 :41-79
  • Test: tests/render/test_voxel_i16_smoke.cpp(无窗渲染冒烟 / 或断言 image 标量类型与传函控制点在量化域)

Interfaces:

  • Produces: vtkSmartPointer<vtkVolume> buildVoxelI16(const ScalarVolumeI16& vol, const Quant& q, const ColorScale& cs, double ox,..,dz, vtkSmartPointer<vtkImageData>& outImage);

  • 要点vtkShortArray 填值;传函/不透明度在量化域 qmin/qmax 加点(q.toQ(vminPhys)..q.toQ(vmaxPhys)kBlank→0 不透明;vtkSmartVolumeMapper

  • Step 1: 写失败测试(构造小 int16 体,断言 outImage->GetScalarType()==VTK_SHORT 且传函在量化域采样不崩)。

  • Step 2: 失败Step 3: 实现Step 4: 通过

  • Step 5: 提交git commit -m "feat(render): int16 voxel actor with quantized transfer fn"


Phase 1 — POC-B整卷上 GPU

Task 8: IVolumeRenderSource + WholeVolumeSourceB

Files:

  • Create: src/render/source/IVolumeRenderSource.hpp, src/render/source/WholeVolumeSource.{hpp,cpp}
  • Test: tests/render/test_whole_volume_source.cpp

Interfaces:

  • Produces:
class IVolumeRenderSource { public: virtual ~IVolumeRenderSource()=default;
  virtual StoreMeta meta() const = 0;
  virtual void update(const Camera& cam) = 0;                 // B首次载全量C按相机换块
  virtual std::vector<vtkSmartPointer<vtkImageData>> currentProps() const = 0; // B1 个C工作集
  virtual vtkImageData* sliceSource() const = 0; };           // 供 SliceTool reslice
class WholeVolumeSource : public IVolumeRenderSource { // 读全块拼 1 个 int16 vtkImageData
  explicit WholeVolumeSource(const std::string& storeDir); };
  • Step 1: 写失败测试(从 Task 6 写出的 store 构造 WholeVolumeSourcecurrentProps().size()==1,维度==meta
  • Step 2: 失败Step 3: 实现(遍历所有 brick 填进整卷 imageStep 4: 通过
  • Step 5: 提交git commit -m "feat(render): IVolumeRenderSource + whole-volume source (B)"

Task 9: POC-B 真实数据度量(探针,含通过判据)

Files:

  • Create: tools/gpr_poc/main.cppCLIgpr_poc build|renderB <明星路目录>)、tools/gpr_poc/Probe.{hpp,cpp}(计时/显存/fps
  • 复用Task 18 全部。

被验证的未知 / 阻塞点: ①int16 GPU ray cast 在真机正常出图;②真实建体耗时与输出体积;③整卷 5~10GB 加载耗时、显存峰值;④切片拖动 fps、体绘制 fps⑤超显存时 vtkSmartVolumeMapperLowResResample(MaxMemoryInBytes) 自动降质观感。

  • Step 1: gpr_poc build 明星路 → 跑 Task 16输出建体耗时、nx×ny×nz、落盘体积、压缩比。记录。
  • Step 2: gpr_poc renderB → WholeVolumeSource 上 vtkSmartVolumeMapper,量化传函着色,真窗口显示。
  • Step 3: Probe 采集:加载耗时、显存峰值、切片拖动 fps脚本化相机/切片移动)、体绘制旋转 fps。
  • Step 4:MaxMemoryInBytes 低于体大小,验证 LowResResample 自动降质路径出图。
  • Step 5:docs/superpowers/plans/poc-results-B.md:指标表 + 结论。
  • B 通过判据:真实数据建体可完成且体积/耗时可接受;整卷在目标显存内出图;切片拖动 ≥ 可用帧率(目标 ≥30fps;超显存时 LowRes 兜底可用。任一硬阻塞(如 GPU 不吃 short、显存必爆且 LowRes 不可接受)→ 记为 B 的落地风险并反馈 spec。
  • Step 6: 提交git commit -m "test(gpr): POC-B real-data metrics harness + results"

Phase 2 — POC-C分块+金字塔+核外,含最小真实分页器)

Task 10: 金字塔生成ChunkedVolumeStore 增量)

Files:

  • Modify: src/data/store/ChunkedVolumeStore.{hpp,cpp}(加 LOD 层 + 每块 min/max
  • Test: tests/data/store/test_pyramid.cpp

Interfaces:

  • Produces: void buildPyramid(int levels);(逐级 2× 降采样,每层独立分块 + 每块 int16 min,max 存 metastd::vector<int16_t> readBrick(int level,int bx,int by,int bz);std::pair<int16_t,int16_t> brickRange(int level,int bx,int by,int bz);

  • Step 1: 写失败测试(建 1 层金字塔,断言 level1 维度≈半、brickRange 命中真实 min/max

  • Step 2: 失败Step 3: 实现Step 4: 通过

  • Step 5: 提交git commit -m "feat(data): volume pyramid + per-brick min/max"

Task 11: brick 分页器LRU 工作集,生产级 TDD

Files:

  • Create: src/render/source/BrickPager.{hpp,cpp}
  • Test: tests/render/test_brick_pager.cpp

Interfaces:

  • Produces:
class BrickPager { // 内存恒定:驻留 ≤ budgetBricks 个解压块
  BrickPager(const ChunkedVolumeStore& store, size_t budgetBricks);
  void requestVisible(const std::vector<BrickId>& visible, int level); // 载入缺失、LRU 淘汰
  const std::vector<int16_t>* get(BrickId id, int level) const;        // 命中返回,未命中 nullptr
  size_t residentCount() const; };
  • Step 1: 写失败测试budget=4请求 6 块 → residentCount==4最早的被淘汰命中/未命中正确)。
  • Step 2: 失败Step 3: 实现LRU + 从 store 解压载入)→ Step 4: 通过
  • Step 5: 提交git commit -m "feat(render): bounded-memory brick pager (LRU)"

Task 12: OutOfCoreSourceC— 最高风险探针:核外体绘制

Files:

  • Create: src/render/source/OutOfCoreSource.{hpp,cpp}(实现 IVolumeRenderSource
  • 复用Task 10/11。

被验证的未知 / 阻塞点C 的命门,必须正面撞):

  1. VTK 能否渲染"动态换入换出的块工作集"为体——把 BrickPager 的工作集作为多个 vtkImageData 喂给 vtkMultiBlockVolumeMapper(注意其"试图全量加载"语义,须只喂视野块)或多个 vtkVolume 叠加;验证可行性与正确性。
  2. 块边接缝:相邻 brick 渲染交界是否可见;试 vtkMultiBlockVolumeMapper 的抖动是否压得住。
  3. LOD 切换:相机拉远用粗层、拉近换细层,切换是否闪烁/可接受。
  4. 热路径解压:拖动每帧换块时 zlib 解压是否拖垮帧率CPU 瓶颈)。

本任务不预写完整实现——它就是要发现上面四点的真实结论。步骤是受控实验,产物是"能跑的最小版本 + 实测结论"。

  • Step 1: update(cam) 内:算视野相交 brick + 选 LOD → BrickPager::requestVisiblecurrentProps() 返回工作集 image。
  • Step 2:vtkMultiBlockVolumeMapper(或 N×vtkVolume)渲染工作集,量化传函复用 Task 7。先静态相机出图确认正确。
  • Step 3: 接相机移动 → 动态换块Probe 测 residentCount/内存恒定、换块 fps。
  • Step 4: 逐项记录未知 14 的实测结论接缝截图、LOD 切换录屏指标、解压占帧时间)。
  • Step 5:poc-results-C.md:四个未知的结论 + 是否构成阻塞 + 缓解手段。
  • C 通过判据:工作集体绘制能正确出图且内存恒定;接缝/闪烁/解压三项各自有可接受方案或明确缓解(不可接受则记为阻塞并反馈 spec C这正是 POC 的目的)。
  • Step 6: 提交git commit -m "test(gpr): POC-C out-of-core volume render probe + results"

Task 13: 切片核外 + B/C 切换贯通

Files:

  • Modify: src/render/source/OutOfCoreSource.cppsliceSource():只读切面相交块拼子体供 reslice
  • 复用现有 SliceToolsrc/render/interact/SliceTool.cpp)对 source 给出的 image 切片。

被验证的未知: 切片只读相交块时的内存/fps同一 IVolumeRenderSource 下 B↔C 运行时切换无缝。

  • Step 1: OutOfCoreSource::sliceSource() 按当前切面算相交 brick → 拼最小子体 image。
  • Step 2: SliceTool 对该 image reslice拖动切片测 fps、内存。
  • Step 3: POC 台加 renderC--source whole|ooc 开关,验证同一上层代码切两实现。
  • Step 4:poc-results-C.md:切片核外指标 + 切换验证。
  • Step 5: 提交git commit -m "feat(render): slice out-of-core + runtime B/C source switch"

Self-Review对照 spec 检查)

1. spec 覆盖

  • spec Bint16 路径(Task 4/7)、结构化建体(Task 5)、裸分块落盘(Task 6)、量化贯穿(Task 4/7/Global)、整卷渲染(Task 8/9)、LowResResample(Task 9) ✓
  • spec C分块(Task 6)、金字塔+min/max(Task 10)、brick 分页(Task 11)、核外体绘制(Task 12)、切片核外(Task 13)、B/C 切换(Task 13) ✓
  • 共有地基:.iprb/.iprh 解析(Task 1/2)、几何/配准(Task 3)、GPR→体(Task 5) ✓
  • 缺口POC 不覆盖,明确记录)ddCode 接入数据集树/UI、后端持久化对接、异常/色阶编辑器接线——POC 阶段不做spec A §2 / B §6 列为成品阶段),不影响复用。

2. 占位符扫描:地基任务(18,10,11)均有完整测试代码与实现描述POC 探针(9,12,13)按设计有意不写杜撰实现,代之以受控实验 + 通过判据(已在任务内注明本质)。无 "TODO/TBD/适当处理" 类空话。

3. 类型一致性ScalarVolumeI16/Quant/BuiltI16/StoreMeta/IVolumeRenderSource/BrickPager 在定义任务与消费任务间签名一致;buildGprVolume/buildVoxelI16/ChunkedVolumeStore::{write,readMeta,readBrick}/requestVisible 全程同名。


关键风险(开工即知)

  • Task 12 是月级生产工程的"最小探针"POC 只需证可行 + 撞出阻塞,不追求生产质量分页器;但若未知 1VTK 渲染动态工作集)撞墙,是 C 的根本阻塞,须立刻反馈 spec C 并评估替代OpenVDS / 自建 GL
  • 真实 13G 在合理分辨率下可能装得进显存 → B 顺过、C 的核外价值要靠"细分辨率/拼全路段大体"的真实配置才能压出Task 9/12 的网格参数留 CLI 可调,用同一份真实数据调出超显存体来考验 C
  • 量化贯穿漏一处即色阶/读数错Global 约束 + Task 4/7Self-Review 已盯。