Files
osmWorkflow/.trellis/spec/guides/artifact-parity-guide.md

8.1 KiB
Raw Blame History

产物一致性Parity指南

触发条件:任何声称"纯重构、产物不变"的改动。

这条管线的产物是 .blend / .glb / .png——二进制、无法 code review、 肉眼看不出 5% 的几何漂移。parity 校验是这一层唯一的回归防线。


为什么不能直接比字节

三类文件全都不是位级可复现的,同一份代码跑两次就会不一样:

产物 为什么不稳定
.blend 内嵌绝对路径;图片按哈希表顺序打包
.png EEVEE 渲染非位级可复现
.glb glTF 导出器会去重相同的 accessorsmart_project 的 UV 带浮点噪声——实测两次跑出 399 vs 398 个 accessor、差 720 字节,而 node/mesh/primitive/material/image 完全一致

所以比对的是结构摘要,不是字节。


三件套

工具 位置 作用
场景摘要 blender/tools/scene_digest.py 在 Blender 内打开 .blend,输出稳定 JSON对象名/顶点数/面数/材质槽/自定义属性、材质参数、场景属性
GLB 摘要 scripts/glb-digest.js 纯 Node 读 GLB 的 JSON chunk输出 node/mesh/material 清单与 PBR 参数,附 buffer 字节长度
驱动 scripts/parity.js 跑构建 → 采集摘要 → 快照 / 比对
node scripts/parity.js capture <label> [--areas a,b] [--stages blender,cesium]
node scripts/parity.js compare <labelA> <labelB>

基线落在 outputs/_refactor-baseline/<label>/,在 .gitignore 里—— 本地草稿,不是产物,不入库parity.js:16-17)。

默认样本区域两个(parity.js:26nantaizi-lake-innovation-valley(主, OSM + osm2streets GeoJSON 齐全)、hanyang-block(次)。


必须先做 control 实验

这是最容易被跳过、跳过之后整个校验就是假的一步。

未改动的代码连跑两次diff 两份摘要。这一步确定哪些字段天然不确定, 把它们列入忽略名单。

node scripts/parity.js capture control-1
node scripts/parity.js capture control-2
node scripts/parity.js compare control-1 control-2   # 必须全绿

没做这步就开始改代码,你会分不清一个差异是"重构引入的 bug"还是"本来就每次都不一样"。

已完成的 control 结论(docs/refactor-plan.md

  • .blend 结构摘要两次完全一致 ← 这是主校验信号,可信
  • .blend 文件 sha256 不一致
  • 渲染 PNG sha256 不一致
  • GLB 结构node / mesh / primitive / material / image两次完全一致 但 accessor 数 399 vs 398、buffer 差 720 字节

忽略名单:必须附理由

parity.js:25-54IGNORED_PATHS,每一条上面都写着为什么被忽略:

files.blend.sha256 / files.glb.{sha256,bytes} / files.render.{sha256,bytes}
glbDigest.fileBytes / glbDigest.buffers / glbDigest.counts.accessors
capturedAt / durationMs / label

这些字段仍然被记录——人读快照时想看到它们——只是不参与比对 parity.js:25-27)。

往忽略名单里加东西是有代价的动作。 加之前先确认这个字段是真的每次都变(用 control 实验证明), 而不是你的改动让它变了。注释里必须写清楚证据。


真正的契约

忽略名单之外剩下的就是契约,改动它们 = 改动产物

契约项 谁产生
SCENE_DONE stdout 标记及其 JSON 内容 generate_scene.py
CESIUM_EXPORT_DONE stdout 标记及其 JSON 内容 export_cesium.py
.blend 全量结构摘要(对象、网格、材质、自定义属性) scene_digest.py
GLB 的 node / mesh / material / image 结构 glb-digest.js
<area>.json 放置元数据 export_cesium.py

改动 stage 的打印格式会静默破坏 parity 契约——parity.js:118-121 解析这两个标记。


摘要工具本身的两条约定

scene_digest.py / glb-digest.js 时:

  1. 浮点数四舍五入到 6 位scene_digest.py:17-18。Blender 会把浮点数 round-trip 过单精度repr 的最后几位不是有意义的信号
  2. 不稳定字段属于 UNSTABLE_* / IGNORED_* 名单,不属于摘要 scene_digest.py:11-13。Blender 自己加的对象自定义属性 _RNA_UIcycles)就是这么排除的(scene_digest.py:24-25

否则校验就是噪音,然后就会被忽略。一个天天报红的检查等于没有检查。


什么时候必须跑

改动 要不要跑
挪函数、拆模块、改导入 必须——纯重构的定义就是产物不变
调整 ROAD_LAYERS / MATERIALS顺序 必须——会平移 GLB 材质索引
改材质名 必须——可能静默断开 Cesium 侧的四张覆盖表
改几何构建、采样、实例化逻辑 必须
改 stage 的 stdout 打印 必须——标记本身是契约
.trellis/ 下的文档 不用
改 README / changelog 不用
有意改变产物(新功能、修渲染 bug 跑,但目的是看清差异范围,不是要全绿

最后一行很重要parity 不只是"证明没变"的工具,也是"确认只变了预期的那部分"的工具。 加一种新植被,应该只看到新增对象,不该看到道路网格的顶点数也动了。


有意改变产物时怎么做

  1. 先 capture 一份改动前的基线
  2. capture 改动后
  3. compare逐条读差异
  4. 差异要么是预期的,要么就是 bug——没有第三种
  5. 把结论写进 docs/changelog.md

风险高的改动要分次提交

docs/refactor-plan.md 对风险最高的一期写着:

逐要素分次提交,每次单独跑 parity。

一次改十个要素然后发现摘要有差异,你不知道是哪个引起的。一次一个,每次跑校验。


当前重构进度(docs/refactor-plan.md

那份计划是临时工作文档P3 收尾后会并入 changelog 并删除。当前状态:

内容 状态
P0 抽纯函数到 osmassets/{osm,geom}.py 已完成
P1 catalog.py 单一定义源 + check_layers 已完成
P2 要素注册表 ⚠️ 部分——water/grass/scrub/tree.py 已拆出,但没有 features/ 注册表building / fountain / roads 仍在 generate_scene.py
P3 材质契约化(自定义属性传递 spec 未做——export_cesium.py 仍不 import catalog,靠四张材质名表;catalog.CESIUM_EXPORT 是死代码

已知缺陷(记录在案,本轮不修)

# 位置 现象
D1 export_cesium.py:74,82,91,100 "Office White Metal Facade" 四张表里都有,但 catalog 里已无此材质——死条目
D2 scene-layers.js vs catalog.py 同一批图层的颜色两侧各自手调,无一致性保证(这是刻意的,见图层表
D3 generate_scene.py tuft_density_wave 注释仍在跟已删除的 hedge banding 作对比

碰到它们不要顺手修——修复会改变产物或扩大 diff属于独立决定。


反模式

反模式 后果
跳过 control 实验直接开始改 分不清真回归和天然噪声
因为"老是报红"往忽略名单里加字段 把真回归一起忽略掉
忽略名单不写理由 下一个人无法判断该不该移出来
直接 diff 文件字节 永远红,然后所有人都不看了
一次改十个地方再跑校验 差异定位不到具体改动
改 stage 打印格式 静默破坏契约
摘要里保留不稳定字段 检查变噪音,最终被忽略
顺手修 D1D3 改变产物或扩大 diff

相关