Initialize Trellis project guidelines
This commit is contained in:
201
.trellis/spec/guides/artifact-parity-guide.md
Normal file
201
.trellis/spec/guides/artifact-parity-guide.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# 产物一致性(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` | 跑构建 → 采集摘要 → 快照 / 比对 |
|
||||
|
||||
```bash
|
||||
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 两份摘要。这一步确定哪些字段天然不确定,
|
||||
把它们列入忽略名单。
|
||||
|
||||
```bash
|
||||
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` 时:
|
||||
|
||||
1. **浮点数四舍五入到 6 位**(`scene_digest.py:17-18`)。Blender 会把浮点数
|
||||
round-trip 过单精度,repr 的最后几位不是有意义的信号
|
||||
2. **不稳定字段属于 `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 不只是"证明没变"的工具,也是"确认只变了预期的那部分"的工具。
|
||||
加一种新植被,应该只看到新增对象,不该看到道路网格的顶点数也动了。
|
||||
|
||||
---
|
||||
|
||||
## 有意改变产物时怎么做
|
||||
|
||||
1. 先 capture 一份改动前的基线
|
||||
2. 改
|
||||
3. capture 改动后
|
||||
4. compare,**逐条读差异**
|
||||
5. 差异要么是预期的,要么就是 bug——**没有第三种**
|
||||
6. 把结论写进 `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` | 同一批图层的颜色两侧各自手调,无一致性保证(**这是刻意的**,见[图层表](../pipeline/layer-registry.md#为什么颜色刻意不同步)) |
|
||||
| D3 | `generate_scene.py` `tuft_density_wave` | 注释仍在跟已删除的 hedge banding 作对比 |
|
||||
|
||||
**碰到它们不要顺手修**——修复会改变产物或扩大 diff,属于独立决定。
|
||||
|
||||
---
|
||||
|
||||
## 反模式
|
||||
|
||||
| 反模式 | 后果 |
|
||||
|---|---|
|
||||
| 跳过 control 实验直接开始改 | 分不清真回归和天然噪声 |
|
||||
| 因为"老是报红"往忽略名单里加字段 | 把真回归一起忽略掉 |
|
||||
| 忽略名单不写理由 | 下一个人无法判断该不该移出来 |
|
||||
| 直接 diff 文件字节 | 永远红,然后所有人都不看了 |
|
||||
| 一次改十个地方再跑校验 | 差异定位不到具体改动 |
|
||||
| 改 stage 打印格式 | 静默破坏契约 |
|
||||
| 摘要里保留不稳定字段 | 检查变噪音,最终被忽略 |
|
||||
| 顺手修 D1–D3 | 改变产物或扩大 diff |
|
||||
|
||||
---
|
||||
|
||||
## 相关
|
||||
|
||||
- [模块结构](../blender/module-structure.md):bpy 层为什么没有单元测试
|
||||
- [测试](../blender/testing.md):纯 Python 层的防线
|
||||
- [资产生成](../blender/asset-generation.md):为什么构建必须确定性
|
||||
- [图层表](../pipeline/layer-registry.md):顺序为什么是承重的
|
||||
Reference in New Issue
Block a user