23 KiB
GPR 三维体 POC(B & 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.6(RenderingVolumeOpenGL2 GPU ray cast,自带 vtkzlib),GoogleTest,nlohmann-json(sidecar),现有 src/{core,data,render,app} 分层。
Global Constraints
- dtype:雷达体走 int16(
vtkShortArray),不污染反演剖面的 double 主路径(ScalarVolume保持std::vector<double>,src/core/model/Field.hpp:8-26)。 - 量化:物理值 ↔ int16 经
scale/offset,必须贯穿传递函数采样、色阶 LUT(src/render/interact/SliceTool.cpp:37)、取值/详情反量化(见 spec B §3.5)。 - 落盘:不用
vtkHDFWriter(VTK 9.6 写不了vtkImageData,记忆vtk96-hdfwriter-no-imagedata)。用裸 int16 分块 + sidecar(json) + 逐块vtkzlib。 - 渲染接缝:上层(场景/切片)只面向
IVolumeRenderSource;B/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-scan(mmap/分块读)
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 Modify:buildVoxel 增 int16 重载 + 量化域传函
tests/io/gpr/ tests/core/ tests/data/store/ ← 对应测试
tools/gpr_poc/ ← POC 度量台(建体/加载/显存/fps 探针 + CLI)
POC 度量统一进 tools/gpr_poc(建体耗时、输出维度、落盘体积/压缩比、加载耗时、显存、切片/体绘制 fps),B/C 用同一套指标对照。
POC vs 生产:Task 1–6(地基)+ 7–8、10–11(接口/存储)是生产代码,走 TDD、有完整代码。Task 9、12–13 是可行性探针:给出明确实验、被测未知、通过/失败判据与度量,不预先杜撰我们正要验证的 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) {
// 写 tmp:samples=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/s(100 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: 分块存储 ChunkedVolumeStore(B/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 + WholeVolumeSource(B)
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; // B:1 个;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 构造 WholeVolumeSource,
currentProps().size()==1,维度==meta)。 - Step 2: 失败 → Step 3: 实现(遍历所有 brick 填进整卷 image)→ Step 4: 通过。
- Step 5: 提交 —
git commit -m "feat(render): IVolumeRenderSource + whole-volume source (B)"
Task 9: POC-B 真实数据度量(探针,含通过判据)
Files:
- Create:
tools/gpr_poc/main.cpp(CLI:gpr_poc build|renderB <明星路目录>)、tools/gpr_poc/Probe.{hpp,cpp}(计时/显存/fps) - 复用:Task 1–8 全部。
被验证的未知 / 阻塞点: ①int16 GPU ray cast 在真机正常出图;②真实建体耗时与输出体积;③整卷 5~10GB 加载耗时、显存峰值;④切片拖动 fps、体绘制 fps;⑤超显存时 vtkSmartVolumeMapper 的 LowResResample(MaxMemoryInBytes) 自动降质观感。
- Step 1:
gpr_poc build 明星路→ 跑 Task 1–6,输出:建体耗时、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存 meta);std::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: OutOfCoreSource(C)— 最高风险探针:核外体绘制
Files:
- Create:
src/render/source/OutOfCoreSource.{hpp,cpp}(实现IVolumeRenderSource) - 复用:Task 10/11。
被验证的未知 / 阻塞点(C 的命门,必须正面撞):
- VTK 能否渲染"动态换入换出的块工作集"为体——把 BrickPager 的工作集作为多个
vtkImageData喂给vtkMultiBlockVolumeMapper(注意其"试图全量加载"语义,须只喂视野块)或多个vtkVolume叠加;验证可行性与正确性。 - 块边接缝:相邻 brick 渲染交界是否可见;试
vtkMultiBlockVolumeMapper的抖动是否压得住。 - LOD 切换:相机拉远用粗层、拉近换细层,切换是否闪烁/可接受。
- 热路径解压:拖动每帧换块时 zlib 解压是否拖垮帧率(CPU 瓶颈)。
本任务不预写完整实现——它就是要发现上面四点的真实结论。步骤是受控实验,产物是"能跑的最小版本 + 实测结论"。
- Step 1:
update(cam)内:算视野相交 brick + 选 LOD →BrickPager::requestVisible→currentProps()返回工作集 image。 - Step 2: 用
vtkMultiBlockVolumeMapper(或 N×vtkVolume)渲染工作集,量化传函复用 Task 7。先静态相机出图,确认正确。 - Step 3: 接相机移动 → 动态换块;Probe 测 residentCount/内存恒定、换块 fps。
- Step 4: 逐项记录未知 1–4 的实测结论(接缝截图、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.cpp(sliceSource():只读切面相交块拼子体供 reslice) - 复用现有
SliceTool(src/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 B:int16 路径(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. 占位符扫描:地基任务(1–8,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 只需证可行 + 撞出阻塞,不追求生产质量分页器;但若未知 1(VTK 渲染动态工作集)撞墙,是 C 的根本阻塞,须立刻反馈 spec C 并评估替代(OpenVDS / 自建 GL)。
- 真实 13G 在合理分辨率下可能装得进显存 → B 顺过、C 的核外价值要靠"细分辨率/拼全路段大体"的真实配置才能压出(Task 9/12 的网格参数留 CLI 可调,用同一份真实数据调出超显存体来考验 C)。
- 量化贯穿漏一处即色阶/读数错(Global 约束 + Task 4/7),Self-Review 已盯。