8.1 KiB
产物一致性(Parity)指南
触发条件:任何声称"纯重构、产物不变"的改动。
这条管线的产物是
.blend/.glb/.png——二进制、无法 code review、 肉眼看不出 5% 的几何漂移。parity 校验是这一层唯一的回归防线。
为什么不能直接比字节
三类文件全都不是位级可复现的,同一份代码跑两次就会不一样:
| 产物 | 为什么不稳定 |
|---|---|
.blend |
内嵌绝对路径;图片按哈希表顺序打包 |
.png |
EEVEE 渲染非位级可复现 |
.glb |
glTF 导出器会去重相同的 accessor,而 smart_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:26):nantaizi-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-54 的 IGNORED_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 时:
- 浮点数四舍五入到 6 位(
scene_digest.py:17-18)。Blender 会把浮点数 round-trip 过单精度,repr 的最后几位不是有意义的信号 - 不稳定字段属于
UNSTABLE_*/IGNORED_*名单,不属于摘要 (scene_digest.py:11-13)。Blender 自己加的对象自定义属性 (_RNA_UI、cycles)就是这么排除的(scene_digest.py:24-25)
否则校验就是噪音,然后就会被忽略。一个天天报红的检查等于没有检查。
什么时候必须跑
| 改动 | 要不要跑 |
|---|---|
| 挪函数、拆模块、改导入 | 必须——纯重构的定义就是产物不变 |
调整 ROAD_LAYERS / MATERIALS 的顺序 |
必须——会平移 GLB 材质索引 |
| 改材质名 | 必须——可能静默断开 Cesium 侧的四张覆盖表 |
| 改几何构建、采样、实例化逻辑 | 必须 |
| 改 stage 的 stdout 打印 | 必须——标记本身是契约 |
改 .trellis/ 下的文档 |
不用 |
| 改 README / changelog | 不用 |
| 有意改变产物(新功能、修渲染 bug) | 跑,但目的是看清差异范围,不是要全绿 |
最后一行很重要:parity 不只是"证明没变"的工具,也是"确认只变了预期的那部分"的工具。 加一种新植被,应该只看到新增对象,不该看到道路网格的顶点数也动了。
有意改变产物时怎么做
- 先 capture 一份改动前的基线
- 改
- capture 改动后
- compare,逐条读差异
- 差异要么是预期的,要么就是 bug——没有第三种
- 把结论写进
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 打印格式 | 静默破坏契约 |
| 摘要里保留不稳定字段 | 检查变噪音,最终被忽略 |
| 顺手修 D1–D3 | 改变产物或扩大 diff |