# 道路编译器独立化(parent) ## Goal 把 native road compiler 从本仓库拆成可独立维护的项目,与本项目通过**版本化文件契约**相辅相成; 同时把道路渲染与 OSM 建筑渲染分离,使二者可各自演进。 本任务是 parent:它拥有源需求、契约定义、子任务地图、跨子任务验收标准和最终集成评审。 **它自身不承担实现工作**,所有可交付物在子任务中完成。 ## 背景:为什么现在拆 - `scripts/lib/native-road.js` 已 1695 行,`compileGeometry()` 单函数编排 15+ 个 pass, 顺序依赖只由行号编码,每加一个特性就往既有函数尾部挂位置参数 (`compileLaneMarkings` 已 7 个位置参数)。 - 道路与建筑渲染纠缠在 `blender/generate_scene.py` 一个脚本内, 道路图层表 `NATIVE_ROAD_LAYERS` 硬编码在 `blender/osmassets/catalog.py:53`。 - 道路编译的迭代节奏(几何、路口、标线、V2X 语义)与建筑/植被/水体渲染完全不同, 放在一个仓库里互相牵制。 拆分的可行性前提(已核实): - `native-road.js` 只 require `fs` / `path` / 两个同族道路模块,**对宿主项目零耦合**。 - 对宿主的唯一耦合是三个 CLI 入口里的 `readAreaConfig`。 - 输出侧 `native-road/` **已经是文件契约**,`blender/generate_scene.py:801` 按图层名读取。 - stdout 标记 `NATIVE_ROAD_COMPILE_DONE` 已存在,与 `SCENE_DONE` / `CESIUM_EXPORT_DONE` 同一约定 —— 子进程边界事实上已预留。 ## Requirements ### R1 契约先行 - R1.1 输入输出契约必须在任何代码移动之前写死并版本化为 `native-road-package/v1`, 命名对齐既有 `osm-asset-package/v1` 约定。 - R1.2 契约的权威定义见本任务 `design.md`;Phase 0 负责把它落成仓库内文档 + 校验脚本。 ### R2 可证明的等价性 - R2.1 拆分过程中每一步都必须对两个有效区域 (`fengshu-er-road` / `nantaizi-lake-innovation-valley`) 验证产物与基线一致。 - R2.2 任何差异必须逐条书面解释后才可接受,禁止"看起来差不多"。 - R2.3 验证方法沿用既有 `.trellis/spec/guides/artifact-parity-guide.md`。 ### R3 独立可维护 - R3.1 拆出的项目必须能脱离宿主自测(自带 fixture,不读宿主 `inputs/` 或 `outputs/`)。 - R3.2 保留 git 历史(`git subtree split` 或 `git filter-repo`)—— 1695 行几何逻辑的 blame 是踩坑记录,丢失后无人敢改。 - R3.3 编译器不得反向依赖宿主任何模块、配置或目录布局。 ### R4 相辅相成(防漂移) 四个机制缺一不可: | 机制 | 作用 | 落地于 | |---|---|---| | `native-road-package/v1` 版本号 | 破坏性变更必须升版本,宿主主动 opt-in | Phase 0 | | 编译器自声明图层(layer manifest) | 加图层不需要改宿主代码 | Phase 3 | | 两区域 parity 基线留在编译器仓库当测试语料 | 编译器无法静默弄坏宿主 | Phase 0 → Phase 2 | | 宿主锁版本依赖,不用 `latest` | 升级是决定,不是意外 | Phase 2 | ### R5 渲染分离 - R5.1 道路图层表必须从宿主 `catalog.py` 的硬编码变为编译器输出的 manifest。 - R5.2 `blender/generate_scene.py` 内道路渲染分支抽离为独立模块; 建筑 / 植被 / 水体保持原位。 - R5.3 分离后,编译器新增图层不需要修改宿主任何代码即可被渲染。 ## Constraints(硬约束) ### C1 不得在拆分过程中重构 IR 🔴 lane graph 提为一等 IR 是正确方向,但**必须在 Phase 2 完成之后作为独立任务**。 理由:拆分的正确性完全依赖"产物逐字节不变"这一 oracle。 一旦同时改 IR,oracle 失效,且无法判断产物变化来自搬迁还是重设计。 未来若做,形式是**新增输出**而非替换 `compiled.json`, 靠"解锁 drawtonomy / OpenDRIVE / Lanelet2 导出"赚取存在理由。 ### C2 拆分期不引入构建步骤 编译器保持 CommonJS、无构建步骤,与宿主 pipeline 层运行时一致 (见 `.trellis/spec/pipeline/index.md`)。 TypeScript / ESM 是独立决策;若后续 drawtonomy PoC 需要 SDK 互操作,只能在其独立任务的扩展目录内处理。 ### C3 Phase 1 必须先于 Phase 2 "包边界"与"换仓库"分两步做。合并执行时若产物变化,无法区分成因。 ## 子任务地图 | Phase | 子任务 | 交付物 | 阻塞后续 | |---|---|---|---| | 0 | `08-25-rc-p0-contract-baseline` | 契约文档 + 两区域 checksum 基线 + 校验脚本 | 是 | | 1 | `08-25-rc-p1-package-boundary` | 本仓库内 `packages/road-compiler/`,窄输入契约 | 是 | | 2 | `08-25-rc-p2-repo-split` | 独立仓库 + 宿主锁版本消费 | 是 | | 3 | `08-25-rc-p3-render-separation` | layer manifest + Blender 道路模块抽离 | 否 | 顺序约束:**0 → 1 → 2 必须串行**。3 依赖 2 完成。 ## 跨子任务验收标准 - [ ] AC1 Phase 0 基线建立后,两区域每个输出文件的 checksum 已提交进版本控制 - [ ] AC2 Phase 1 结束时,`npm run build:area` 两区域产物对 AC1 基线逐字节一致 - [ ] AC3 Phase 2 结束时,宿主从打包依赖构建,两区域 parity 仍成立 - [ ] AC4 Phase 2 结束时,编译器仓库 `npm test` 在不访问宿主仓库的情况下通过 - [ ] AC5 Phase 3 结束时,向编译器新增一个图层,宿主**零代码改动**即可渲染出来(实测验证) - [ ] AC6 Phase 3 结束时,`.blend` 结构摘要对基线一致(走 artifact-parity-guide) - [ ] AC7 全程未修改 `compiled.json` 的结构(C1 未被违反) - [ ] AC8 编译器仓库对宿主的反向依赖数为 0(grep 验证) ## 已识别风险(跨子任务,逐个必须有归属) | # | 风险 | 归属 Phase | 处置 | |---|---|---|---| | K1 | `lib/traffic-signals.js` 是真共用模块:`reimport-gpkg.js` / `build-osm2streets-qgis.js` / `build-area.js` / `test-preview-assets.js` 都在用,而 `native-traffic-signals.js` 也依赖它 | 1 | 需细读后拆分:信号文档 schema + OSM 信号节点提取随编译器走(编译器生成它就该拥有契约),legacy QGIS/预览读取器留宿主。**全案唯一需要细读再动的地方** | | K2 | `comparison.json` 依赖宿主产物 —— `compile-native-roads.js:87` 读 `area.outputs.geojsonDir` 里 osm2streets 输出做对比,拆出后摸不到 | 0(决策)/ 1(执行) | osm2streets 已 legacy,倾向直接砍掉,用高德参考 + 规范校验取代。决策需在 Phase 0 定档 | | K3 | config 内绝对路径指向宿主仓库:`config/areas/fengshu-er-road.json` 的 `referenceFile: "/Users/que01/osm2streets-qgis-workflow/inputs/osm/珠山湖大道(枫树二路)口.geojson"` | 1 | 需定参考文件解析约定(相对 config 目录 / 显式 basePath) | | K4 | workbench 的 OpenLayers 来自宿主 `node_modules`(design.md 的 import map 方案) | 2 | 新仓库自带依赖 | | K5 | 测试 fixture 依赖宿主:`test-native-road.js:167` 读 `inputs/osm/枫树二路.osm`,其余为内联合成 OSM | 1 | 该文件(或裁剪版)作为测试数据提交进编译器仓库 | ## Out of Scope - lane graph IR 重构(见 C1,未来独立任务) - pass manager / 显式依赖声明重构(同上,属编译器内部演进,不属本次拆分) - 替换编译器为 drawtonomy 或 osm2streets —— 已评估否决: drawtonomy 开源部分只读 Lanelet2(显式车道边界),不做 OSM `highway=*` 推导, 与本编译器的核心能力方向垂直 - osm2streets / QGIS legacy 链路的任何改动 - 宿主侧建筑 / 植被 / 水体渲染逻辑的改动(Phase 3 只抽离道路部分) ## Notes - 评估结论与选型依据见 `design.md` 的「决策记录」一节。 - 时间估计:Phase 0-2 约 3 天(搬家 + 证明没搬坏),Phase 3 约 1-2 天。 拿到"道路与建筑渲染分离、可独立维护"是在 Phase 3 结束。 - 父任务验收完成后,下一项候选任务是独立的 `08-25-rc-p4-drawtonomy-ext`(drawtonomy 扩展 PoC);它不属于本任务完成条件。