chore(task): archive 00-bootstrap-guidelines

This commit is contained in:
2026-08-03 10:56:56 +08:00
parent 4c5981c555
commit a6c79cb510
4 changed files with 3 additions and 3 deletions

View File

@@ -0,0 +1,196 @@
# 技术设计Trellis spec 重建
> 对应 `prd.md`。本文件定义目录结构、每个文件的内容边界与取材来源。
---
## 1. 为什么按这四个包切分
Trellis 单仓模式下,`.trellis/spec/` 的每个子目录自动成为一个 "spec layer"
`packages_context.py:30-41``guides` 被显式排除)。零配置,不需要动
`config.yaml``packages:`(那是 monorepo 用的)。
切分依据是**运行时边界**,不是目录名:
| 包 | 覆盖代码 | 运行时 | 切分理由 |
|---|---|---|---|
| `pipeline/` | `scripts/*.js`, `scripts/lib/scene-layers.js` | Node宿主机 | 唯一能调外部进程QGIS/Blender/ogr2ogr的层 |
| `blender/` | `blender/**/*.py` | Blender 内嵌 Python + 系统 Python | 依赖 `bpy`,且内部还有一条纯 Python 子边界 |
| `preview/` | `scripts/lib/cesium-preview.{js,css}` | 浏览器 | 无构建步骤的 IIFE唯一的 DOM 环境 |
| `config/` | `config/areas/*.json`, `config/examples/template.json` | 数据(无运行时) | 三层共同消费的契约,改一个字段会同时影响三层 |
`scripts/normalize-lane-arrows.py` 虽是 Python但跑在 QGIS 的 Python 里、由
`build-osm2streets-qgis.js` 调起,属于管线的外部工具调用,归 `pipeline/`
## 2. 目标结构
```
.trellis/spec/
├── index.md # 顶层索引:四个包 + guides 的路由表
├── pipeline/
│ ├── index.md # 包索引 + 五个 stage 的数据流全景
│ ├── cli-and-stages.md
│ ├── layer-registry.md
│ └── external-tools.md
├── blender/
│ ├── index.md # 包索引 + osmassets 依赖分层图
│ ├── module-structure.md
│ ├── asset-generation.md
│ └── testing.md
├── preview/
│ └── index.md
├── config/
│ └── index.md
└── guides/
├── index.md # 更新:触发点换成本项目的
├── code-reuse-thinking-guide.md # 保留,补本项目实例
├── cross-layer-thinking-guide.md # 保留,补本项目实例
└── artifact-parity-guide.md # 新增
```
被删除:`.trellis/spec/frontend/` 全部 7 个文件6 个模板 + index
## 3. 每个文件的内容边界与取材
### 3.1 `pipeline/cli-and-stages.md`
| 要写什么 | 取材 |
|---|---|
| `--kebab-case``camelCase` 的参数解析约定,重复实现在三个脚本里 | `build-area.js:50`, `reimport-gpkg.js:93`, `build-osm2streets-qgis.js` |
| 五个 stage 的职责与依赖顺序 | `build-area.js:32-46` 的顶层调度 |
| `reimport` 不在 `all` 里,是恢复步骤不是构建步骤 | `build-area.js:157-160` 注释 |
| `intermediates` × `reimport` 互斥,显式抛错 | `build-area.js:19-26` |
| 配置归一化:从 `id` 推导默认输出路径,`outputs` 可覆盖 | `build-area.js:74` `normalizeAreaConfig` |
| 缺失必填项用 `requireText` 抛错而非默认值兜底 | `build-area.js:143`, `reimport-gpkg.js:125` |
| 外部命令统一走 `runCommand`,非零退出即终止 | `build-area.js:301` |
**边界**:只写"怎么加一个 stage / 怎么加一个 CLI 参数",不复述 README 里的用法示例。
### 3.2 `pipeline/layer-registry.md`
最重要的一篇。核心是"九个图层在两种语言里各存一份"这个刻意设计。
| 要写什么 | 取材 |
|---|---|
| `SCENE_LAYERS` 是 JS 侧唯一事实源,`zIndex` 兼作绘制顺序 | `scene-layers.js:3-13` 顶部注释 |
| 从前同一份表复制了四次z_index 表 / 样式 JSON / QGIS 工程 / README改一处漏一处会静默错栈 | `scene-layers.js:5-10` |
| `outline: null` = 无描边QGIS 侧转成全透明 | `scene-layers.js:13-14` |
| `mergeScene(getCollection)` 用回调取集合,让 build内存和 reimport磁盘共用同一套合并 | `scene-layers.js:107-124` |
| Python 侧 `catalog.ROAD_LAYERS` 存的是另一组事实Blender 高度 `z` + 线性色) | `catalog.py:25-47` |
| **颜色故意不同步**JS 是 QGIS sRGB 调试色Python 是 Blender 线性场景色,分别调过 | `catalog.py:11-15` |
| `check_layers()` 只校验图层**集合与顺序**,读的是产物 `osm2streets_scene_style.json` | `catalog.py:162-195` |
| 校验是 warn 不是 fail过期或缺失的输出目录不该阻断重建 | `catalog.py:168-169` |
| **加一个图层的完整清单**:改 `scene-layers.js` + `catalog.py` 两处,顺序必须一致 | 综合 |
### 3.3 `pipeline/external-tools.md`
| 要写什么 | 取材 |
|---|---|
| QGIS 可执行文件路径推导(`Contents/MacOS/{ogr2ogr,ogrinfo}`),启动前 `existsSync` 校验 | `reimport-gpkg.js:30-41` |
| GDAL 必须注入 `PROJ_LIB` / `GDAL_DATA`,否则坐标系静默出错 | `reimport-gpkg.js:132-137` |
| **不设 `COORDINATE_PRECISION`**:会触发精度裁剪,实测丢 28 个顶点 | `reimport-gpkg.js:152-157` |
| `ogr2ogr` 失败会留 0 字节文件 → 必须 staging 目录先导出+校验,全通过才拷回 | `reimport-gpkg.js:11-13, 61-91` |
| 用 `copyFileSync` 不用 `rename`:临时目录可能跨文件系统 | `reimport-gpkg.js:72-73` |
| 导出后必须验 `type === "FeatureCollection"``features` 是数组 | `reimport-gpkg.js:168-179` |
| Blender 以 `--background --factory-startup` 调起,`--` 之后才是脚本参数 | `build-area.js:237-290`, README 低层命令 |
| 空图层是警告不是错误 | `reimport-gpkg.js:85-88` |
### 3.4 `blender/module-structure.md`
| 要写什么 | 取材 |
|---|---|
| **按依赖分包不按功能**`osm.py`/`geom.py` 纯 Python其余可 import `bpy` | `osmassets/__init__.py:1-12` |
| 这条线是几何可测试的前提,拆分前只能靠渲染整片区域来验证 | `__init__.py:9-12`, `test_pure.py:5-8` |
| 各模块职责一览geom/osm/mesh/materials/catalog/tree/water/grass/scrub | 各文件 docstring |
| 要素模块统一 `assemble(...)` 签名,返回计数供调用方汇总 | `water.py:7`, `grass.py:7`, `scrub.py:6` |
| 要素模块接收裁剪边界参数,自己调 `clip_polygon`,不假设调用方已裁剪 | 三个 `assemble` 的首行 |
| `catalog` 声明"是什么"、`materials` 负责"怎么建"——这个拆分让 catalog 能被无 Blender 环境读取 | `materials.py:1-7` |
| 新增一种 OSM 要素 = 新增一个模块 + 注册一行,不改 `build()` | `docs/refactor-plan.md` 目标节 |
### 3.5 `blender/asset-generation.md`
| 要写什么 | 取材 |
|---|---|
| `MeshBatch` 是主力:批成一个 mesh datablock压低对象数和 glTF 节点数 | `mesh.py:1-9` |
| 累积几何后调一次 `finish()` | `mesh.py:6-8` |
| 树用实例化import 一次 → bake 朝向 → 每棵树只链一个轻对象复用 datablock | `tree.py:1-8` |
| 材质在本地重建而非沿用源文件因为两个源模型都不可直接用apple 57% 贴图透明,需 alpha-clip | `tree.py:12-16` |
| `MATERIALS` / `ROAD_LAYERS` 是 list**顺序决定 GLB 材质索引**,只能追加 | `catalog.py:16-18` |
| `kind` 选择构建器:`solid` / `textured``procedural` 叠加噪声 | `catalog.py:52-55` |
| Cesium 侧的调色覆盖(`cesium` 字段 + `CESIUM_EXPORT`)为什么单独存在 | `catalog.py:139-153` |
| Cesium 默认光照偏白,场景内材质普遍手工提亮过;新资产不提亮会显得发黑 | `docs/changelog.md` 2026-07-31 条目 |
### 3.6 `blender/testing.md`
| 要写什么 | 取材 |
|---|---|
| 运行方式:`python3 -m unittest discover blender/tests`,不需要 Blender | `test_pure.py:1-3` |
| **期望值必须从几何推导,不能录制当前实现**——录制型测试会把 bug 固化成规范 | `test_pure.py:9-11` |
| 每个几何函数都覆盖退化输入(空、单点、共线、重复顶点、零长线段) | `test_pure.py``test_degenerate_*` |
| 覆盖除零守卫要写明针对哪一行代码 | `test_pure.py:76-78` |
| 随机采样函数必须验 seed 可复现 | `test_pure.py:135-138` |
| 解析器的容错语义:坏节点跳过不致命,缺 bounds 直接抛 | `test_pure.py:343-356` |
| 只能测纯 Python 侧;`bpy` 侧的回归靠 parity 校验 | 指向 `guides/artifact-parity-guide.md` |
### 3.7 `preview/index.md`
| 要写什么 | 取材 |
|---|---|
| 无构建步骤IIFE + `"use strict"`,通过 `window.OSM_ASSET_PREVIEW_CONFIG` 接参 | `cesium-preview.js:1-4` |
| HTML 由 `build-area.js:697 cesiumPreviewHtml()` 生成,注入需转义(`escapeHtml` / `escapeScriptJson` | `build-area.js:759-767` |
| DOM 句柄在顶部集中获取,不散在函数里 | `cesium-preview.js:5-20` |
| status 分两类消息(场景摘要 vs 瞬时提示),摘要要保留以便瞬时提示可被替换而非摧毁 | `cesium-preview.js:21-25` |
| `Cesium.Ion.defaultAccessToken = ""`:不依赖 Ion 服务 | `cesium-preview.js:27` |
| 必须用 HTTP 服务打开,`file://` 会被浏览器拦截 | README 预览节 |
| 车辆巡航是预览层实验功能,不影响 Blender/GLB 主资产 | README 实验节 |
### 3.8 `config/index.md`
| 要写什么 | 取材 |
|---|---|
| 字段全表与默认值 | `config/examples/template.json`, `build-area.js:74` |
| 路径必须绝对 | template.json |
| `outputs` 覆盖是逃生舱,默认从 `id` 推导 | README 区域配置节 |
| `qgis.*` 各旋钮的物理含义arrowScale / arrowMergeTriangles / arrowOutlineSimplifyMeters / intersectionCornerSourceMaxDimensionMeters | README QGIS knobs 节 |
| `arrowOutlineSimplifyMeters: 0.05` 这个默认值的来历(去掉两个畸形尾顶点而不动箭头头部) | README |
| `intersectionCornerSourceMaxDimensionMeters` 为什么要过滤大多边形(会盖住可行驶路口) | README |
| 新增区域:从 `config/examples/template.json` 复制,落到 `config/areas/` | README |
| 已沉淀的两个区域配置 | `config/areas/` |
### 3.9 `guides/artifact-parity-guide.md`(新增)
| 要写什么 | 取材 |
|---|---|
| 什么时候需要跑 parity任何声称"纯重构"的改动 | `docs/refactor-plan.md` 硬约束节 |
| 三件套分工:`scene_digest.py`.blend 结构)/ `glb-digest.js`GLB 结构)/ `parity.js`(驱动+比对) | refactor-plan 工具表 |
| **必须先做 control 实验**(同代码跑两次)确定天然不稳定字段,跳过这步的 parity 校验是假的 | refactor-plan |
| 已确定的不稳定字段与原因blend sha256内嵌绝对路径+图片打包顺序、PNGEEVEE 非位级可复现、accessor 数UV 浮点噪声影响去重) | refactor-plan control 结论 |
| 忽略名单写在 `parity.js:IGNORED_PATHS` 且必须附原因 | `parity.js:28-32` |
| 真正的契约stage stdout 标记 + .blend 全量结构摘要 + GLB node/mesh/material/image + `<area>.json` | refactor-plan |
| 基线落在 gitignore 的 `outputs/_refactor-baseline/`,是本地草稿不是产物 | `parity.js:16-17` |
### 3.10 `guides/` 现有两篇的本地化
保留通用内容,把"触发点"清单换成本项目的真实场景:
- `cross-layer-thinking-guide.md`:加图层表 JS↔Python 双向同步、GeoJSON→gpkg→GeoJSON 往返、材质名跨 `generate_scene.py`/`export_cesium.py` 对接
- `code-reuse-thinking-guide.md`:加 `parseArgs` 在三个脚本重复实现、`clip_polygon` 在四个要素模块各调一次
### 3.11 `spec/index.md`(新增)
一张路由表:改哪类代码 → 先读哪个包的 index。加一句"跨语言/跨层改动先读 `guides/`"。
## 4. 风险与对策
| 风险 | 对策 |
|---|---|
| 写成"应该怎么做"的理想规范,与代码实际不符 | 每条约定必须能追到 `path:line`PRD 验收标准里已定"每文件至少 2 处真实引用" |
| 把 README 的用法说明搬进 spec造成两处维护 | spec 只写"怎么改代码",用法一律指向 README |
| 删 `frontend/` 时误删 `guides/` | 只删 `.trellis/spec/frontend/` 单个目录,删后立即用 `--mode packages` 验证 |
| 行号引用随代码变动失效 | 行号只作定位提示,同时写函数名/常量名;关键处引用注释原文而非行号 |
## 5. 兼容性
- 不动 `config.yaml`:单仓模式自动扫描 `spec/` 子目录
- 不动 `.template-hashes.json`:只有 2 个条目,均与本任务文件无关,`trellis update` 不会覆盖
- 现有任务的 `implement.jsonl` / `check.jsonl`:本任务尚未生成,无需迁移