Compare commits
67 Commits
feature/na
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
| a4dfbf884a | |||
| 82dcb83663 | |||
| 7e8fcd82ed | |||
| eaa7c08f6a | |||
| 113ea54535 | |||
| 009fe34f79 | |||
| 7883a451c5 | |||
| 620127031e | |||
| 5704b51315 | |||
| 969b57106f | |||
| 7d4b3b566a | |||
| 5cde08090e | |||
| b78cb6e990 | |||
| b44c995457 | |||
| 2469cfdbff | |||
| 284d3dbedb | |||
| f542a64924 | |||
| d5c58b5b0c | |||
| 73707dabbe | |||
| 5220c6b2a2 | |||
| d7c2e13124 | |||
| e7bc7f82b1 | |||
| 25cf82e7c7 | |||
| bc845444bb | |||
| bb2a1449ac | |||
| 1c2d091ad2 | |||
| e997ece188 | |||
| b7031f145a | |||
| ee273c5cd6 | |||
| 71ba536c7c | |||
| 903b34b5ae | |||
| f1f829c2c6 | |||
| a16400ff96 | |||
| 8ecd794633 | |||
| 12aeda9a63 | |||
| 9a8dbc1a74 | |||
| 5403936ae4 | |||
| 0bc949bc24 | |||
| 6a0437ff83 | |||
| 3cad384b25 | |||
| 9e0e25aadf | |||
| e9ab1f03eb | |||
| 1154de3ba1 | |||
| c057258085 | |||
| 41d32457f2 | |||
| 5accc0a4b1 | |||
| 7204c28161 | |||
| a63a3ae0d8 | |||
| f7e71cfe9c | |||
| e41bfd13ec | |||
| 46542c5f4e | |||
| 1896884900 | |||
| 9bb97d4507 | |||
| bd38aa55d3 | |||
| b318f7b641 | |||
| d065dcdaf3 | |||
| 472a83c670 | |||
| bc045177a5 | |||
| 8409d2c6c3 | |||
| 5f59ad0ef0 | |||
| 47efb78f1e | |||
| 8bfc02e66e | |||
| 9d42181246 | |||
| fb863dafb2 | |||
| 7dc5c947a3 | |||
| 866b59acf8 | |||
| 4c4f4534c0 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ outputs/
|
||||
__pycache__/
|
||||
assets/models/speedtree/
|
||||
assets/models/lyrog/
|
||||
inputs/osm
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
## 两层配置
|
||||
|
||||
用户只写第一层,第二层是机器生成的中间产物:
|
||||
用户只写第一层;legacy QGIS 链路需要时才生成第二层中间产物:
|
||||
|
||||
```
|
||||
config/areas/<id>.json ← 你写的
|
||||
│ scripts/lib/area-config.js: normalizeAreaConfig()
|
||||
│ 补默认值 + 推导输出路径
|
||||
▼
|
||||
<areaDir>/_pipeline/osm2streets-qgis.config.json ← 生成的,不要手改
|
||||
<areaDir>/_pipeline/osm2streets-qgis.config.json ← legacy 生成的,不要手改
|
||||
│
|
||||
▼ build-osm2streets-qgis.js / reimport-gpkg.js
|
||||
```
|
||||
@@ -54,6 +54,8 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
| `turnLaneArrows` | | 见下 | 从 OSM `turn:lanes:*` 生成自定义车道箭头的发布开关 |
|
||||
| `osm2streets` | | 见下 | 透传给 osm2streets 的选项 |
|
||||
| `blender` | | 见下 | Blender 侧选项 |
|
||||
| `nativeRoad` | | 见下 | 原生道路编译选项 |
|
||||
| `v2xPreview` | | 见下 | 可选的 Cesium 预览实时 V2X 叠加设置 |
|
||||
| `compress` | | 见下 | 默认交付压缩阶段的 GLB 压缩选项 |
|
||||
| `budget` | | 见下 | 区域 GLB 性能与体量预算 |
|
||||
| `outputs` | | 从 `id` 推导 | 输出路径覆盖,逃生舱 |
|
||||
@@ -65,7 +67,7 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
|
||||
| 字段 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `intermediates` | `true` | 旧名 `qgis` 仍被接受 |
|
||||
| `intermediates` | `false` | native-only 默认;旧名 `qgis` 仍被接受,显式开启才运行 legacy |
|
||||
| `blender` | `true` | |
|
||||
| `cesium` | `true` | |
|
||||
| `compress` | `true` | 压缩 staged GLB,供随后发布使用 |
|
||||
@@ -132,7 +134,41 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
|---|---|---|
|
||||
| `treeStyle` | `"natural"` | 合法值见 `generate_scene.py` 的 `TREE_STYLES`(`natural`、`procedural`、`shapespark`) |
|
||||
| `officeOverrides` | `""` | 旧名 `office_overrides` 仍被接受 |
|
||||
| `roadProvider` | `"osm2streets"` | Blender 道路来源。`"native"` 时仅使用 `native-road/` 的道路、路口和人行道面;可由 `build:area --road-provider native` 临时覆盖。 |
|
||||
| `roadProvider` | `"native"` | Blender 道路来源。`"native"` 时仅使用 `native-road/` 的道路、路口和人行道面;`"osm2streets"` 仅用于显式 legacy/debug 构建。 |
|
||||
|
||||
### `nativeRoad`
|
||||
|
||||
| 字段 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `edgeLines` | `false` | 是否输出道路边缘线。 |
|
||||
| `junctionTemplates.enabled` | `false` | 启用显式绑定的参数化路口模板。 |
|
||||
| `junctionTemplates.references` | `[]` | 仅支持 `cross-v1`;每项必须给出 OSM `nodeId`,可带 GCJ-02 `referenceFile` 作校准与有效性检查。 |
|
||||
| `junctionTemplates.clusters` | `[]` | `cross-cluster-v1` 的相邻 OSM 节点簇。输出各外部进口的参数化渐变道路面,簇内短段与节点级路口面保留;`approachWidthMultiplier` / `approachLengthMeters` 控制渐变。可选 `referenceFile` 记录 GCJ-02 校准来源,不合并节点级道路、connector、信号或停止线语义。 |
|
||||
| `junctionTemplates.clusters[].cornerRadiusMeters` | `12`(4–25) | 仅 `complex-junction-v1`:相邻进口夹角处路缘圆角的半径。圆角切于两侧最外道路边缘,只补齐夹角处的路面,不改变 connector、信号或停止线。 |
|
||||
| `junctionTemplates.clusters[].outerRadiusExtraMeters` | `18`(18–35) | 仅 `complex-junction-v1`:路口中心到外部进口交接边界的额外半径。用于让圆角包住角部斑马线;未配置时保持原有 18 m。 |
|
||||
|
||||
`junctionTemplates.references[*].referenceFile` 与
|
||||
`junctionTemplates.clusters[*].referenceFile` 是唯一允许相对写法的路径:相对当前
|
||||
`config/areas/<id>.json` 解析。`readAreaConfig()` 会先将其规范化为绝对路径,再通过
|
||||
`toRoadCompilerInput()` 传入 road compiler;包内不得自行解析相对路径或读取配置文件。
|
||||
|
||||
### `v2xPreview`
|
||||
|
||||
这是 Cesium 验证预览的可选实时叠加层,不进入发布的 `package/`。默认值:
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": false,
|
||||
"apiBaseUrl": "/api",
|
||||
"wsBaseUrl": "/websocket",
|
||||
"crossCode": ""
|
||||
}
|
||||
```
|
||||
|
||||
`apiBaseUrl` 和 `wsBaseUrl` 应使用同源反向代理路径,不能写入私有上游主机、账号或令牌。
|
||||
V2X 接口返回的地图数据是 GCJ-02;浏览器预览在创建 Cesium entity 前一次性转为 WGS84,
|
||||
而 native package 的 WGS84/ENU 契约保持不变。详见
|
||||
[`docs/v2x-cesium-preview.md`](../../../docs/v2x-cesium-preview.md)。
|
||||
|
||||
### `compress`
|
||||
|
||||
@@ -187,7 +223,8 @@ transcode,成功后替换**package staging** 中的主 GLB 与 manifest;未
|
||||
|
||||
静态发布路径另有 `packageDir`、`packageStagingDir`、`packageManifest`、
|
||||
`packageStagingManifest`、`packageModelDir`、`packageStagingModelDir` 与
|
||||
`packagePrimaryGlb`;预览路径另有 `previewDir` 与 `previewDescriptor`。除非在迁移旧调用,
|
||||
`packagePrimaryGlb`;预览路径另有 `previewDir`、`previewDescriptor` 与
|
||||
`trafficSimulation`(native preview 的可迁移仿真描述符)。除非在迁移旧调用,
|
||||
不要覆盖 `glb` / `metadata`:它们是 staging 内部路径,不是下游资产入口。
|
||||
|
||||
**优先改 `fileStem` 或 `areaDir`**——它们能一次性影响全部派生路径。逐个覆盖容易漏。
|
||||
|
||||
@@ -414,6 +414,294 @@ GeoJSON,native Blender 构建通过 `catalog.NATIVE_ROAD_LAYERS` 消费它们
|
||||
正确:道路方向箭头进入 `direction_arrows.geojson`;只有 OSM 明确标注的动作进入
|
||||
`turn_arrows.geojson`。工作台用两个开关呈现,Blender 复用同一现有箭头材质。
|
||||
|
||||
## Native 普通路口圆角
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
`compile-native-roads.js` 为普通 T / 十字路口生成 `intersection_surface.geojson`
|
||||
和 `sidewalk_surface.geojson` 的路口边界。路口道路面必须在同一 cutback 处结束,不能
|
||||
用未裁剪的道路矩形覆盖圆角边界。
|
||||
|
||||
### 2. 几何契约
|
||||
|
||||
- 相邻道路边缘使用两条支持切线的交点作为二次曲线控制点;采样段数由
|
||||
`JUNCTION_CURVE_SEGMENTS` 统一控制。
|
||||
- 机动车路口边界、人行道内侧路缘和人行道外侧边界都必须使用同一切线圆角规则;外侧
|
||||
不能只对内侧采样点做线性偏移,避免内外曲率不一致。
|
||||
- `boundary_mode` 使用 `rounded-approach-envelope`,无法安全构造的角保持确定性直线
|
||||
回退,并写入 `junction-rounded-corner-fallback` warning。
|
||||
- 已发布的 connector 必须包含在最终边界内,边界退化或 connector 越界时才允许使用
|
||||
`connector-convex-fallback`。
|
||||
|
||||
### 3. 校验与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 支持切线交点有限且曲线不过远 | 生成采样圆角 |
|
||||
| 切线近似平行或交点退化 | 保留该角直线并记录 `junction-rounded-corner-fallback` |
|
||||
| 边界自相交或 connector 越界 | 使用 connector 凸包兜底;仍自相交则不发布路口面 |
|
||||
|
||||
### 4. 必需测试
|
||||
|
||||
- `npm run test:native-road`:普通 T / 十字路口的圆角顶点数、内收方向、内外人行道
|
||||
曲线和 continuation 语义。
|
||||
- `npm run test:road-workbench`:工作台仍能加载 native 路口及人行道图层。
|
||||
- `npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json`。
|
||||
- `npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json`。
|
||||
|
||||
### 5. 错误与正确写法
|
||||
|
||||
错误:先对路缘生成圆角,再把外侧边界按每个采样点线性平移;这会导致内外曲率不同,
|
||||
在人行道角落留下不一致的折面。
|
||||
|
||||
正确:对内侧和外侧分别用相同的道路边缘支持切线规则生成曲线,仅在外侧切线退化时
|
||||
使用确定性的偏移回退。
|
||||
|
||||
## Native 道路中心虚线
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
`node scripts/compile-native-roads.js --config <area>` 为可确认的双向非 service
|
||||
道路段写入 `native-road/layers/center_lines.geojson`。这是原生几何:只能依据
|
||||
canonical OSM 中心线、native 双方向道路模型和 native 路口 cutback 生成,osm2streets
|
||||
的 `center_lines.geojson` 只可作为视觉基准,绝不能作为输入。
|
||||
|
||||
### 2. 调用形式
|
||||
|
||||
```bash
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run road:workbench -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview --road-provider native
|
||||
```
|
||||
|
||||
工作台 `GET /api/state` 通过 `layers.centerLines` 返回该 FeatureCollection;native
|
||||
Blender adapter 必须将 source `center_lines` 映射到既有 `center_lines` material layer。
|
||||
|
||||
### 3. 契约
|
||||
|
||||
- 每一 dash 均为 Polygon,长 `2m`、宽 `0.25m`、确定性间隔 `2m`,使用
|
||||
`native-road-center-line/v1` provenance。
|
||||
- 要素必须含有 `native_id`、`segment_id`、`road_id`、`directional_road_ids`、
|
||||
`osm_way_ids`、`dash_index`、`dash_length_m`、`dash_gap_m` 与 `placement_rule`,以便
|
||||
Workbench 用中文显示“道路中心虚线”并可回溯来源。
|
||||
- 只有同一 `segment_id` 恰有一条 forward 和一条 backward native road 时才生成;单向、
|
||||
`highway=service` 或退化中心线都不能伪造中心线。退化中心线须写
|
||||
`invalid-center-line` diagnostic。
|
||||
- 线段必须先经过 native junction cutback;再与 crosswalk、vehicle stop line 的控制面
|
||||
求冲突,冲突 dash 直接略去。控制标线优先于中心虚线。
|
||||
- `compiled.json.layers.centerLines`、`comparison.json.nativeCenterLineFeatures`、
|
||||
`build-area.js` 的 native input records 与 required-layer 验证必须全部使用
|
||||
`layers/center_lines.geojson`;缺失时在 Blender 启动前失败,不得静默漏画。
|
||||
|
||||
### 4. 校验与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 可确认的双向普通道路段 | 生成 2m / 0.25m 黄虚线,固定 2m gap |
|
||||
| 单向或 `highway=service` 道路 | 不生成中心虚线 |
|
||||
| 中心线不足两点、长度不可用 | `invalid-center-line` diagnostic,不写畸形 Polygon |
|
||||
| dash 进入 junction cutback | trim 后不生成该范围 dash |
|
||||
| dash 与斑马线或停止线相交 | 不生成冲突 dash |
|
||||
| native Blender 输入缺 `center_lines.geojson` | `ensureNativeRoadLayers()` 抛错 |
|
||||
|
||||
### 5. 正常、基础与错误示例
|
||||
|
||||
- 正常:一条有 forward/backward carriageway 的 residential 段在两个方向道路之间生成黄虚线。
|
||||
- 基础:没有双向证据的道路仍可有车道分隔线,但不产生道路中心虚线。
|
||||
- 错误:从 osm2streets 图层复制或裁剪中心线;这会将渲染器缺陷重新变成 native 数据依赖。
|
||||
|
||||
### 6. 必需测试
|
||||
|
||||
- `npm run test:native-road`:断言双向生成、provenance、2m 尺寸/间隔、单向和 service 跳过。
|
||||
- `npm run test:road-workbench`:断言 `centerLines` API、中文开关、选择溯源和概览计数。
|
||||
- `npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json`:输出合法图层,检查中心线与控制标线不重叠。
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview --road-provider native`:日志列出 `center_lines`,且不运行 `package`。
|
||||
|
||||
### 7. 错误与正确写法
|
||||
|
||||
错误:只按一条 directional road 生成中心线,或忽略控制标线。
|
||||
|
||||
```js
|
||||
const line = road.centerline;
|
||||
features.push(makeDash(line));
|
||||
```
|
||||
|
||||
正确:先确认成对的双方向道路、做路口裁剪,再排除控制标线冲突。
|
||||
|
||||
```js
|
||||
if (roads.length !== 2 || !forward || !backward || forward.highway === "service") continue;
|
||||
const line = trimLineAtJunctions(forward.centerline, forward.sourceNodeIds, junctionPlans);
|
||||
if (!ringsOverlapControl([ring], [...controls.crosswalks, ...controls.stopLines])) features.push(dash);
|
||||
```
|
||||
|
||||
## Native 道路中心线样式覆写
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
Road Workbench 选中 `native-road-center-line/v1` 要素后,可为其 `segment_id`
|
||||
保存样式覆写。覆写属于 `native-road-overrides.json`,不是对
|
||||
`center_lines.geojson` 的手工编辑;重新编译必须从覆写重建图层。
|
||||
|
||||
### 2. 调用形式
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "道路中心线:segment:way/123/1",
|
||||
"kind": "center-line-style",
|
||||
"segmentId": "segment:way/123/1",
|
||||
"color": "white",
|
||||
"pattern": "solid"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 契约
|
||||
|
||||
- `color` 只能是 `yellow` 或 `white`;`pattern` 只能是 `dashed` 或 `solid`。
|
||||
目标 `segmentId` 必须属于当前 native road model。
|
||||
- `double: true` 只表示双黄实线,因此只能与 `color: "yellow"`、
|
||||
`pattern: "solid"` 组合。编译器为每段的每个有效实线 mark 写两条相距 0.32m 的
|
||||
平行 Polygon,并记录 `double: true`、`effective_style:
|
||||
"double-yellow-solid"`;不得把它实现为可任意组合的双线开关。
|
||||
- 未覆写段保持黄色虚线(2m dash、2m gap);`solid` 为 0 gap,但相邻的 2m
|
||||
几何块须重叠 `0.04m`,避免投影精度造成可见裂缝。
|
||||
- 每个生成面记录 `color`、`pattern` 和 `effective_style`。Workbench 以这些属性
|
||||
着色;native Blender 将 white centre lines 分派至 `Native Center Line White`,
|
||||
yellow 则继续复用 `Center Line`。
|
||||
- 下拉框变化即暂存覆写,顶部“保存并重新生成”是唯一写盘/重编译动作;不要求用户
|
||||
再点击一个容易遗漏的暂存按钮。
|
||||
|
||||
### 4. 校验与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 合法颜色、图案和当前 segment | 保存后重新生成有效样式 |
|
||||
| 非法颜色/图案或不存在 segment | `validateOverrides()` 拒绝整个请求 |
|
||||
| 实线块触及控制标线 | 该块略去,不以连续性为由穿过控制标线 |
|
||||
| 白色中心线进入 native Blender | 使用白线材质,不改变 legacy 图层材质 |
|
||||
|
||||
### 5. 正常、基础与错误示例
|
||||
|
||||
- 正常:点选任意 dash,选择“白色实线”,保存重编译后整段显示连续白线。
|
||||
- 基础:选择“黄色虚线(默认)”仍是显式覆写,但几何与默认规则一致。
|
||||
- 错误:只在 Workbench 改填充色;Blender/Cesium 会继续显示旧黄色。
|
||||
|
||||
### 6. 必需测试
|
||||
|
||||
- `npm run test:native-road`:合法/非法样式覆写、solid 属性、控制标线避让。
|
||||
- `npm run test:road-workbench`:中文样式面板、下拉框自动暂存和 API payload。
|
||||
- `python3 -m unittest discover blender/tests`:catalog 仍是可导入的纯 Python。
|
||||
- Nantaizi native `blender,cesium,preview` 构建:既有 yellow centre lines 不回归。
|
||||
|
||||
### 7. 错误与正确写法
|
||||
|
||||
错误:实线块仅以零间隔精确相接,且每块使用高对比 outline。
|
||||
|
||||
正确:小幅重叠相邻块,并让 Workbench 实线 stroke 与 fill 同色。
|
||||
|
||||
## Native 道路外缘线样式覆写
|
||||
|
||||
`edge_lines.geojson` 是车行道最外侧边界标线,使用 `native-road-edge-line/v1`
|
||||
provenance。单向道路生成左右两条外缘线;双向道路每个方向只生成远离道路中心的外缘
|
||||
线,双向道路中间分界由 `center_lines.geojson` 负责。默认是白色实线,必须带
|
||||
`road_id`、`side`(`left` / `right`)、`osm_way_ids`、`color`、`pattern` 与
|
||||
`effective_style`。
|
||||
|
||||
区域配置中的 `nativeRoad.edgeLines` 默认是 `false`;因此 native Blender/Cesium 默认不
|
||||
生成或显示该层。只有明确设置为 `true` 才会发布 `edge_lines.geojson` 并交给下游消费。
|
||||
|
||||
Road Workbench 点选外缘线后以中文显示其方向侧边,可暂存以下 area-local 覆写:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "道路外缘线:road:way/123:forward:left",
|
||||
"kind": "edge-line-style",
|
||||
"roadId": "road:way/123:forward",
|
||||
"side": "left",
|
||||
"color": "white",
|
||||
"pattern": "solid"
|
||||
}
|
||||
```
|
||||
|
||||
`roadId` 必须是当前 directional native road 的精确 ID,`side` 必须为 `left` 或 `right`。
|
||||
实线使用连续 offset line;虚线使用确定性
|
||||
2m mark / 2m gap。工作台的暂存与“保存并重新生成”是唯一的写盘路径,不能只改浏览器填充色;
|
||||
native Blender 将该层映射到既有 `lane_separators` material layer。
|
||||
|
||||
## Native 控制标线
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
`node scripts/compile-native-roads.js --config <area>` 为 native road provider
|
||||
生成 `crosswalks.geojson` 和 `vehicle_stop_lines.geojson`。这是原生道路的独立
|
||||
产物,禁止读取 osm2streets 的渲染图层作为几何输入。
|
||||
|
||||
### 2. 调用形式
|
||||
|
||||
```bash
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run road:workbench -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
```
|
||||
|
||||
工作台 `GET /api/state` 通过 `layers.crosswalks` 和
|
||||
`layers.vehicleStopLines` 返回两个 FeatureCollection。
|
||||
|
||||
### 3. 契约
|
||||
|
||||
- 只有 `highway=crossing` 且 `crossing:markings` 不是 `no`、`none` 或
|
||||
`unmarked` 的 OSM 节点可以生成斑马线。每个安全匹配点生成六条 stripe,带
|
||||
`crossing_node_id`、`road_id`、`lane_id`、`osm_way_ids`、`direction`、
|
||||
`placement_method`、`junction_inset_m` 和 `native-road-crosswalk/v1` provenance。
|
||||
- 停止线还必须对应一个启用的 native arrival endpoint,且过街节点位于该进口到
|
||||
路口的安全距离内;其 provenance 是 `native-road-stop-line/v1`。不能确认进口
|
||||
时保留斑马线并写 `crossing-no-safe-stop-line` diagnostic,不得猜测一条线。停止线
|
||||
必须复用斑马线的 `junction_inset_m`,保持与斑马线的上游间距。
|
||||
- 有安全进口且存在普通路口 plan 时,斑马线中心推进到 cutback 内约 1.5 米;单次
|
||||
最大推进 4 米。`junction_inset_m` 记录实际推进量,避免远离路口的 crossing 被过度
|
||||
移动。
|
||||
- `catalog.NATIVE_ROAD_LAYERS` 将两个源层映射到现有的 `crosswalks` 和
|
||||
`vehicle_stop_lines` 材质层。不得把它们加入 legacy `SCENE_LAYERS`。
|
||||
- 控制标线优先于箭头:默认直行箭头与其相交时跳过;路口转向箭头依次尝试在距路口
|
||||
6、10、14、18、22 米处放置,均冲突时记录 `turn-arrow-control-conflict`。
|
||||
|
||||
### 4. 校验与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 标记过街没有可匹配 native lane | `crossing-no-native-lane`,不生成任何控制标线 |
|
||||
| 有横道但没有安全进口方向 | 生成斑马线,记录 `crossing-no-safe-stop-line`,不生成停止线 |
|
||||
| 箭头与任一控制标线相交 | 直行箭头跳过;转向箭头后移或记录冲突 diagnostic |
|
||||
| native Blender 输入缺任一控制图层 | `ensureNativeRoadLayers()` 在启动 Blender 前失败 |
|
||||
|
||||
### 5. 正常、基础与错误示例
|
||||
|
||||
- 正常:一个靠近路口的 marked crossing 生成 6 条斑马线和 1 条进口停止线。
|
||||
- 基础:一条孤立的 marked crossing 可以生成斑马线,但不能凭邻近道路方向臆造停止线。
|
||||
- 错误:先生成箭头再叠加停止线,导致两者重叠;控制标线是道路控制语义,必须优先。
|
||||
|
||||
### 6. 必需测试
|
||||
|
||||
- `npm run test:native-road`:断言 marked / unmarked / 无 native lane 的输出,停止线的
|
||||
provenance、斑马线与停止线共享 `junction_inset_m`,以及箭头遇控制标线时后移。
|
||||
- `npm run test:road-workbench`:断言 controls 开关、两条 API layer 和中文选中溯源。
|
||||
- `npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json`:核查两层
|
||||
feature count 及每条停止线都是 native arrival direction。
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview --road-provider native`:不运行 `package`。
|
||||
|
||||
### 7. 错误与正确写法
|
||||
|
||||
错误:以最近任意方向车道和固定正向偏移生成停止线。
|
||||
|
||||
```js
|
||||
const stopCenter = offsetByMeters(nearestLane.point, nearestLane.axis, 2.7);
|
||||
```
|
||||
|
||||
正确:先确认该方向的终点是一个已启用的路口 arrival,再在人行横道的上游生成停止线。
|
||||
|
||||
```js
|
||||
const approach = candidates.find((item) => arrivalEndpointIds.has(`endpoint:${item.road.id}:end`));
|
||||
const stopCenter = offsetByMeters(laneCenterAtCrossing, approach.placement.axis, -2.7);
|
||||
```
|
||||
|
||||
## 斑马线与停止线来源
|
||||
|
||||
### 1. 范围与触发条件
|
||||
@@ -527,6 +815,32 @@ area.outputs.trafficSignals
|
||||
- `layout.mastHeightMeters` 与 `layout.headCenterHeightMeters` 必须相等,表示横杆与灯壳的
|
||||
中心对齐;`lensVerticalOffsetsMeters` 以灯壳中心为基准,正值向上、负值向下。当前倒计时牌
|
||||
垂直偏移为 `0`,必须贴在横杆上而非悬挂。
|
||||
- 复合路口中,具有相同 `cluster_id` 的停止线按道路轴线匹配 OSM control arm;轴线的两个
|
||||
极性必须视为等价,但每条候选停止线仍只能消费一次。普通路口继续使用有向进口匹配,不能
|
||||
用复合路口的无向规则覆盖普通路口。
|
||||
- 复合路口的中心绿化带/中央分隔带不由 native 复合模板伪造;模板只生成 OSM 道路和路口
|
||||
内必要的安全岛。每条生成的复合进口车道须在停止线外侧上游约 `8m` 生成一支与车道拓扑
|
||||
一致的左转、直行或右转箭头;不得额外叠加一套固定直行箭头。
|
||||
- **控制要素避让顺序**:`buildComplexJunctionGeometry` 在 `compileLaneCenterlines` 之前生成,
|
||||
产出的 `complex-crosswalk` / `complex-corner-crosswalk` / `complex-stop-line` 必须以
|
||||
`{ crosswalks, stopLines }` 参数传入 `compileLaneCenterlines`。车道中心线、车道分隔线、
|
||||
中心虚线、导向箭头都在这一裁剪之后才落地。这里的裁剪对象包括两类车道几何:一是簇
|
||||
边界上的 OSM 车道,二是模板自造的 `cluster-approach-lane` 预览车道。裁剪必须先按到
|
||||
路口中心的距离把线定向为“外向内”,再复用 `trimLineBeforeFirstControl`:因为 OSM 车道
|
||||
由簇边缘向外指、预览车道由外圈向核心指,两种朝向都存在,直接裁会裁反。裁剪后的几何
|
||||
同时喂给车道标线源(`markingLanes[i].coordinates`),否则分隔线/箭头会画到斑马线之外
|
||||
继续侵入核心区。拓扑几何 `coordinates`(供 connector 使用)不得裁剪,否则 turn path
|
||||
会断。发布几何 vs 拓扑几何的双轨在 `compileLaneCenterlines` 内并存,别合并。
|
||||
- **复合路口人行道转角**:直步行带被 `trimLineAtComplexCluster` 裁在簇外圈,两条相邻的
|
||||
臂之间会留一个楔形无覆盖区。转角带由 `buildComplexJunctionGeometry` 与 `complex-corner-fillet`
|
||||
共用同一 `curve` / `tangents` / `edges` / `apex` 复用生成,不重新拟合曲线;宽度
|
||||
`SIDEWALK_WIDTH_METERS = 2` 必须与 `native-road.js:DEFAULT_SIDEWALK_WIDTH_METERS` 保持
|
||||
一致,法线方向按“离簇中心更远”选取(路缘相对中心是星形的)。判定两侧臂是否都带步行带
|
||||
时必须按臂的外向方向翻转 road 自身的 `sidewalkLeft/Right`,因为那是相对数字化方向的。
|
||||
端头以 `SIDEWALK_CORNER_OVERRUN_METERS = 6` 冗余越过簇边界,让转角带和直条带自然重叠,
|
||||
不追求精确缝合。自交(`ringSelfIntersects`)或任一坐标非有限时跳过并报
|
||||
`complex-junction-sidewalk-corner-fallback`;不允许硬塞坏几何。产物经 `islands` 通道
|
||||
与安全岛一同并入 `generatedComplexSidewalks`,最终落入 `sidewalk_surface.geojson`。
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
@@ -536,6 +850,9 @@ area.outputs.trafficSignals
|
||||
| 直接运行 `blender` / `preview` 但锚点不存在 | 在启动外部工具前报 `Traffic signal anchors not found` |
|
||||
| `signal_uid` 缺失/重复、非空 `display_id` 重复、字段或 Point 无效 | 重导入在替换任何输出前失败 |
|
||||
| 用户仅修改 QGIS 后运行 `reimport` | 重新生成锚点,不沿用旧坐标 |
|
||||
| 复合路口车道整体落在斑马线/停止线内 | 报 `lane-centerline-fully-inside-control`,回退为未裁剪发布几何,不写空 LineString |
|
||||
| 复合路口人行道转角自交或坐标非有限 | 报 `complex-junction-sidewalk-corner-fallback`,跳过该角,两侧步行带保持断开 |
|
||||
| 有 `referenceFile` 时同时配置 `coreRadiusMeters` | 校准值胜出,报 `complex-junction-configured-radius-ignored` 记录被忽略的配置值 |
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
@@ -1076,9 +1393,9 @@ gpkg: path.resolve(outputOverrides.gpkg || path.join(areaDir, `${fileStem}.gpkg`
|
||||
|
||||
| 阶段 | 做什么 | 读 | 写 |
|
||||
|---|---|---|---|
|
||||
| `intermediates` | OSM → osm2streets GeoJSON → GeoPackage → QGIS 工程 + 预览图 | `.osm` | `osm2streets_web_out/`、`.gpkg`、`.qgz`、`-preview.png` |
|
||||
| `intermediates` |(显式 legacy)OSM → osm2streets GeoJSON → GeoPackage → QGIS 工程 + 预览图 | `.osm` | `osm2streets_web_out/`、`.gpkg`、`.qgz`、`-preview.png` |
|
||||
| `reimport` | GeoPackage → GeoJSON(**反向**) | `.gpkg` | `osm2streets_web_out/` |
|
||||
| `blender` | OSM + GeoJSON → 场景 | `.osm`、`osm2streets_web_out/` | `.blend`、`.png` |
|
||||
| `blender` | 默认 OSM → native road compiler → Blender 场景;legacy provider 时读取 GeoJSON | `.osm`、`native-road/`(legacy 为 `osm2streets_web_out/`) | `.blend`、`.png` |
|
||||
| `cesium` | 场景 → static staging GLB + manifest | `.blend` | `_pipeline/package-staging/models/*.glb`、staged manifest;动态 GLB 到 `_preview/` |
|
||||
| `compress` | 压缩 Cesium staging 主模型 | staged `.glb`、manifest | 压缩 staged `.glb`、manifest |
|
||||
| `package` | 校验并原子发布静态资产包 | staging manifest 与 models | `package/manifest.json`、`package/models/*.glb` |
|
||||
@@ -1098,12 +1415,11 @@ gpkg: path.resolve(outputOverrides.gpkg || path.join(areaDir, `${fileStem}.gpkg`
|
||||
|
||||
未知阶段名**抛错并列出合法值**(`:181`),不静默忽略。
|
||||
|
||||
### `all` 不含 `reimport`
|
||||
### `all` 是 native-only 完整构建
|
||||
|
||||
```js
|
||||
// 'reimport' is deliberately absent from 'all': it is a recovery step for
|
||||
// hand-edited GeoPackages, never part of a full build. build-area.js:159-160
|
||||
all: ["intermediates", "blender", "cesium", "compress", "package", "preview"],
|
||||
// Legacy intermediates/reimport are deliberately absent from 'all'.
|
||||
all: ["blender", "cesium", "compress", "package", "preview"],
|
||||
```
|
||||
|
||||
`compress`、`package`、`preview` 均在 `all` 中,确保完整构建以压缩后的可复用静态 package
|
||||
@@ -1148,6 +1464,72 @@ parity 校验依赖 stage 的 stdout 标记来判断阶段是否跑到(如 `SC
|
||||
|
||||
---
|
||||
|
||||
## Native Road Parity
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
拆分 native road compiler、移动其模块,或声称 native-road JSON / GeoJSON 未变化时,
|
||||
用 `scripts/road-parity.js`。它独立于 build stage,只读现有 native-road 输出。
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
```bash
|
||||
node scripts/road-parity.js --config config/areas/<area>.json --snapshot <out.json>
|
||||
node scripts/road-parity.js --config config/areas/<area>.json --compare <baseline.json>
|
||||
```
|
||||
|
||||
`--config` 必填,且 `--snapshot` 与 `--compare` 必须二选一。
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- 快照覆盖 `outputs/<area>/native-road/` 下全部文件和同级
|
||||
`native-traffic-signals.json`。
|
||||
- 普通 JSON 递归按对象 key 排序后生成 `contentHash`。
|
||||
- GeoJSON 同时生成排序 feature 的 `contentHash` 与原序 `orderHash`;任一不同都算
|
||||
parity 失败。
|
||||
- 绝对路径归一化为 `<repo>/...` 或 `<external>/<basename>`,随机
|
||||
`native-road-*` staging 名归一化为 `<staging>`。坐标不做容差或舍入。
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 缺 `--config` 或同时/均未传 snapshot、compare | 打印 usage,退出非零 |
|
||||
| native-road 输出目录不存在 | 抛出 `Native road output not found`,退出非零 |
|
||||
| baseline 缺文件、出现额外文件或任一 hash/count/bytes 不同 | 每项打印 `ROAD_PARITY_DIFF`,退出非零 |
|
||||
| 完全一致 | 打印 `ROAD_PARITY_OK`,退出 0 |
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: 移动编译器前后,两个 hash 均一致。
|
||||
- Base: 绝对 OSM 输入路径变化但归一化后内容一致。
|
||||
- Bad: 只比较排序后的 GeoJSON 内容并忽略 `orderHash`,会掩盖输出顺序漂移。
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- `npm run test:road-parity`:相等基线、顺序 hash 变化与缺文件都应被断言。
|
||||
- `node --check scripts/road-parity.js`。
|
||||
- 对每个受支持区域运行 `--compare` 对已提交基线自比对。
|
||||
- 迁移前先连续编译两次;只有 control 实验全绿,基线才可作为 oracle。
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
Wrong:
|
||||
|
||||
```js
|
||||
// Sorting hides a changed feature order.
|
||||
assert.equal(sortedContentHash, baseline.contentHash);
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```js
|
||||
assert.equal(contentHash, baseline.contentHash);
|
||||
assert.equal(orderHash, baseline.orderHash);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 反模式
|
||||
|
||||
| 反模式 | 后果 |
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
| 加阶段、加 CLI 参数、改配置字段 | [CLI 与阶段](./cli-and-stages.md) |
|
||||
| 改区域诊断/质量门命令或共享区域配置归一化 | [CLI 与阶段](./cli-and-stages.md#区域诊断命令) 和 [质量门](./cli-and-stages.md#区域质量门命令) |
|
||||
| 改 stage manifest 写入、读取或 stale 判断 | [CLI 与阶段](./cli-and-stages.md#stage-manifest-契约) |
|
||||
| 改复合路口几何(`buildComplexJunctionGeometry`、车道控制避让、人行道转角) | [CLI 与阶段](./cli-and-stages.md#可编辑信号设施与运行时锚点的跨阶段消费) 的复合路口小节 |
|
||||
| 改预览页生成 | [../preview/](../preview/index.md) |
|
||||
| 声称"纯重构,产物不变" | [产物一致性指南](../guides/artifact-parity-guide.md) |
|
||||
| Native road compiler CLI、输入或完成标记 | [编译器契约指针](./native-road-package.md) |
|
||||
|
||||
---
|
||||
|
||||
@@ -30,7 +32,7 @@ config/areas/<id>.json
|
||||
▼ build-area.js — 阶段调度
|
||||
_pipeline/osm2streets-qgis.config.json (派生配置)
|
||||
│
|
||||
├─[intermediates]─▶ build-osm2streets-qgis.js
|
||||
├─[intermediates]─▶ build-osm2streets-qgis.js(显式 legacy/reference)
|
||||
│ osm2streets-js-node 解析 .osm
|
||||
│ → splitLayers() 拆成九个图层
|
||||
│ → normalize-lane-arrows.py(QGIS Python)
|
||||
@@ -45,8 +47,8 @@ config/areas/<id>.json
|
||||
│ → 重建 scene.geojson + scene_style.json
|
||||
│ → _pipeline/stages/reimport.manifest.json
|
||||
│
|
||||
├─[blender]───────▶ Blender + blender/generate_scene.py
|
||||
│ 读 .osm + osm2streets_web_out/
|
||||
├─[blender]───────▶ native-road ZIP importer + Blender + blender/generate_scene.py
|
||||
│ 默认读 .osm + validated ZIP cache/
|
||||
│ → <id>.blend + <id>.png
|
||||
│ → _pipeline/stages/blender.manifest.json
|
||||
│
|
||||
@@ -62,7 +64,7 @@ config/areas/<id>.json
|
||||
│ → package/manifest.json + package/models/*.glb
|
||||
│ → _pipeline/stages/package.manifest.json
|
||||
│
|
||||
└─[preview]───────▶ 生成 <id>-cesium-preview.html
|
||||
└─[preview]───────▶ 生成 <id>-cesium-preview.html(native 可无车辆路线)
|
||||
│ + 拷贝 lib/cesium-preview.{js,css}
|
||||
+ _preview/ 车辆巡航路线、模型、动态信号
|
||||
│ → _pipeline/stages/preview.manifest.json
|
||||
|
||||
62
.trellis/spec/pipeline/native-road-package.md
Normal file
62
.trellis/spec/pipeline/native-road-package.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Native Road Package Contract
|
||||
|
||||
The native-road package is maintained in the private `road-compiler` repository:
|
||||
|
||||
`https://git.app.que01.top/que01/road-compiler`
|
||||
|
||||
The host consumes a read-only ZIP exported by the private `road-compiler` repository. The host never installs or invokes the compiler. `nativeRoadPackage` is resolved relative to the area config, hashed, validated, and extracted into `_pipeline/native-road-import/<sha256>/` before native consumers start.
|
||||
|
||||
The completion marker is `NATIVE_ROAD_COMPILE_DONE <json>`. `build-area.js` validates the marker's count, JSON payload, area id, output path, process status, and signal before starting Blender.
|
||||
|
||||
## Scope / Trigger
|
||||
|
||||
This contract applies to native-road consumption from `scripts/build-area.js`, diagnostics, parity, Blender, and native preview generation.
|
||||
|
||||
## Signatures
|
||||
|
||||
```text
|
||||
road-compiler --input <RoadCompilerInput.json>
|
||||
```
|
||||
|
||||
`RoadCompilerInput` contains `areaId`, absolute `osmFile`, `outDir`, `stagingDir`, `overridesFile`, `trafficSignalsFile`, optional `comparisonDir`, and `options`.
|
||||
|
||||
## Contracts
|
||||
|
||||
The compiler writes `compiled.json`, `diagnostics.json`, `comparison.json`, signal runtime files, `manifest.json`, and GeoJSON layers under `outDir`. Since `native-road-package/v1.1`, `manifest.json` is required: it declares every GeoJSON source, whether its role is renderable (`surface` / `marking`) or semantic, and the host material slot for renderable layers. The Blender adapter rejects a missing or invalid manifest, a declared/published GeoJSON mismatch, and an unknown material slot. On success stdout contains exactly one marker whose JSON includes `areaId`, `roads`, `endpoints`, `diagnostics`, and `output`.
|
||||
|
||||
## Validation & Error Matrix
|
||||
|
||||
| Condition | Required result |
|
||||
|---|---|
|
||||
| CLI cannot start | throw the spawn error |
|
||||
| non-zero exit or signal | fail with status/signal |
|
||||
| zero completion markers | fail |
|
||||
| multiple completion markers | fail |
|
||||
| malformed marker JSON | fail |
|
||||
| marker `areaId` or `output` mismatch | fail |
|
||||
|
||||
## Good / Base / Bad Cases
|
||||
|
||||
- Good: host writes an absolute input JSON, runs the installed exact-tag CLI, validates one marker, then starts Blender.
|
||||
- Base: compiler emits deterministic files and no optional comparison source exists.
|
||||
- Bad: host imports compiler source, reads `config/areas` inside the package, or accepts a missing/duplicate marker.
|
||||
|
||||
## Tests Required
|
||||
|
||||
- `scripts/test-road-compiler-cli.js` asserts marker success, missing, duplicate, malformed, area mismatch, and output mismatch cases.
|
||||
- `scripts/road-parity.js` compares `contentHash`, `orderHash`, feature counts, and byte counts for both supported areas.
|
||||
- `npm run test:road-workbench` asserts the workbench is loaded from the installed package.
|
||||
|
||||
## Wrong vs Correct
|
||||
|
||||
Wrong:
|
||||
|
||||
```js
|
||||
require("../packages/road-compiler/src/compile/compiler");
|
||||
```
|
||||
|
||||
Correct:
|
||||
|
||||
```js
|
||||
spawnSync(process.execPath, [compilerCli(), "--input", inputFile]);
|
||||
```
|
||||
@@ -307,6 +307,105 @@ npm run build:area -- --config config/areas/<area>.json --stages cesium
|
||||
|
||||
---
|
||||
|
||||
## 实时 V2X 车辆仅数据源
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
适用于开启 `v2xPreview.enabled` 的 Cesium 运营预览。触发:修改
|
||||
`v2x-cesium-overlay.js`、preview descriptor 的 `routeName`、或实时车辆展示。
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
```text
|
||||
WS /network/ws/network/signal?authorization=<token>
|
||||
WS /network/ws/network/obuPosition?authorization=<token>
|
||||
WS /network/ws/network/targetPosition?authorization=<token>
|
||||
```
|
||||
|
||||
OBU 消息使用 `carCode|obuCode`、`lon`、`lat`、`angle`、`speed`;目标识别消息使用
|
||||
`data[deviceId][]` 内的 `id`、`longitude`、`latitude`、`type`、`subType`、`angle`、`speed`。
|
||||
两类车辆消息的根部还带 `interval`(推送间隔,`0` 视为 `500`)。
|
||||
信号灯消息使用 `lamps[]` 内的 `phaseNo`、`status`、`countDown`。
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- 两条车辆流均为 GCJ-02,必须在实体创建前恰好调用一次 `gcj02ToWgs84`。
|
||||
- 本预览的 `routeName` 必须为 `null`,且不得生成 `trafficSimulation` 描述符。
|
||||
真正的不变量是**不得有生成路线或交通仿真**,而不是「没有车模型」——
|
||||
实时 V2X 车辆需要打包的车模型才能渲染,故 `vehicleModelNames` 由
|
||||
`writeVehicleModel(area)` 正常写出(`build-area.js:writeCesiumPreview`),
|
||||
`vehicleModelName` 取其首项。早期版本靠清空模型列表来阻止仿真车辆,
|
||||
该机制已不适用,不要再用它作为约束手段。
|
||||
- 每辆实时车保留最多 24 个已转换的位置作为实际轨迹;轨迹不是推测路径。
|
||||
- **灯色码字典只有一份**,取自源看板 `HologramCross/components/utils.ts`:
|
||||
`11=灭 21=红 22=黄 23=绿 31=其他`,未知码归入「其他」,**不得**落到红色。
|
||||
overlay 归一化为 `{nodeKeys, color, countDown}` 后单向传给 `cesium-preview.js`;
|
||||
预览层不得自带第二份字典。
|
||||
- **三条 socket 必须发心跳** `{"heartBeat":"ping"}` / 30000ms,并在断线后退避重连,
|
||||
且**重连后重放订阅帧**(signal 发 `junctionId`,obu 发 bounds,target 发 `deviceId`)。
|
||||
缺心跳会被服务端断开,画面在一分钟后静默变空。
|
||||
- **相位到原生灯头靠几何绑定**:`traffic-signals.json` 无 `phaseNo`,用 link 末段停止线点与
|
||||
航向匹配原生 `stopLongitude/stopLatitude` 与行车方向;行车方向 =
|
||||
`faceHeadingDegrees + 180`(生成侧 `scripts/lib/traffic-signals.js` 定义
|
||||
`mast = travel - 90`、`face = travel + 180`)。
|
||||
一相位可点亮多条进口道,一条 link 可点亮同进口道多个灯头,两侧都取并集。
|
||||
未绑定相位须计数上报,可用 `v2xPreview.phaseSignalMap` 显式覆盖。
|
||||
- **车辆生命周期**:超过 `interval * 1.5` 未更新即隐藏(不删除),隐藏项作为同模型的可复用槽位;
|
||||
socket 断开清空车辆。OBU 数据 hook 标记为 `car_obu.glb`,但 dashboard 的最终
|
||||
`CrossCars` 模板实际渲染 `11.glb`,预览必须以 `11.glb` 为准;目标车为
|
||||
`${type}${subType}.glb`。`11/12/13/14/15/16/17/31/221/222.glb` 必须打入
|
||||
`_preview/v2x-vehicles/`。
|
||||
- **可见性是运行时契约**:有效推送创建的模型必须在路口概览下至少为 `28px`,并允许最大
|
||||
`8x` 放大;位置高度须高于静态道路表面。首次有效车辆推送应聚焦到车群附近,标签须不受
|
||||
静态 GLB 的深度遮挡。仅验证 WS 收包或 registry 条数不算通过。
|
||||
- 车辆推送体解析必须容忍非严格 JSON(源看板用 `saferEval`),失败须计数并在面板可见,
|
||||
**不得**静默丢弃;实现不得使用 `eval` / `new Function`。
|
||||
- 周流量比 `FlowTravelRatio/queryListWeek` **不属于**实时路口范围。
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 未登录、令牌失效、REST/WS 不可用 | 静态路口继续显示,车辆层为空,并显示实时数据不可用状态 |
|
||||
| 消息不是 JSON、心跳、坐标无效 | 忽略该消息,不创建车辆;非心跳的解析失败须计数 |
|
||||
| 收到有效车辆坐标 | 创建或更新真实车辆与实际轨迹 |
|
||||
| 车辆超过 `interval * 1.5` 未更新 | 隐藏该车并保留槽位,不得堆积幽灵车 |
|
||||
| 相机视野变化 | 节流后向 obu socket 发送 GCJ-02 四角 `bounds` |
|
||||
| 视野矩形不可用 | 发空帧(等于不过滤),不得跳过发送导致空场景 |
|
||||
| 相位一个都没绑定 | 面板显性提示并建议配置 `phaseSignalMap` |
|
||||
|
||||
### 5. Good / Base / Bad Cases
|
||||
|
||||
- Good:OBU 和感知目标连续推送,页面只显示对应车辆的实际行驶轨迹。
|
||||
- Base:服务无数据,页面没有车辆或线路。
|
||||
- Bad:将旧路线 JSON 或 `native-preview-traffic-simulation` 用作回退展示。
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- `npm run test:v2x-cesium-preview`:校验灯色字典(含 `2`/`3` 不再是黄/绿的回归断言)、
|
||||
宽松解析、几何相位绑定(对真实 `traffic-signals.json` 须达成精确一对一)、
|
||||
同相位多进口道并集、车辆超时与槽位复用、心跳间隔与重连重放订阅帧、bounds 报文。
|
||||
- `npm run test:preview-assets`:断言三条 WebSocket 存在,runtime 不调用
|
||||
`addVehicleCruises`、不显示 simulation 诊断,且预览层**不再自带** `lampColorName`
|
||||
或 `Number(status) === 3` 判断;断言实时车辆最小像素尺寸和诊断可见数量读数存在。
|
||||
- 对目标区域运行 `npm run build:area -- --config config/areas/<area>.json --stages preview`,
|
||||
检查 descriptor 中 `routeName` 为空、不存在 traffic-simulation 文件,
|
||||
且 `vehicleModelNames` 已写出(实时车辆渲染需要)。
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
错误:接口不可用时恢复构造路线。
|
||||
|
||||
```js
|
||||
const cruise = addVehicleCruises(viewer, routeData, signalData, start);
|
||||
```
|
||||
|
||||
正确:保持空车辆层,等待真实流。
|
||||
|
||||
```js
|
||||
const cruise = createLiveVehicleState();
|
||||
```
|
||||
|
||||
## 本地预览必须走 HTTP
|
||||
|
||||
```bash
|
||||
|
||||
@@ -31,7 +31,7 @@ classifyConnection(incomingEdge, outgoingEdge) =>
|
||||
`maneuvers`、`edgeIds`、`laneSegments` 与 `connectors`。道路区间来自匹配的 Driving lane polygon 中轴。
|
||||
- 路线拓扑以 `network.json` 的 internal road 和 intersection 为准;禁止把整个 OSM way 直接当作一条不可分割 edge。
|
||||
- connector 必须绑定同一个 internal intersection,并位于对应 `intersection_surface.geojson` 内或允许的边界容差内;越界时拒绝候选路线。
|
||||
- preview 必须将 `lane_polygons.geojson`、`network.json` 和 `intersection_surface.geojson` 作为强制输入;缺失或无效时在写产物前失败。
|
||||
- legacy osm2streets preview 必须将 `lane_polygons.geojson`、`network.json` 和 `intersection_surface.geojson` 作为强制输入;native preview 不读取这些文件,路线缺失时保留可用预览并省略车辆巡航。
|
||||
- route 经纬度由 Cesium 按 WGS84 直接放置;最终道路 GLB 必须由 WGS84 ECEF→ENU
|
||||
`Projector` 生成。禁止以固定米/度近似投影道路,否则即使 route 与 lane polygon
|
||||
完全一致,最终画面仍会随离锚点距离产生横向偏移。
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{"file": ".trellis/spec/preview/index.md", "reason": "校验改动未违反预览层约束:零依赖、无构建步骤、浏览器原生语法、IIFE。"}
|
||||
{"file": "docs/v2x-cesium-preview.md", "reason": "校验文档与实现一致:接口清单、坐标契约、配置项、回滚说明。"}
|
||||
{"file": ".trellis/tasks/08-24-v2x-realtime-cross-fidelity/prd.md", "reason": "AC1-AC10 是验收依据,逐条核对。"}
|
||||
{"file": ".trellis/tasks/08-24-v2x-realtime-cross-fidelity/design.md", "reason": "核对灯色字典、相位映射算法、心跳与订阅帧是否与源项目逐项对齐。"}
|
||||
235
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/design.md
Normal file
235
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/design.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Design — 还原 V2X 实时路口信号灯与车辆展示
|
||||
|
||||
## 1. 现状与根因
|
||||
|
||||
`scripts/lib/v2x-cesium-overlay.js`(418 行,单个 IIFE)承担了登录、REST、三个 WS、坐标转换、
|
||||
实体渲染的全部职责。REST 路径与登录契约与源项目一致,失真集中在数据处理与渲染层。
|
||||
|
||||
逐项根因(对应 PRD 的 R1–R10):
|
||||
|
||||
| # | 现象 | 根因位置 |
|
||||
| --- | --- | --- |
|
||||
| R1 | 连接几十秒后静默中断 | `openSocket()` 无心跳、无重连 |
|
||||
| R2 | 灯永远红 | `lampColor()` 判 `2`/`3`;真实码是 `21/22/23` |
|
||||
| R3 | 原生信号灯模型从不亮 | `signalByPhase` 回退 `signal.id`,与 `phaseNo` 永不相等 |
|
||||
| R4 | 同相位只亮一条进口道 | `state.linkPhases` 是 `Map<phase, entity>`,后写覆盖 |
|
||||
| R5 | 无倒计时 | `updateSignalPhases()` 丢弃 `countDown` |
|
||||
| R6 | OBU 可能一辆车都没有 | `connectObuSocket()` 的 `onOpen` 传 `null`,未发订阅帧 |
|
||||
| R7 | 幽灵车堆积 | `state.vehicles` 只增不删,未用推送 `interval` |
|
||||
| R8 | 车型错配 | 按数组下标取模型,非 `${type}${subType}` 语义 |
|
||||
| R9 | 推送可能整条被丢 | `JSON.parse` + 静默 `catch`,源项目用 `saferEval` |
|
||||
| R10 | 多余请求 | `FlowTravelRatio/queryListWeek` 不属实时路口 |
|
||||
|
||||
## 2. 边界与不变量
|
||||
|
||||
保持不变:
|
||||
- overlay 仍是**单文件零依赖 IIFE**,由 `build-area.js:561` 原样拷入产物。不新增文件,避免改动
|
||||
`area-preview.js` 的 HTML 装配与包契约。
|
||||
- 纯逻辑继续通过 `createV2xCesiumOverlay.utils` 导出,供 `scripts/test-v2x-cesium-overlay.js`
|
||||
的 `vm.runInNewContext` 沙箱直接测试(无 DOM、无 Cesium)。
|
||||
- 坐标契约不变:GCJ-02 → WGS84 只在建实体前转一次。
|
||||
- 令牌只进 `sessionStorage`,页面始终从登录门进入。
|
||||
|
||||
新增的可测试纯函数一律**不触碰 `window` / `Cesium` / `document`**,副作用留在薄薄的适配层。
|
||||
|
||||
## 3. 模块内部分层
|
||||
|
||||
文件内按四层组织,自下而上:
|
||||
|
||||
```
|
||||
L1 纯工具 gcj02ToWgs84 / joinUrl / md5 / toWebSocketUrl / getAngle / haversine
|
||||
L2 纯解析 lampColorName / parseLoosePayload / normalizeObuVehicle /
|
||||
normalizeTargetVehicles / normalizeSignalLamps
|
||||
L3 纯状态机 createVehicleRegistry / createPhaseBinding / buildPhaseSignalMap
|
||||
L4 副作用层 createSocket(心跳/重连) / 实体渲染 / UI 面板
|
||||
```
|
||||
|
||||
L1–L3 全部导出到 `.utils`,测试只打 L1–L3。
|
||||
|
||||
## 4. 关键设计决策
|
||||
|
||||
### 4.1 灯色状态字典(R2)
|
||||
|
||||
直接采用源项目 `HologramCross/components/utils.ts` 的字典,语义化返回而非直接返回颜色,
|
||||
使 overlay 与 `cesium-preview.js` 共用同一套判定:
|
||||
|
||||
```
|
||||
11 -> "off" 灭灯
|
||||
21 -> "red"
|
||||
22 -> "yellow"
|
||||
23 -> "green"
|
||||
31 -> "other"
|
||||
其他 -> "other" // 不再落到 red
|
||||
```
|
||||
|
||||
`lampColorName(status)` 放在 overlay 的 L2 并导出。`cesium-preview.js` 中重复的
|
||||
`lampColorName()` 删除,改为消费 overlay 通过 `setSignalState` 回调传入的**已归一化**灯态
|
||||
(`{nodeKeys, color, countDown}`),避免两处字典漂移。
|
||||
|
||||
> 取舍:也可以把字典抽到共享文件让两边 require,但 overlay 必须是浏览器端零依赖 IIFE,
|
||||
> `cesium-preview.js` 同样是浏览器端脚本,两者无模块系统。让数据流单向(overlay → preview)
|
||||
> 比共享常量更简单,也消除了双字典。
|
||||
|
||||
### 4.2 相位 → 原生信号灯映射(R3,本任务最难的一处)
|
||||
|
||||
`traffic-signals.json` 的信号灯项只有 `phaseGroup: 0`,**不含 V2X `phaseNo`**,两侧没有公共 ID。
|
||||
唯一可靠的连接是**几何**。
|
||||
|
||||
源项目的做法给了线索:`CrossTrafficLights3D.vue:96-104` 对每条 link 取 `geom.coordinates` 的
|
||||
最后两点,末点即停止线位置,两点连线的方位角即进口道航向。原生信号灯项恰好也有
|
||||
`stopLongitude/stopLatitude` 与 `headingDegrees`。
|
||||
|
||||
因此定义纯函数:
|
||||
|
||||
```
|
||||
buildPhaseSignalMap(links, nativeSignals, options) -> {
|
||||
byPhase: Map<phaseNo, string[]>, // nodeKey 列表
|
||||
bound: number,
|
||||
unbound: Array<{phaseNo, reason}>,
|
||||
diagnostics: Array<{linkId, matchedNodeKeys, distanceMeters, headingDeltaDegrees}>
|
||||
}
|
||||
```
|
||||
|
||||
算法:
|
||||
1. 对每条 link:解析 `geom`,取末两点,**先 GCJ-02 → WGS84**,再算 `stopPoint` 与
|
||||
`approachHeading = getAngle(prePoint, lastPoint)`。
|
||||
2. 对每个原生 signal:取 `(stopLongitude, stopLatitude)` 与**行车方向**。
|
||||
|
||||
行车方向的语义由生成侧 `scripts/lib/traffic-signals.js` 定死,不是猜的:
|
||||
- `:54` `headingDegrees = atan2(axis)`,而 `axis = roadAxis(stopLineCenter → intersectionCenter)`,
|
||||
即**停止线指向路口中心的行车方向**。
|
||||
- `:82` `mast_heading_deg = heading - 90`
|
||||
- `:83` `face_heading_deg = heading + 180`
|
||||
|
||||
运行时 `traffic-signals.json` 中 `headingDegrees === mastHeadingDegrees`(实测 7/7 相等,
|
||||
如 `150.658`),即存的是 **mast 值**;`faceHeadingDegrees` 比它小 90(`60.658`)。因此:
|
||||
|
||||
```
|
||||
travelHeading = (faceHeadingDegrees + 180) % 360 // 首选
|
||||
= (mastHeadingDegrees + 90) % 360 // face 缺失时的等价回退
|
||||
```
|
||||
|
||||
实测对全部 7 个信号自洽。
|
||||
3. 候选条件:`haversine(stopPoint, signalStop) <= maxDistanceMeters`
|
||||
且 `angleDelta(approachHeading, travelHeading) <= maxHeadingDeltaDegrees`。
|
||||
4. 一条 link **可匹配多个** signal(同一进口道的多个灯头),满足 R4 的一半。
|
||||
5. link 的 `phaseList` 中每个 `phase` → 该 link 匹配到的全部 nodeKey,**并集累加**
|
||||
(`Map<phaseNo, Set<nodeKey>>`),满足 R4 的另一半(同相位多进口道)。
|
||||
6. 默认容差:`maxDistanceMeters = 30`、`maxHeadingDeltaDegrees = 45`。
|
||||
7. 未匹配上的相位进入 `unbound`,面板显示「N 已绑定 / M 未绑定」。
|
||||
|
||||
**实现期修正**:设计初稿认为朝向语义不可判定,打算用「三字段 ± 180 的候选集,任一命中即可」。
|
||||
实测该方案在凤树二路路口产生**严重误匹配** —— 一条 link 同时命中 3 条不同 `approachId` 的进口道
|
||||
(距离 1.57 / 13.01 / 17.56 m,角差 3.8 / 7.8 / 5.4°),因为 ±180 × 三字段几乎放行任意 90° 朝向。
|
||||
改为上述单一确定式后,7 条 link 对 7 个信号达成 **7/7 精确一对一**,误匹配为 0。
|
||||
候选集方案已废弃。
|
||||
|
||||
|
||||
**配置覆盖**:`v2xPreview.phaseSignalMap = { "<phaseNo>": ["<nodeKey>", …] }` 存在时,
|
||||
该 phaseNo 直接用配置值,跳过几何匹配。这是几何匹配失败时的逃生舱,也让映射可人工固化。
|
||||
|
||||
> 取舍:不用 `approachId` / `sourceWayId` 匹配,因为那是 OSM 侧标识,V2X link 不携带;
|
||||
> 不用最近邻唯一指派(匈牙利算法),因为一进口道多灯头是正常情况,强制一对一会漏灯。
|
||||
> 容差可配是因为不同路口的停止线标注精度差异较大。
|
||||
|
||||
### 4.3 WebSocket 连接层(R1、R6)
|
||||
|
||||
新增 `createSocket({url, onOpen, onMessage, onClose, heartbeatMs, reconnect})` 副作用封装:
|
||||
|
||||
- 心跳:`setInterval(() => send('{"heartBeat":"ping"}'), 30000)`,与源项目一致。
|
||||
- 重连:指数退避 `1s, 2s, 4s, 8s`,上限 `8s`,**重连成功后重新执行 `onOpen`**,
|
||||
确保订阅帧(`junctionId` / `deviceId` / `bounds`)被重放。这是源项目 `onConnected` 的语义。
|
||||
- `close()` 主动关闭时不触发重连;`dispose()` 清心跳与退避计时器。
|
||||
- 计时器通过参数注入(`setIntervalFn` / `setTimeoutFn`),使重连与心跳逻辑可在测试中用假时钟驱动。
|
||||
|
||||
订阅帧内容严格对齐源项目:
|
||||
|
||||
| socket | 连接后首帧 | 来源 |
|
||||
| --- | --- | --- |
|
||||
| signal | `{"junctionId":"<crossCode>"}` | `CrossTrafficLights3D.vue:129` |
|
||||
| obu | `bounds \|\| ""`(dashboard 传 `null` → 空串) | `useObuCars.ts:31` |
|
||||
| target | `{"deviceId":"<ids>"}` 或 `{"deviceId":null}` | `useTargetCars.ts:50-53` |
|
||||
|
||||
OBU 的 bounds:源项目取 AMap 视野四角。本项目用 Cesium `camera.computeViewRectangle()`
|
||||
换算成同格式 `lng,lat;lng,lat;lng,lat;lng,lat`(**WGS84 → GCJ-02 反向转换**,因为服务端按 GCJ-02
|
||||
过滤),在 `camera.moveEnd` 时节流发送 `{"bounds": "<…>"}`。视野不可用时退回空串,等价于不过滤。
|
||||
|
||||
> 取舍:Cesium 是倾斜视角,`computeViewRectangle()` 在极端俯仰下可能返回 `undefined`;
|
||||
> 此时退回空串而非跳过发送,保证仍能收到全量推送——宁可多收也不要空场景。
|
||||
|
||||
### 4.4 车辆注册表(R7、R8)
|
||||
|
||||
纯状态机 `createVehicleRegistry({now})`:
|
||||
|
||||
```
|
||||
registry.ingest(vehicles, interval) -> {added, updated, hidden}
|
||||
registry.sweep(nowMs) -> hiddenKeys
|
||||
registry.list() -> VehicleRecord[]
|
||||
```
|
||||
|
||||
- 复刻源项目:`interval = Number(payload.interval) === 0 ? 500 : Number(payload.interval)`。
|
||||
- `sweep()`:`Math.abs(now - timeStamp) >= interval * 1.5` 时 `visible = false`。
|
||||
- 槽位复用:新车优先占用同模型的 `visible === false` 记录(源项目 `useTargetCars.ts:110`),
|
||||
减少 Cesium 实体的增删抖动。
|
||||
- `now` 注入,测试用假时钟推进。
|
||||
- socket `onClose` 时 `registry.clear()`,对齐源项目 `onDisconnected: cars.value = []`。
|
||||
|
||||
渲染适配层据 `visible` 切 `entity.show`,**不删实体**,与槽位复用配套。
|
||||
|
||||
模型选择(R8):OBU 的数据 hook 虽标记 `car_obu.glb`,但最终的 `CrossCars/index.vue` 模板固定
|
||||
传入 `11.glb`,因此 `modelNameFor(vehicle)` 对 OBU 返回 `11.glb`;目标车辆返回
|
||||
`${type}${subType}.glb`。预览阶段把 dashboard 的 `11/12/13/14/15/16/17/31/221/222.glb`
|
||||
复制到 `_preview/v2x-vehicles/`,并在 `vehicleModelNames` 中按文件名匹配;未知车型回退 `11.glb`
|
||||
并累加 `missingModels` 计数供面板展示。
|
||||
|
||||
平滑:用 `Cesium.SampledPositionProperty` 按 `duration` 插值,替代当前的直接赋值跳变。
|
||||
|
||||
### 4.5 宽松解析(R9)
|
||||
|
||||
源项目用 `saferEval`。浏览器端零依赖不能引入该库,也不应引入 `eval`。改为
|
||||
`parseLoosePayload(text)`:先 `JSON.parse`;失败则做一次受限规范化(单引号→双引号、
|
||||
裸键补引号、去尾逗号、`NaN`/`Infinity` → `null`)后重试;仍失败返回 `null` 并
|
||||
`state.parseFailures += 1`,面板显示失败计数(R9 要求可见)。
|
||||
|
||||
> 取舍:不用 `new Function` 还原 `saferEval` 的完整语义——那等于在预览页开一个任意代码执行面。
|
||||
> 受限规范化覆盖实际会遇到的非严格 JSON 形态,且失败可观测,比静默丢弃安全得多。
|
||||
|
||||
### 4.6 移除范围外请求(R10)
|
||||
|
||||
`loadLiveData()` 的 `Promise.allSettled` 去掉 `FlowTravelRatio/queryListWeek`,
|
||||
`state.metrics` 及面板中的 `flow metrics` 文案一并移除。
|
||||
|
||||
## 5. 数据流
|
||||
|
||||
```
|
||||
登录 → resolveCrossCode()
|
||||
→ queryCrossLinkInfo / queryPoles / findDeviceByCrossCode / crossDeviceConfig
|
||||
→ buildPhaseSignalMap(links, nativeSignals) [纯]
|
||||
→ connectLiveSockets()
|
||||
signal → normalizeSignalLamps → lampColorName → setSignalState({nodeKeys,color,countDown})
|
||||
→ cesium-preview 点亮原生灯头 + 倒计时
|
||||
obu → parseLoosePayload → normalizeObuVehicle ┐
|
||||
target → parseLoosePayload → normalizeTargetVehicles ┴→ registry.ingest → 渲染适配
|
||||
registry.sweep → entity.show
|
||||
```
|
||||
|
||||
## 6. 兼容性与回滚
|
||||
|
||||
- `v2xPreview.enabled=false` 时 `createV2xCesiumOverlay()` 仍返回 `null`,生成页不含面板,
|
||||
包产物字节不变(AC10)。
|
||||
- 原生信号灯在**无 V2X 灯态时**的行为保持现状(`hideUnconfirmedSignalAssets` 语义不变),
|
||||
只有收到实时灯态才点亮,不会退回模拟相位。
|
||||
- 回滚点:本任务对 `cesium-preview.js` 的改动限于删除重复的 `lampColorName` 与调整
|
||||
`setSignalState` 入参形状,可单独 revert 而不影响 overlay。
|
||||
- 新增 `phaseSignalMap`、`maxDistanceMeters`、`maxHeadingDeltaDegrees` 均为可选配置,
|
||||
缺省即当前默认行为。
|
||||
|
||||
## 7. 风险
|
||||
|
||||
| 风险 | 影响 | 缓解 |
|
||||
| --- | --- | --- |
|
||||
| 几何匹配在实际路口全部落空 | 灯仍不亮 | 面板显性报「0 已绑定」;`phaseSignalMap` 配置覆盖兜底 |
|
||||
| `computeViewRectangle()` 返回 undefined | OBU 过滤异常 | 退回空串=不过滤 |
|
||||
| 宽松解析未覆盖真实非法形态 | 丢消息 | `parseFailures` 计数可见,便于按真实样本补规则 |
|
||||
| 服务端心跳格式与源项目不符 | 仍被断开 | 心跳报文与间隔取自源项目源码,AC1 用模拟服务端验证 |
|
||||
| 真实推送 `interval` 缺失 | 车辆过早隐藏 | 缺失时回退 500ms,与源项目一致 |
|
||||
@@ -0,0 +1,4 @@
|
||||
{"file": ".trellis/spec/preview/index.md", "reason": "预览层契约:无构建步骤、零依赖 IIFE、参数经 window.OSM_ASSET_PREVIEW_CONFIG 注入。overlay 与 cesium-preview.js 的所有改动都受这些约束。"}
|
||||
{"file": ".trellis/spec/pipeline/index.md", "reason": "确认 build-area.js 如何把 v2x-cesium-overlay.js 拷入产物,避免误增文件破坏包契约。"}
|
||||
{"file": ".trellis/spec/config/index.md", "reason": "新增可选配置 v2xPreview.phaseSignalMap / maxDistanceMeters / maxHeadingDeltaDegrees 需符合区域配置约定。"}
|
||||
{"file": "docs/v2x-cesium-preview.md", "reason": "现有 V2X 预览文档:配置项、坐标契约、代理要求、回滚方式。移除 FlowTravelRatio 与新增配置后需同步更新。"}
|
||||
171
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/implement.md
Normal file
171
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/implement.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# Implement — 还原 V2X 实时路口信号灯与车辆展示
|
||||
|
||||
## 参照实现(只读)
|
||||
|
||||
`/Users/que01/Project/v2x_web/applications/dashboard/src/views/dashboard/components/Content/Map/HologramCross/`
|
||||
|
||||
改动前先读对应源文件,不要凭记忆写契约。
|
||||
|
||||
## 改动文件
|
||||
|
||||
| 文件 | 性质 |
|
||||
| --- | --- |
|
||||
| `scripts/lib/v2x-cesium-overlay.js` | 主要改动,L1–L4 分层重写 |
|
||||
| `scripts/lib/cesium-preview.js` | 删除重复 `lampColorName`,调整 `setSignalState` 入参 |
|
||||
| `scripts/test-v2x-cesium-overlay.js` | 扩充单测,覆盖 AC2–AC7 |
|
||||
| `.trellis/tasks/.../prd.md` `design.md` | 如实现中发现偏差需回写 |
|
||||
|
||||
不改:`build-area.js`、`area-preview.js`、包产物生成逻辑。
|
||||
|
||||
## 执行顺序
|
||||
|
||||
每步结束跑一次 `npm run test:v2x-cesium-preview`,保持绿色再进下一步。
|
||||
|
||||
### 步骤 1 — L2 灯色字典 + 宽松解析(R2、R9)
|
||||
|
||||
1. 在 overlay 加 `LAMP_STATUS`(`11/21/22/23/31`)与 `lampColorName(status)`,未知返回 `"other"`。
|
||||
2. 删除旧 `lampColor()` 中 `Number(status)===3 / ===2` 的判断。
|
||||
3. 加 `parseLoosePayload(text)`:`JSON.parse` → 受限规范化重试 → `null`。
|
||||
4. 心跳报文 `pong` 短路判定保留(与源项目一致)。
|
||||
5. 二者导出到 `.utils`。
|
||||
|
||||
验证:`node scripts/test-v2x-cesium-overlay.js`
|
||||
- `lampColorName(21)==="red"`, `(22)==="yellow"`, `(23)==="green"`, `(11)==="off"`, `(31)==="other"`
|
||||
- **`lampColorName(2)!=="yellow"` 且 `lampColorName(3)!=="green"`**(防回归,AC2)
|
||||
- `parseLoosePayload("{'a':1,}")` 解析成功;`parseLoosePayload("{bad")===null`(AC7)
|
||||
|
||||
**审查点 A**:灯色字典必须与 `HologramCross/components/utils.ts` 的
|
||||
`lightStatusColorDict` 逐项一致,不得增删码值。
|
||||
|
||||
### 步骤 2 — L1 几何工具(支撑 R3)
|
||||
|
||||
1. 加 `getAngle(start, end)`:直接对齐源项目 `utils.ts` 的实现(返回 0–360)。
|
||||
2. 加 `haversineMeters(a, b)`、`angleDeltaDegrees(a, b)`(后者处理 350° vs 10° 的环绕)。
|
||||
3. 导出到 `.utils`。
|
||||
|
||||
验证:正北 `getAngle([0,0],[0,1])≈0`;正东 `≈90`;
|
||||
`angleDeltaDegrees(350,10)===20`;`haversineMeters` 对已知两点误差 < 0.5m。
|
||||
|
||||
### 步骤 3 — 相位映射(R3、R4)
|
||||
|
||||
1. 实现 `buildPhaseSignalMap(links, nativeSignals, options)`,返回
|
||||
`{byPhase, bound, unbound, diagnostics}`,算法见 `design.md` §4.2。
|
||||
2. link 末两点**先 GCJ-02→WGS84 再算角度与距离**(原生信号是 WGS84)。
|
||||
3. 一 link 匹配多 signal;`Map<phaseNo, Set<nodeKey>>` 并集累加。
|
||||
4. 支持 `options.phaseSignalMap` 配置覆盖,命中则跳过几何匹配。
|
||||
5. 默认 `maxDistanceMeters=30`、`maxHeadingDeltaDegrees=45`。
|
||||
6. 导出到 `.utils`。
|
||||
|
||||
验证(AC3、AC4):
|
||||
- 用 `outputs/fengshu-er-road/package/runtime/traffic-signals.json` 的真实 7 条信号做夹具,
|
||||
配一组构造的 link(末点取自 `stopLongitude/stopLatitude` 反算 GCJ-02、
|
||||
航向取自 `headingDegrees`),断言 `bound > 0`。
|
||||
- 构造两条不同 link 共用同一 `phaseNo`,断言 `byPhase.get(phaseNo)` 含**两条 link 的全部 nodeKey**。
|
||||
- 构造一条远离所有信号的 link,断言其相位进入 `unbound` 且带 `reason`。
|
||||
- 配置覆盖生效:`phaseSignalMap` 指定的 phaseNo 取配置值。
|
||||
|
||||
**审查点 B**:这是全任务最易做错的一步。合入前必须用真实 `traffic-signals.json` 跑出
|
||||
非零绑定数,若为 0 则说明容差或坐标方向反了,**不得靠放大容差蒙混过关**——先打印
|
||||
`diagnostics` 的实际距离/角差,确认量级合理(距离应是米级而非千米级)。
|
||||
|
||||
### 步骤 4 — 车辆注册表(R7、R8)
|
||||
|
||||
1. 实现 `createVehicleRegistry({now})`,含 `ingest / sweep / list / clear`。
|
||||
2. `interval` 取自推送体,`0` 或缺失回退 `500`。
|
||||
3. `sweep`:`|now - timeStamp| >= interval*1.5` → `visible=false`。
|
||||
4. 槽位复用:优先占用同 `model` 的不可见记录。
|
||||
5. `modelNameFor(vehicle)`:OBU → dashboard 最终模板使用的 `11.glb`;target → `${type}${subType}.glb`。把 dashboard 车辆 GLB 复制到预览包。
|
||||
6. `normalizeTargetVehicles` 补上源项目的 `type==1 && isNil(subType) → subType=1`。
|
||||
7. 导出 `createVehicleRegistry`、`modelNameFor`。
|
||||
|
||||
验证(AC5):
|
||||
- 注入一次推送 → `list()` 一条 `visible=true`。
|
||||
- 假时钟推进 `interval*1.5` 后 `sweep()` → 该车 `visible=false`。
|
||||
- 再注入同 key → 复用原槽位,`list().length` 不变。
|
||||
- `clear()` 后为空。
|
||||
- `modelNameFor({kind:"obu"})==="11.glb"`;`{kind:"target",type:1,subType:2}` → `"12.glb"`。
|
||||
|
||||
### 步骤 5 — WebSocket 连接层(R1、R6)
|
||||
|
||||
1. 实现 `createSocket({url, onOpen, onMessage, onClose, heartbeatMs, reconnect, socketFactory, setIntervalFn, setTimeoutFn})`。
|
||||
2. 心跳 `{"heartBeat":"ping"}` / 30000ms。
|
||||
3. 退避重连 `1s→2s→4s→8s` 封顶,**重连成功重放 `onOpen`**。
|
||||
4. 主动 `close()` 不重连;`dispose()` 清所有计时器。
|
||||
5. 三个 socket 的订阅帧按 `design.md` §4.3 的表格实现。
|
||||
6. OBU bounds:`camera.computeViewRectangle()` → WGS84→GCJ-02 → `"lng,lat;…"` 四角,
|
||||
`camera.moveEnd` 节流发送;不可用时发空串。
|
||||
7. 导出 `createSocket`、`buildBoundsMessage`。
|
||||
|
||||
验证(AC1、AC6):
|
||||
- 假 socket + 假时钟:推进 30s 断言收到一帧 `{"heartBeat":"ping"}`;推进 90s 断言三帧。
|
||||
- 模拟 `onclose` 后推进退避时间,断言重新建连**且订阅帧被重放**。
|
||||
- signal 首帧 `{"junctionId":"<code>"}`;target 无配置首帧 `{"deviceId":null}`,
|
||||
有配置 `{"deviceId":"1,2"}`;obu 首帧为空串。
|
||||
- `dispose()` 后推进时钟不再产生任何发送。
|
||||
|
||||
**审查点 C**:心跳报文与间隔必须逐字节对齐源项目
|
||||
(`useObuCars.ts:28-31`、`useTargetCars.ts:27-30`、`CrossTrafficLights3D.vue:122-130`)。
|
||||
|
||||
### 步骤 6 — 接线与渲染适配(R4、R5、R10)
|
||||
|
||||
1. `loadLiveData()` 移除 `FlowTravelRatio/queryListWeek`,删 `state.metrics` 与面板 flow 文案。
|
||||
2. 用 `buildPhaseSignalMap` 结果替换 `state.linkPhases`(`Map<phase, entity>` 整体删除)。
|
||||
3. `updateSignalPhases` → 归一化为 `{nodeKeys, color, countDown}` 列表后调
|
||||
`context.setSignalState(...)`,同时给 link polyline 上色(同相位多 link 全部上色)。
|
||||
4. 车辆渲染改为消费 `registry`:`visible` 切 `entity.show`,位置用
|
||||
`SampledPositionProperty` 按 `duration` 插值。
|
||||
5. 面板状态行补:已绑定/未绑定相位数、缺失模型数、解析失败数。
|
||||
6. `dispose()` 清 socket、计时器、实体、registry。
|
||||
|
||||
### 步骤 7 — `cesium-preview.js` 对接(R2、R5)
|
||||
|
||||
1. 删除 `cesium-preview.js` 中的 `lampColorName()`(与 overlay 重复且映射错误)。
|
||||
2. `createLiveTrafficSignals().update(lamps)` 改签名为
|
||||
`update(entries)`,`entries = [{nodeKeys:string[], color:"red"|"yellow"|"green"|"off"|"other", countDown:number}]`。
|
||||
3. 按 `nodeKeys` 直接点亮 `TrafficSignalDynamic_<nodeKey>_<color>` 节点,
|
||||
删除失效的 `signalByPhase` 回退逻辑。
|
||||
4. `countDown` 驱动 `category === "countdown"` 资产显示。
|
||||
5. `color === "off" | "other"` 时三色节点全灭。
|
||||
|
||||
验证:`npm run test:preview-assets`
|
||||
|
||||
### 步骤 8 — 全量校验
|
||||
|
||||
```bash
|
||||
npm run test:v2x-cesium-preview
|
||||
npm run test:v2x-preview-server
|
||||
npm run test:preview-assets
|
||||
npm run test:traffic-signals
|
||||
npm run test:native-preview-traffic
|
||||
npm run test:package-contract
|
||||
```
|
||||
|
||||
AC10 校验:将区域配置 `v2xPreview.enabled` 置 `false` 重新生成,
|
||||
确认生成页无 V2X 面板且 `package/` 下产物字节不变。
|
||||
|
||||
## 人工验收(AC9,需真实上游)
|
||||
|
||||
```bash
|
||||
V2X_UPSTREAM=http://<host>:<port> \
|
||||
npm run serve:v2x-preview -- --root outputs/fengshu-er-road --host 0.0.0.0 --port 7862
|
||||
```
|
||||
|
||||
登录后逐项确认:
|
||||
- [ ] 信号灯随相位变色,颜色与 dashboard 实时路口一致
|
||||
- [ ] 倒计时数字走动
|
||||
- [ ] 车辆出现、移动平滑、离开视野后消失(无幽灵车堆积)
|
||||
- [ ] 连接持续 5 分钟以上不掉线
|
||||
- [ ] 车辆与路口位置无明显偏移(坐标未二次转换)
|
||||
- [ ] 面板绑定数、失败数符合预期
|
||||
|
||||
## 回滚点
|
||||
|
||||
- 步骤 7 独立可 revert(只动 `cesium-preview.js`)。
|
||||
- 步骤 1–6 全在 overlay 单文件内,`git checkout scripts/lib/v2x-cesium-overlay.js` 即回到当前状态。
|
||||
- `v2xPreview.enabled=false` 是运行时总开关。
|
||||
|
||||
## 不做(留待后续任务)
|
||||
|
||||
- AMap 底图 / 矢量瓦片图层
|
||||
- `CrossDevices/Model3D.vue` 3D 杆件模型(设备维持点位)
|
||||
- `useV2XEvents` 事件气泡
|
||||
99
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/prd.md
Normal file
99
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/prd.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# 还原 V2X 实时路口信号灯与车辆展示
|
||||
|
||||
## Goal
|
||||
|
||||
把 `v2x_web` dashboard「实时路口」(`HologramCross`) 的实时数据行为完整搬到本项目的 Cesium 预览中,使
|
||||
`outputs/<area>/…-cesium-preview.html` 在登录后能持续、正确地显示 **信号灯相位灯色与倒计时** 和
|
||||
**OBU / 目标识别车辆**。
|
||||
|
||||
当前实现(Codex 移植)接口地址是对的,但数据处理与渲染层基本失真,实际效果是:灯永远红、车可能一辆都不出现、
|
||||
出现后也不消失、连接几十秒后被服务端断开。本任务修复这条链路。
|
||||
|
||||
## Source of Truth
|
||||
|
||||
参照实现(只读,不修改):
|
||||
`/Users/que01/Project/v2x_web/applications/dashboard/src/views/dashboard/components/Content/Map/HologramCross/`
|
||||
|
||||
- `components/CrossTrafficLights3D.vue` — 信号灯:link 相位、WS 订阅、灯色/倒计时
|
||||
- `components/utils.ts` — 灯色状态字典、航向角计算
|
||||
- `components/CrossCars/hooks/useObuCars.ts` — OBU 车辆
|
||||
- `components/CrossCars/hooks/useTargetCars.ts` — 目标识别车辆
|
||||
- `components/CrossCars/type.ts` — 车辆数据结构
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1 WebSocket 连接层对齐
|
||||
- 三个 socket(`signal` / `obuPosition` / `targetPosition`)均按源项目发送心跳
|
||||
`{"heartBeat":"ping"}`,间隔 30000ms。
|
||||
- 断线自动重连,重连后重新发送各自的订阅消息(signal 发 `junctionId`,target 发 `deviceId`,obu 发 bounds)。
|
||||
- 页面 `visibilitychange` 回到可见时行为不得导致重复连接堆积;`dispose()` 必须清掉心跳与重连计时器。
|
||||
|
||||
### R2 信号灯灯色映射修正
|
||||
- 采用源项目 `utils.ts` 的状态字典:`11=灭灯 21=红 22=黄 23=绿 31=其他`。
|
||||
- 现有 `lampColor()`(overlay)与 `lampColorName()`(`cesium-preview.js`)中基于 `2`/`3` 的判断必须移除。
|
||||
- 未知状态码按「其他/灭灯」处理,不得静默落到红色。
|
||||
|
||||
### R3 相位到原生信号灯的映射
|
||||
- `traffic-signals.json` 中不存在 V2X `phaseNo`(只有 `phaseGroup: 0`),当前回退到 `signal.id` 导致永不匹配。
|
||||
必须建立一条真实可用的 V2X `phaseNo` → 原生信号灯头的映射。
|
||||
- 映射结果必须可诊断:面板要能显示「N 个相位已绑定 / M 个未绑定」。
|
||||
- 允许区域配置提供显式覆盖映射。
|
||||
|
||||
### R4 一个相位可点亮多条进口道
|
||||
- 同一 `phaseNo` 对应的所有 link / 信号灯头都要被点亮,当前 `Map<phase, entity>` 的覆盖行为必须消除。
|
||||
|
||||
### R5 倒计时
|
||||
- 使用推送中的 `countDown` 字段(源项目 `phaseValue['time'] = signal.countDown`),驱动预览中已有的
|
||||
countdown 资产或等效显示。
|
||||
|
||||
### R6 车辆订阅正确建立
|
||||
- OBU socket 连接后必须发送订阅消息(源项目 `send(bounds || '')`),相机视野变化时发送
|
||||
`{"bounds": "<lng,lat;…>"}`。
|
||||
- target socket 连接后发送 `{"deviceId": "<ids joined by comma>"}`,无配置时发送 `{"deviceId": null}`。
|
||||
|
||||
### R7 车辆生命周期
|
||||
- 按源项目实现超时隐藏:超过 `interval * 1.5` 未更新的车辆隐藏并可复用槽位;`interval` 取自推送体,
|
||||
`0` 时回退 `500`。
|
||||
- socket 断开时清空车辆列表。
|
||||
- 车辆位置更新使用推送间隔做平滑插值,避免逐帧跳变。
|
||||
|
||||
### R8 车辆模型命名与资产对齐
|
||||
- OBU 数据层标记为 `car_obu.glb`,但 dashboard 的实际 `CrossCars` 模板固定渲染 `11.glb`;预览必须遵从该最终渲染契约。目标识别车辆使用 `${type}${subType}.glb`。
|
||||
- 预览包必须携带 dashboard 实际使用的车辆模型;包内缺少未知车型时回退到 `11.glb`,并在面板提示,不得静默显示错误车型。
|
||||
|
||||
### R9 推送体解析健壮性
|
||||
- 源项目对两个车辆 socket 使用 `saferEval` 而非 `JSON.parse`,说明载荷不保证严格 JSON。
|
||||
- 解析必须容忍非严格 JSON,且解析失败要计数并在面板可见,不得整条静默丢弃。
|
||||
|
||||
### R10 范围纠正
|
||||
- `/facilities/api/FlowTravelRatio/queryListWeek`(周流量比)不属于「实时路口」,从实时加载链路移除。
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- 不移植 AMap 底图 / 矢量瓦片图层(`linkVectorHdMapTileUrl`)。
|
||||
- 不移植 `CrossDevices/Model3D.vue` 的 3D 杆件模型;设备维持点位显示(可后续独立任务)。
|
||||
- 不移植 `useV2XEvents` 的 V2X 事件气泡。
|
||||
- 不改变原生包产物(GLB / metadata / traffic-signals.json 的生成逻辑)。
|
||||
|
||||
## Constraints
|
||||
|
||||
- 不得提交上游地址、账号、令牌、AMap key;`v2xPreview` 配置保持非敏感。
|
||||
- 令牌只存 `sessionStorage`,页面始终从登录门进入。
|
||||
- 坐标契约不变:V2X 数据 GCJ-02,进 Cesium 前转一次 WGS84;原生数据不得二次转换。
|
||||
- `v2xPreview.enabled` 默认 `false`,关闭时生成页不含 V2X 面板。
|
||||
- 生成的预览为静态页,无构建步骤,overlay 保持零依赖 IIFE。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] AC1 三个 socket 均可见 30s 心跳;模拟服务端在 60s+ 后连接仍存活。
|
||||
- [ ] AC2 单测覆盖灯色映射:`21→红 22→黄 23→绿 11→灭 31→其他`,且 `2`/`3` 不再被当作黄/绿。
|
||||
- [ ] AC3 单测覆盖相位映射:给定 V2X link 相位与 `traffic-signals.json`,输出的绑定数 > 0,
|
||||
未绑定项被明确列出。
|
||||
- [ ] AC4 单测覆盖同相位多进口道:一个 `phaseNo` 对应 2 条 link 时,两条都被点亮。
|
||||
- [ ] AC5 单测覆盖车辆超时:注入两次推送并推进时间超过 `interval*1.5`,车辆转为不可见。
|
||||
- [ ] AC6 单测覆盖 OBU/target 订阅消息:连接后发出的首帧内容与源项目一致。
|
||||
- [ ] AC7 单测覆盖非严格 JSON 推送体能被解析,且失败计数可读。
|
||||
- [ ] AC8 `npm run test:v2x-cesium-preview`、`test:v2x-preview-server`、`test:preview-assets` 全绿。
|
||||
- [ ] AC9 真机(`npm run serve:v2x-preview` 接真实上游)登录后可见:信号灯随相位变色并显示倒计时、
|
||||
车辆出现并移动、离开后消失。此项由开发者人工确认。
|
||||
- [ ] AC10 `v2xPreview.enabled=false` 重新生成后,页面不含 V2X 面板,包产物字节不变。
|
||||
26
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/task.json
Normal file
26
.trellis/tasks/08-24-v2x-realtime-cross-fidelity/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "v2x-realtime-cross-fidelity",
|
||||
"name": "v2x-realtime-cross-fidelity",
|
||||
"title": "还原 V2X 实时路口信号灯与车辆展示",
|
||||
"description": "",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-24",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
1
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/check.jsonl
Normal file
1
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/check.jsonl
Normal file
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
96
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/prd.md
Normal file
96
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/prd.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# 后续计划:drawtonomy 扩展 PoC
|
||||
|
||||
前置任务:`08-25-road-compiler-extraction` 的 Phase 0-3 完成并完成验收。
|
||||
评估依据:父任务归档前的 `design.md` §4 决策记录 D1 / D2 / D3。
|
||||
|
||||
## Goal
|
||||
|
||||
在**编译器仓库内**实现一个 drawtonomy 扩展:把编译产物注入其浏览器编辑器,
|
||||
回读快照后走其开源导出器产出 OpenDRIVE / Lanelet2。
|
||||
|
||||
定位是 **PoC,不进关键路径**。目的是验证「编译器当 scene generator +
|
||||
drawtonomy 当编辑器与工业格式后端」这条链路是否值得投入。
|
||||
|
||||
## 前提认知(已核实)
|
||||
|
||||
必须先明确,否则会做错方向:
|
||||
|
||||
- **drawtonomy 编辑器闭源**,克隆仓库只有 SDK / dev-server / mcp-server。
|
||||
README 卖点里的 topology-aware lanes、lane tool、intersection templates、
|
||||
Map→lanes 全部不在开源代码内。
|
||||
- **它不做 raw OSM 推导**。`exporter/osmParser.ts` 是 Lanelet2 解析器
|
||||
(车道左右边界已显式)。整个 exporter 目录 grep `highway` 命中 1 次且是
|
||||
车道类型字符串。→ **不能替代本编译器**,只能做下游。
|
||||
- **junctionTools 不能做成扩展**:无 canvas/overlay 能力、无工具注册、
|
||||
**无任何 change 事件推送**(`ExtensionClient.handleMessage` 入站只有
|
||||
`ext:init` / 5 个 `*-response` / `ext:error`,读取全靠轮询)。
|
||||
→ junction 编辑留在自有 workbench。
|
||||
- **`drawtonomy-dev-server` 是 drawtonomy.com 的缓存代理**(TTL 1 小时),
|
||||
非自托管。宿主协议会漂(manifest 有 `minHostVersion`)。→ 不进关键路径。
|
||||
|
||||
## Requirements
|
||||
|
||||
### R4.1 扩展:编译产物 → 编辑器
|
||||
|
||||
- manifest capabilities:`shapes:write`、`ui:panel`、`snapshot:read`、`ui:notify`
|
||||
- 面板调编译器的本地 HTTP 服务(`road-workbench` 已经是一个 HTTP 服务)取产物
|
||||
- 车道边界 → `createLaneWithBoundaries(leftPoints, rightPoints, opts)` → `addShapes()`
|
||||
- 坐标转换:本项目 WGS84/ENU(米)→ drawtonomy 画布像素。
|
||||
沿用其 `drawtonomy_origin_lat/lon` + `latLonToCanvas` 约定
|
||||
- ⚠️ **借它的结构,不借它的坐标系**:其 `BaseShape.x/y` 是画布像素
|
||||
(`z` 注释明确写 "world units — NOT canvas pixels like x/y")。
|
||||
编译器内部坐标系不得因此改变
|
||||
|
||||
### R4.2 回读 → 工业格式导出
|
||||
|
||||
- `requestSnapshot()` 取回快照
|
||||
- 本地跑其开源 `exportToOpenDrive` / `lanelet2`(Apache-2.0)
|
||||
- 产出 `.xodr` / `.osm`(Lanelet2),用其 `validateOpenDrive` 自校验
|
||||
- Lanelet2 是 Autoware 的输入格式 —— 对本项目 V2X 方向有实际价值
|
||||
|
||||
### R4.3 许可与依赖合规
|
||||
|
||||
- drawtonomy SDK 是 Apache-2.0:借用代码需保留 NOTICE / 署名
|
||||
- 若依赖 drawtonomy.com 托管服务,需查其服务条款(app 非 Apache-2.0)
|
||||
- ESM/CJS 互操作:SDK 是 ESM+TS,编译器是 CJS。
|
||||
本阶段可**局部**引入构建步骤,但仅限扩展目录,不得污染编译器核心
|
||||
(父任务 C2 的边界)
|
||||
|
||||
### R4.4 归属
|
||||
|
||||
扩展代码放**编译器仓库**,不放宿主 —— 符合父任务「独立维护」目标。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] AC4.1 扩展能把一个区域的编译产物注入 drawtonomy 编辑器并正确显示车道
|
||||
- [ ] AC4.2 编辑器内手改后回读快照,能导出通过 `validateOpenDrive` 的 `.xodr`
|
||||
- [ ] AC4.3 能导出 Lanelet2 `.osm`
|
||||
- [ ] AC4.4 编译器核心未引入构建步骤(构建仅限扩展目录)
|
||||
- [ ] AC4.5 编译器内部坐标系未因适配画布像素而改变
|
||||
- [ ] AC4.6 Apache-2.0 署名 / NOTICE 已按要求保留
|
||||
- [ ] AC4.7 结论记录:这条链路是否值得继续投入,写入任务 `research/`
|
||||
|
||||
## 启动条件
|
||||
|
||||
- 道路编译器独立化父任务(Phase 0-3)已完成验收。
|
||||
- 编译器仓库、稳定 CLI 与版本化输出契约已可用。
|
||||
- 此任务不阻塞道路编译器抽离或渲染分离的交付。
|
||||
|
||||
## 明确不做
|
||||
|
||||
- junctionTools 做成扩展(API 不支持,见前提认知)
|
||||
- 把 drawtonomy 放进生产构建链路
|
||||
- 用 drawtonomy 替代编译器(D1 已否决)
|
||||
- 自建通用白板编辑器
|
||||
|
||||
## 后续可能(不属本阶段)
|
||||
|
||||
父任务 design §4 的 D3 列了三项值得从 drawtonomy 借用的东西,
|
||||
它们与本阶段独立,应各自开任务:
|
||||
|
||||
- `odrGeometryFit.ts` 的拟合器 → 替掉手调的
|
||||
`approachWidthMultiplier=1.45` / `coreRadiusMeters=28`,改为对 `referenceFile`
|
||||
拟合、残差作质量指标
|
||||
- validator 的 mutation-proven 方法 → 给现有 31 条诊断规则建触发证明
|
||||
- validator 的分层命名空间(`xml.*` / `ref.*` / `junction.*` / `geom.*`)
|
||||
→ 替代当前平铺的 31 条规则
|
||||
28
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/task.json
Normal file
28
.trellis/tasks/08-25-rc-p4-drawtonomy-ext/task.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"id": "rc-p4-drawtonomy-ext",
|
||||
"name": "rc-p4-drawtonomy-ext",
|
||||
"title": "后续计划:drawtonomy 扩展 PoC",
|
||||
"description": "道路编译器独立化完成后,评估在编译器仓库内实现 drawtonomy 扩展:编译产物注入编辑器,快照回读后走开源 OpenDRIVE/Lanelet2 导出",
|
||||
"status": "planning",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P3",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-25",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "不属于道路编译器独立化父任务的完成条件。父任务验收完成后,将此任务作为下一项候选任务提示。",
|
||||
"meta": {
|
||||
"follow_up_after": "08-25-road-compiler-extraction"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Native Road Parity Baselines
|
||||
|
||||
Generated from commit `25cf82e7c75ea8d881639c4a80648121efbd39f6` on 2026-08-25.
|
||||
|
||||
```bash
|
||||
npm run road:compile -- --config config/areas/fengshu-er-road.json
|
||||
node scripts/road-parity.js --config config/areas/fengshu-er-road.json \
|
||||
--snapshot .trellis/tasks/08-25-road-compiler-extraction/baseline/fengshu-er-road.json
|
||||
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
node scripts/road-parity.js --config config/areas/nantaizi-lake-innovation-valley.json \
|
||||
--snapshot .trellis/tasks/08-25-road-compiler-extraction/baseline/nantaizi-lake-innovation-valley.json
|
||||
```
|
||||
|
||||
Both samples emit all twelve native-road layers. `fengshu-er-road` enables its
|
||||
configured complex-junction template; `nantaizi-lake-innovation-valley` uses
|
||||
the default, template-free compiler path. `hanyang-block` is an abandoned area
|
||||
and deliberately excluded from this corpus.
|
||||
|
||||
The snapshot normalizes absolute source paths and random `native-road-*`
|
||||
staging names. It does not round coordinates or ignore feature ordering.
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"contract": "native-road-package/v1",
|
||||
"areaId": "fengshu-er-road",
|
||||
"files": {
|
||||
"../native-traffic-signals.json": {
|
||||
"contentHash": "88e7f0ef4fc7bdb9ef202af018b3fb3179035ffc8ed5eeba03276c263e1f0476",
|
||||
"bytes": 5886
|
||||
},
|
||||
"comparison.json": {
|
||||
"contentHash": "651eb4000a44a7521a79c3e1429795b83d20f8899e0c67c4bb25b56cf4a6f75d",
|
||||
"bytes": 1295
|
||||
},
|
||||
"compiled.json": {
|
||||
"contentHash": "2ab82eafcdab70155078fae3559692ef1fde0645a52c6decbaea762a9ffeb24a",
|
||||
"bytes": 159230
|
||||
},
|
||||
"diagnostics.json": {
|
||||
"contentHash": "1d9fdae06ddcbc19b5262b797a4a35c494fc30a98a66dbfa111533b12c5fdfc7",
|
||||
"bytes": 6862
|
||||
},
|
||||
"layers/center_lines.geojson": {
|
||||
"contentHash": "04db66df5821b319d59297b8515502fabcad95faf083cae19edb338be2f6024b",
|
||||
"orderHash": "04db66df5821b319d59297b8515502fabcad95faf083cae19edb338be2f6024b",
|
||||
"features": 5,
|
||||
"bytes": 6909
|
||||
},
|
||||
"layers/connectors.geojson": {
|
||||
"contentHash": "e7979d4642c7c10f33fb23e92721f22533a851ae1a2b4d35f224d50927bf2fe9",
|
||||
"orderHash": "c327b7b154d4d9ca04cd895523eb0d8ad67c37051ef1d2135b2b46c51e12df58",
|
||||
"features": 90,
|
||||
"bytes": 197885
|
||||
},
|
||||
"layers/crosswalks.geojson": {
|
||||
"contentHash": "354cd4d7ed105a11b95904b44848dce567b3a3383f76755936d8c00691fe29af",
|
||||
"orderHash": "d24376438ac956bde52fcbfa955dd72c2ba31a97995608aaed1c79f6fd3633ab",
|
||||
"features": 152,
|
||||
"bytes": 219286
|
||||
},
|
||||
"layers/direction_arrows.geojson": {
|
||||
"contentHash": "7b8362ad4912c997a7661befde55ddfe58757c3c8d139c15867e1938ea8f6918",
|
||||
"orderHash": "5274e8b433946410a7cdffdaa452c1750e04f53e73a86bf81d034cfb26d4c254",
|
||||
"features": 686,
|
||||
"bytes": 1053308
|
||||
},
|
||||
"layers/edge_lines.geojson": {
|
||||
"contentHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"orderHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"features": 0,
|
||||
"bytes": 52
|
||||
},
|
||||
"layers/intersection_surface.geojson": {
|
||||
"contentHash": "b2022ca70339aa470f3be451bf3ba2f55b59c3900645c864538372173811309f",
|
||||
"orderHash": "b2022ca70339aa470f3be451bf3ba2f55b59c3900645c864538372173811309f",
|
||||
"features": 5,
|
||||
"bytes": 13048
|
||||
},
|
||||
"layers/lane_centerlines.geojson": {
|
||||
"contentHash": "19c550c794810c1071de55dee158295235106a56e3877a2ba10da920ee214593",
|
||||
"orderHash": "67f8e7d33ec578644416e4837dd4aafef62a55aaeb4d438d07ab07eddfbb1df1",
|
||||
"features": 84,
|
||||
"bytes": 73294
|
||||
},
|
||||
"layers/lane_separators.geojson": {
|
||||
"contentHash": "02fdf2c7b6e89c2850a60ea2ac72b9f62910259bdb2949681fcaa7e67d2b51f0",
|
||||
"orderHash": "81782a059c4ff9f69fdf7de9107d20a8d6cf55d34bb0620ec28a9a8c5106c982",
|
||||
"features": 3596,
|
||||
"bytes": 4164106
|
||||
},
|
||||
"layers/road_surface.geojson": {
|
||||
"contentHash": "5a2936a705e64338d0d79b395a13d761f1893dd72330a9b9ee4696ae75eab295",
|
||||
"orderHash": "d8402ae6e772e0778b862b190abfa291de1b02a15537d455f7dcd8596245dfd8",
|
||||
"features": 30,
|
||||
"bytes": 49554
|
||||
},
|
||||
"layers/sidewalk_surface.geojson": {
|
||||
"contentHash": "b6f8f876e9f18fd2ef6749125a09d654c73d986e916bd262ceb9967bca1a54c6",
|
||||
"orderHash": "3fd42a67e18e3b8743fa538428e039ef66a1f44150a6881a7aaa18326607b5cb",
|
||||
"features": 26,
|
||||
"bytes": 54997
|
||||
},
|
||||
"layers/turn_arrows.geojson": {
|
||||
"contentHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"orderHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"features": 0,
|
||||
"bytes": 52
|
||||
},
|
||||
"layers/vehicle_stop_lines.geojson": {
|
||||
"contentHash": "cb904550ab8eec011cf33e5edfa81044de6dfca0280dbfe488e77229055ed590",
|
||||
"orderHash": "8a4b877a17a5af60adc8656465afad6756944736291664139e878d46c5a4f363",
|
||||
"features": 4,
|
||||
"bytes": 4191
|
||||
},
|
||||
"traffic-signal-assemblies.json": {
|
||||
"contentHash": "1788026a0c8c531b2fc7dd49628f7fbadd809c7b2f7f8819cffb3ac7d30689ca",
|
||||
"orderHash": "b409ea6b77b6c8639f63c33a156581f9b184a84fde34d265b5deb05705828251",
|
||||
"features": 7,
|
||||
"bytes": 5411
|
||||
},
|
||||
"traffic-signals.json": {
|
||||
"contentHash": "625b79b6eb82b41f2099ba51960717c7f794fdfbd87310eb12e99da966540d89",
|
||||
"bytes": 15491
|
||||
}
|
||||
},
|
||||
"volatileExcluded": [
|
||||
"absolute paths -> <repo> or <external>",
|
||||
"native-road-* staging directory -> <staging>"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"contract": "native-road-package/v1",
|
||||
"areaId": "nantaizi-lake-innovation-valley",
|
||||
"files": {
|
||||
"../native-traffic-signals.json": {
|
||||
"contentHash": "36cb9f069fb44a6e24f0af0d130ef8cb5b4a93250dc9fb0891191d1ba039a5b2",
|
||||
"bytes": 28329
|
||||
},
|
||||
"comparison.json": {
|
||||
"contentHash": "5aa11da443221a48288641ac6950c6e4d75d834713a93110bbf265859202a265",
|
||||
"bytes": 1153
|
||||
},
|
||||
"compiled.json": {
|
||||
"contentHash": "ea5ae4a04f1259b1ccec9fc1cca7ef8ba81426a77584a447480b6047b37f5e01",
|
||||
"bytes": 184985
|
||||
},
|
||||
"diagnostics.json": {
|
||||
"contentHash": "7e9327f0424dd612f389c560c114a4cafee1349bbfa36f49b2b4eab846e5ca91",
|
||||
"bytes": 9755
|
||||
},
|
||||
"layers/center_lines.geojson": {
|
||||
"contentHash": "2b2710fce06e75b0a3e4941c2da6119cea7b8bb7834345607bbee384de20ee6b",
|
||||
"orderHash": "ea71a8841370689d6fc2b75d0b30ec7104638fb2904da6d707dc82ff3131e065",
|
||||
"features": 511,
|
||||
"bytes": 694102
|
||||
},
|
||||
"layers/connectors.geojson": {
|
||||
"contentHash": "901a86bc6c536d38bb73ae4654ec1ce7d1c0790c2d6eba92543c48f584cd15ac",
|
||||
"orderHash": "4df882ad59b65a97d892df64de0156b35beaf071b04eed671ef7b535e9e2a7b9",
|
||||
"features": 76,
|
||||
"bytes": 160866
|
||||
},
|
||||
"layers/crosswalks.geojson": {
|
||||
"contentHash": "53fda76182c0d1db5f9e7ac284337ece62655d7fee8bb446fe170ea26abe8bbc",
|
||||
"orderHash": "53fda76182c0d1db5f9e7ac284337ece62655d7fee8bb446fe170ea26abe8bbc",
|
||||
"features": 48,
|
||||
"bytes": 50536
|
||||
},
|
||||
"layers/direction_arrows.geojson": {
|
||||
"contentHash": "38e80bbfe02d4f12ce40e7d0c8342e3bef6b602c64f81dd320b366b6a6b3d205",
|
||||
"orderHash": "579aa6c37585bf4edf9d6b6b1e120f976b93ec2d0c303d82a154b75c114a45f5",
|
||||
"features": 324,
|
||||
"bytes": 493431
|
||||
},
|
||||
"layers/edge_lines.geojson": {
|
||||
"contentHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"orderHash": "ad37fe6278e0c7caf3b77c1c5068a56e18a6c87b78ed5a840ee22a0f961ea7a8",
|
||||
"features": 0,
|
||||
"bytes": 52
|
||||
},
|
||||
"layers/intersection_surface.geojson": {
|
||||
"contentHash": "7f79e57215e3fd07c5c0f703c71e516873db770cb869efdf18925a3184059ca3",
|
||||
"orderHash": "d69df8d92d0fce8f7ee172959b978d941fd011e094857e9ab7304b3d1e98bf92",
|
||||
"features": 12,
|
||||
"bytes": 38528
|
||||
},
|
||||
"layers/lane_centerlines.geojson": {
|
||||
"contentHash": "3fea4ebfe62e0d720c6977e2289f4e6517ed7b3c5ab361270d03378971aec780",
|
||||
"orderHash": "69a8ebaa42a74da4f7d5940c5a81bd82ceb104efd3f858723dccfadbae12121e",
|
||||
"features": 50,
|
||||
"bytes": 43582
|
||||
},
|
||||
"layers/lane_separators.geojson": {
|
||||
"contentHash": "dc0b50b53da191e65f3dba01162acb7561966d1796a9c0789527cebfd6ed06a4",
|
||||
"orderHash": "0bfd873e5698869fb4855783fe30bb6db29456958e4d154b7264374a5fdb4a10",
|
||||
"features": 731,
|
||||
"bytes": 835933
|
||||
},
|
||||
"layers/road_surface.geojson": {
|
||||
"contentHash": "81af19c71cf6fc8a3884488c00cfb590e1a7fe9ed9e4351fce65825ceadd50fa",
|
||||
"orderHash": "f2ddec5258411fbdbc050b7ecefbb157b59e00b8226afcc30d2b772e1859e357",
|
||||
"features": 25,
|
||||
"bytes": 38275
|
||||
},
|
||||
"layers/sidewalk_surface.geojson": {
|
||||
"contentHash": "bb8f4516e27b16238d753b112d18b8acfa5cfad2cea522a65b7972a609be9563",
|
||||
"orderHash": "4fa773a6f39c5b6d23551e1338c6d99ef8a5ecd9ebfcafa16154c9765fef8c6e",
|
||||
"features": 73,
|
||||
"bytes": 122432
|
||||
},
|
||||
"layers/turn_arrows.geojson": {
|
||||
"contentHash": "a65bb8fe079f27e00bf2e6208148fc6b73d2d07e76d48b1cd1bf8a610d7e2d66",
|
||||
"orderHash": "66966ac2c65464f9c6715ca9cda5a3620520bf1d066cba2dee36114dc46af936",
|
||||
"features": 108,
|
||||
"bytes": 132536
|
||||
},
|
||||
"layers/vehicle_stop_lines.geojson": {
|
||||
"contentHash": "4f45caf3c0bf13bbf10d9cac15fa80b1a9be3969ed84803ba48e56c4a0080992",
|
||||
"orderHash": "4f45caf3c0bf13bbf10d9cac15fa80b1a9be3969ed84803ba48e56c4a0080992",
|
||||
"features": 8,
|
||||
"bytes": 8475
|
||||
},
|
||||
"traffic-signal-assemblies.json": {
|
||||
"contentHash": "9f8d032c00784bb72bb1322238cd7b0a80e621d435b43c728da86f1e77ca352a",
|
||||
"orderHash": "0b9cae13094dba1994faca4bf1d4d80e8bc5497daa46df5899bf5e6fc7f26d61",
|
||||
"features": 35,
|
||||
"bytes": 26398
|
||||
},
|
||||
"traffic-signals.json": {
|
||||
"contentHash": "c4b68cc1cefeb0d393a18eef6c7eb79933fba28630826745165ed433c25779d0",
|
||||
"bytes": 74151
|
||||
}
|
||||
},
|
||||
"volatileExcluded": [
|
||||
"absolute paths -> <repo> or <external>",
|
||||
"native-road-* staging directory -> <staging>"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
294
.trellis/tasks/08-25-road-compiler-extraction/design.md
Normal file
294
.trellis/tasks/08-25-road-compiler-extraction/design.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# 道路编译器独立化 — 技术设计(parent)
|
||||
|
||||
本文件是 **契约的权威定义**,供所有子任务引用。
|
||||
Phase 0 的交付物是把本节内容落成编译器仓库内的正式文档 + 校验脚本,
|
||||
而不是重新发明契约。
|
||||
|
||||
---
|
||||
|
||||
## 1. 契约:`native-road-package/v1`
|
||||
|
||||
### 1.1 输入契约
|
||||
|
||||
现状:`compile-native-roads.js` 接收整个 normalized areaConfig,但**实际只用 9 个字段**。
|
||||
|
||||
```
|
||||
area.id → areaId
|
||||
area.input → osmFile (OSM XML 路径)
|
||||
area.nativeRoad.edgeLines → options.edgeLines
|
||||
area.nativeRoad.junctionTemplates → options.junctionTemplates
|
||||
area.outputs.nativeRoadOverrides → overridesFile
|
||||
area.outputs.nativeTrafficSignals → trafficSignalsFile
|
||||
area.outputs.nativeRoadDir → outDir
|
||||
area.outputs.pipelineDir → stagingDir
|
||||
area.outputs.geojsonDir → comparisonDir (见 K2,倾向移除)
|
||||
```
|
||||
|
||||
目标形状:
|
||||
|
||||
```js
|
||||
// RoadCompilerInput —— 编译器唯一入口参数
|
||||
{
|
||||
areaId: string,
|
||||
osmFile: string, // 绝对路径
|
||||
outDir: string, // native-road/ 的目标位置
|
||||
stagingDir: string, // 原子提升用的临时目录父级
|
||||
overridesFile: string,
|
||||
trafficSignalsFile: string,
|
||||
options: {
|
||||
edgeLines: boolean,
|
||||
junctionTemplates: {
|
||||
enabled: boolean,
|
||||
references: [],
|
||||
clusters: [{ id, template, referenceFile, nodeIds, ...几何参数 }]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**宿主侧责任**:`scripts/lib/area-config.js` 把 areaConfig 映射成 `RoadCompilerInput`。
|
||||
**编译器侧责任**:不认识 areaConfig,不读 `config/areas/*.json`,不推导任何路径。
|
||||
|
||||
### 1.2 输出契约
|
||||
|
||||
`compiled.json` 顶层键(已核实):
|
||||
|
||||
```
|
||||
schema, areaId, source, model, movements, trafficSignals, diagnostics, layers
|
||||
```
|
||||
|
||||
`source` 含 `{ osm, overrides, trafficSignals }` 三个路径 —— 是 parity 归一化的重点对象。
|
||||
|
||||
```
|
||||
<outDir>/ # 原子提升:先写 staging 再 rename
|
||||
manifest.json # ← Phase 3 新增,图层自声明
|
||||
compiled.json # 单一读模型,含 model + movements
|
||||
diagnostics.json # severity / subject / sourceIds / rule / message / geometry?
|
||||
comparison.json # ← 见 K2,可能移除
|
||||
layers/
|
||||
road_surface.geojson
|
||||
intersection_surface.geojson
|
||||
sidewalk_surface.geojson
|
||||
edge_lines.geojson
|
||||
lane_separators.geojson
|
||||
center_lines.geojson
|
||||
crosswalks.geojson
|
||||
vehicle_stop_lines.geojson
|
||||
direction_arrows.geojson
|
||||
turn_arrows.geojson
|
||||
lane_centerlines.geojson # 语义层,不渲染
|
||||
connectors.geojson # 语义层,不渲染
|
||||
|
||||
<trafficSignalsFile> # 兄弟文件,不在 outDir 内
|
||||
```
|
||||
|
||||
**stdout 完成标记**(与宿主 `SCENE_DONE` / `CESIUM_EXPORT_DONE` 同约定):
|
||||
|
||||
```
|
||||
NATIVE_ROAD_COMPILE_DONE {"areaId":…,"roads":N,"endpoints":N,"diagnostics":N,"output":…,"comparison":…}
|
||||
```
|
||||
|
||||
### 1.3 消费方式:子进程为主契约
|
||||
|
||||
| 方式 | 定位 |
|
||||
|---|---|
|
||||
| CLI 子进程 + 读 `outDir` + 解析 stdout 标记 | **主契约**。宿主 pipeline 层本就是唯一能启动外部进程的层 |
|
||||
| `require()` in-process | 仅作性能优化,不得成为唯一路径 |
|
||||
|
||||
选子进程的理由:
|
||||
1. 语言无关 —— 编译器将来若换 TS/Rust,宿主零改动。
|
||||
2. 强制文件契约成为真契约,无法偷传对象绕过边界。
|
||||
3. 与既有 QGIS / GDAL / Blender 调用方式一致。
|
||||
|
||||
`build-area.js:7` 现在是 in-process `require`。Phase 2 改为子进程调用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 模块清单:什么搬、什么留
|
||||
|
||||
### 2.1 搬(约 3400 行核心 + workbench)
|
||||
|
||||
| 文件 | 行数 | 依赖 |
|
||||
|---|---|---|
|
||||
| `scripts/lib/native-road.js` | 1695 | fs, path, turn-lane-arrows, complex-junction |
|
||||
| `scripts/lib/complex-junction.js` | 474 | fs, gaode-junction-reference |
|
||||
| `scripts/lib/turn-lane-arrows.js` | 502 | fs, path, lane-geometry |
|
||||
| `scripts/lib/gaode-junction-reference.js` | 231 | fs |
|
||||
| `scripts/lib/lane-geometry.js` | 161 | 无(纯函数) |
|
||||
| `scripts/lib/native-traffic-signals.js` | 49 | osm, traffic-signals ← **见 K1** |
|
||||
| `scripts/lib/osm.js` | 102 | 无 |
|
||||
| `scripts/compile-native-roads.js` | — | area-config ← **要换成窄契约** |
|
||||
| `scripts/check-native-roads.js` | — | area-config ← 同上 |
|
||||
| `scripts/road-workbench.js` + `scripts/workbench/app.js` | 173 + — | area-config ← 同上 |
|
||||
| `scripts/test-native-road.js`、`scripts/test-road-workbench.js` | — | fixture ← **见 K5** |
|
||||
|
||||
依赖图(已核实,`native-road.js` 对宿主零耦合):
|
||||
|
||||
```
|
||||
native-road.js ──→ turn-lane-arrows ──→ lane-geometry (纯)
|
||||
└──────────→ complex-junction ──→ gaode-junction-reference
|
||||
native-traffic-signals ──→ osm.js
|
||||
└──→ traffic-signals.js ← 共用,需拆
|
||||
```
|
||||
|
||||
### 2.2 留
|
||||
|
||||
- `scripts/lib/area-config.js` —— 宿主拥有,新增 `toRoadCompilerInput()` 映射
|
||||
- `scripts/build-area.js` —— 改为子进程调用编译器
|
||||
- `scripts/lib/traffic-signals.js` 的 legacy 读取器部分(见 K1)
|
||||
- `blender/` 全部 —— Phase 3 内部重组,但不搬出仓库
|
||||
- `scripts/lib/scene-layers.js`、osm2streets / QGIS legacy 链路 —— 完全不动
|
||||
|
||||
### 2.3 K1 的拆分建议(Phase 1 需细读确认)
|
||||
|
||||
`lib/traffic-signals.js` 当前混了两类东西:
|
||||
|
||||
| 类别 | 使用方 | 归属 |
|
||||
|---|---|---|
|
||||
| OSM 信号节点提取 + 信号文档 schema | `native-traffic-signals.js` | **随编译器走** —— 编译器生成该文档,就该拥有其契约 |
|
||||
| `readTrafficSignalFeatures` | `build-osm2streets-qgis.js` | 留宿主 |
|
||||
| `readTrafficSignals` | `build-area.js` | 留宿主 |
|
||||
| `buildTrafficSignals` | `test-preview-assets.js` | 待判定 |
|
||||
|
||||
拆完后宿主从编译器包 import 信号文档 schema,反向依赖为 0 不受影响
|
||||
(宿主依赖编译器是允许的方向)。
|
||||
|
||||
---
|
||||
|
||||
## 3. Phase 3:渲染分离的设计
|
||||
|
||||
### 3.1 现状问题
|
||||
|
||||
`blender/osmassets/catalog.py:53` 的 `NATIVE_ROAD_LAYERS` 是一张跨仓库重复表:
|
||||
|
||||
```python
|
||||
# 注释自陈:"It is a provider adapter, not a second scene-layer registry."
|
||||
NATIVE_ROAD_LAYERS = (
|
||||
{"source": "road_surface", "material_layer": "road_surface"},
|
||||
{"source": "edge_lines", "material_layer": "lane_separators"},
|
||||
... 共 10 项
|
||||
)
|
||||
```
|
||||
|
||||
编译器写 12 个 geojson,这张表只列 10 项 —— `lane_centerlines` / `connectors`
|
||||
是语义层不参与渲染。**但这个事实只存在于这张表的省略里,编译器侧没有任何声明。**
|
||||
|
||||
后果:编译器新增图层 → 必须有人记得去另一个仓库改 `catalog.py` → 忘了就静默少渲染一层。
|
||||
这正是 `.trellis/spec/pipeline/index.md` 首页警告的「最容易出静默错误」。
|
||||
|
||||
### 3.2 目标:编译器自声明图层
|
||||
|
||||
`<outDir>/manifest.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"contract": "native-road-package/v1",
|
||||
"areaId": "fengshu-er-road",
|
||||
"layers": [
|
||||
{ "source": "road_surface", "role": "surface",
|
||||
"materialLayer": "road_surface" },
|
||||
|
||||
{ "source": "lane_centerlines", "role": "semantic" },
|
||||
{ "source": "connectors", "role": "semantic" },
|
||||
|
||||
{ "source": "center_lines", "role": "marking",
|
||||
"materialLayer": "center_lines",
|
||||
"splitBy": { "prop": "color",
|
||||
"cases": [ { "match": "white", "material": "native_center_line_white" },
|
||||
{ "default": true, "material": "center_lines" } ] } },
|
||||
|
||||
{ "source": "lane_separators", "role": "marking",
|
||||
"materialLayer": "lane_separators",
|
||||
"splitBy": { "prop": "color",
|
||||
"cases": [ { "match": "yellow", "material": "native_lane_separator_yellow" },
|
||||
{ "default": true, "material": "lane_separators" } ] } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `role: "semantic"` 的图层 Blender 直接跳过 —— 把"不渲染"从省略变成显式声明。
|
||||
- `splitBy` 表达当前 `generate_scene.py:815-819` 里硬编码的
|
||||
`color != "white"` / `color == "yellow"` 分流逻辑。
|
||||
- 材质本体(颜色、z 高度、贴图)仍归宿主 `catalog.py::MATERIALS` —— 编译器不懂渲染。
|
||||
编译器只声明"我有这些图层、它们是什么角色、该用哪个材质槽"。
|
||||
|
||||
### 3.3 Blender 侧改造
|
||||
|
||||
- `generate_scene.py` 内道路分支抽为 `blender/osmassets/native_roads.py`,读 manifest 遍历。
|
||||
- 建筑(`handle_building` / `dispatch_ways`)、植被、水体保持原位不动。
|
||||
- `catalog.NATIVE_ROAD_LAYERS` 删除。
|
||||
|
||||
### 3.4 AC5 的验证方法
|
||||
|
||||
向编译器加一个 throwaway 图层(例如 `debug_probe.geojson` + manifest 声明),
|
||||
不改宿主任何代码,跑 blender 阶段,确认它被渲染出来。验证完回滚该图层。
|
||||
|
||||
---
|
||||
|
||||
## 4. 决策记录
|
||||
|
||||
### D1 不用 drawtonomy 替代编译器 ❌
|
||||
|
||||
评估结论(2026-08-25):
|
||||
|
||||
- 克隆仓库内**没有编辑器本体**。`packages/` 只有 SDK / dev-server / mcp-server;
|
||||
白板闭源,托管在 drawtonomy.com。README 卖点里的 topology-aware lanes、
|
||||
lane tool、intersection/roundabout templates、Map→lanes **全部不在开源代码内**。
|
||||
- **它完全不做 raw OSM 推导**。`exporter/osmParser.ts` 首行自陈是
|
||||
"Lanelet2 OSM (.osm XML) parser" —— 读的是 Lanelet2(车道左右边界已显式)。
|
||||
整个 `exporter/` 目录 grep `highway` 命中 1 次,是 `opendrive.ts:685` 的
|
||||
车道类型字符串,不是 OSM 标签解析。从不读 `highway=*` / `lanes` / `turn:lanes`。
|
||||
- 本编译器的核心能力恰是"从稀疏有歧义的中国 OSM 标签推导车道几何",方向垂直。
|
||||
- 语义成熟度对比:本编译器有逐值 provenance
|
||||
(`tag:lanes:forward` / `inferred:highway-default` / `override:<id>`)+ 31 条语义诊断规则;
|
||||
drawtonomy SDK 两者皆无,其 25 条校验规则全是 OpenDRIVE 结构/XML 完整性检查。
|
||||
|
||||
### D2 drawtonomy 作为**下游后端 + 编辑器**(后续独立 PoC)✅
|
||||
|
||||
可用能力(扩展 API,`types.ts:257` 8 个 capability):
|
||||
`shapes:write` + `ui:panel` 足以把编译产物注入编辑器;
|
||||
`snapshot:read` 回读后由本地跑其开源 `exportToOpenDrive` / `lanelet2`。
|
||||
|
||||
不可用能力(决定 junctionTools 不能做成扩展):
|
||||
- 无 canvas / overlay 能力,UI 只能是侧栏 iframe
|
||||
- 无工具注册,画布指针事件完全归宿主
|
||||
- **无任何 change 事件推送** —— `ExtensionClient.handleMessage` 入站只有
|
||||
`ext:init` / 按 requestId 匹配的 5 个 `*-response` / `ext:error`,所有读取靠轮询
|
||||
|
||||
结论:junctionTools 留在自有 workbench(画布自己的,可任意绘制)。
|
||||
drawtonomy 承担场景编排 + 工业格式导出。二者是不同的活,不强行合并 UI。
|
||||
|
||||
依赖风险记录:`drawtonomy-dev-server` 是 `https://www.drawtonomy.com` 的
|
||||
**缓存代理**(TTL 1 小时),非自托管。manifest 有 `minHostVersion` 字段,
|
||||
说明宿主协议会漂。后续 PoC 不把它放进关键路径。
|
||||
|
||||
### D3 值得从 drawtonomy 借用的(Apache-2.0,需保留 NOTICE/署名)
|
||||
|
||||
| 来源 | 行数 | 用途 | 建议阶段 |
|
||||
|---|---|---|---|
|
||||
| `exporter/odrGeometryFit.ts` | 610 | 折线→解析曲线拟合(中位数去噪 + 贪心生长 + 最简原语优先 + 逐拟合回验 + G1 硬不变量)。可替掉手调的 `approachWidthMultiplier=1.45` / `coreRadiusMeters=28`,改为对 `referenceFile` 拟合、残差作质量指标 | 拆分后独立任务 |
|
||||
| validator 的 mutation-proven 方法 | — | 故意破坏合法输入、断言校验器必须抓到。本编译器 31 条诊断规则目前无任何触发证明 | 拆分后独立任务,成本低 |
|
||||
| validator 的分层 + 命名空间(`xml.*` → `ref.*` → `junction.*` → `geom.*`) | — | 替代当前 31 条平铺规则 | 同上 |
|
||||
| OpenDRIVE + Lanelet2 导出器 | 3059 + 869 | 补齐工业格式输出(Lanelet2 是 Autoware 的输入格式) | 后续独立 PoC |
|
||||
|
||||
### D4 为什么 Phase 1 与 Phase 2 必须分开
|
||||
|
||||
Phase 1 只改"包边界与入口契约",仓库不变 → 若产物变化,成因必在代码改动。
|
||||
Phase 2 只改"仓库位置与消费方式",代码不变 → 若产物变化,成因必在搬迁。
|
||||
合并执行则两者混淆,parity oracle 失去诊断价值。
|
||||
|
||||
### D5 IR 重构推迟到拆分之后
|
||||
|
||||
见 prd.md C1。拆分的正确性完全建立在"产物逐字节不变"上,
|
||||
同期改 IR 会同时摧毁 oracle 与归因能力。
|
||||
|
||||
---
|
||||
|
||||
## 5. 回滚形状
|
||||
|
||||
| Phase | 回滚方式 |
|
||||
|---|---|
|
||||
| 0 | 无代码改动,仅新增文档与基线,无需回滚 |
|
||||
| 1 | `git revert`;`packages/road-compiler/` 与旧 `scripts/lib/*` 并存过渡期内可切回旧路径 |
|
||||
| 2 | 宿主依赖回指本仓库内路径(`file:packages/road-compiler`),编译器仓库保留不动 |
|
||||
| 3 | Blender 侧恢复 `catalog.NATIVE_ROAD_LAYERS`,manifest 保留但不消费 |
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
139
.trellis/tasks/08-25-road-compiler-extraction/prd.md
Normal file
139
.trellis/tasks/08-25-road-compiler-extraction/prd.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 道路编译器独立化(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);它不属于本任务完成条件。
|
||||
34
.trellis/tasks/08-25-road-compiler-extraction/task.json
Normal file
34
.trellis/tasks/08-25-road-compiler-extraction/task.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"id": "road-compiler-extraction",
|
||||
"name": "road-compiler-extraction",
|
||||
"title": "道路编译器独立化(parent)",
|
||||
"description": "把 native road compiler 拆成可独立维护的项目,并与本项目通过版本化契约相辅相成;同时分离道路与 OSM 建筑渲染",
|
||||
"status": "planning",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P1",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-25",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [
|
||||
"08-25-rc-p0-contract-baseline",
|
||||
"08-25-rc-p1-package-boundary",
|
||||
"08-25-rc-p2-repo-split",
|
||||
"08-25-rc-p3-render-separation",
|
||||
"08-26-rc-p5-artifact-consumption"
|
||||
],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "P3 已完成。P5 artifact-only parent 已建立,P5A ZIP export 已在 road-compiler v0.3.0 完成;P5B 宿主 ZIP import 待实施。",
|
||||
"meta": {
|
||||
"next_task": "08-25-rc-p4-drawtonomy-ext"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"file":".trellis/spec/blender/testing.md","reason":"P3 scene digest parity 是消费迁移的回归门槛。"}
|
||||
{"file":".trellis/spec/pipeline/native-road-package.md","reason":"验证 ZIP 解压后的 v1.1 contract、areaId 和 source 集合。"}
|
||||
{"file":".trellis/spec/guides/artifact-parity-guide.md","reason":"验证导入迁移不改变 Blender 产物结构。"}
|
||||
23
.trellis/tasks/08-26-rc-p5-artifact-consumption/design.md
Normal file
23
.trellis/tasks/08-26-rc-p5-artifact-consumption/design.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# P5 Artifact-Only Road Package Design
|
||||
|
||||
P5 replaces the current runtime dependency boundary with a file-only boundary:
|
||||
|
||||
```text
|
||||
road-compiler Web editor
|
||||
-> deterministic root-flat native-road-package/v1.1 ZIP
|
||||
-> osmWorkflow config nativeRoadPackage
|
||||
-> private verified extraction cache
|
||||
-> Blender / Cesium / preview
|
||||
```
|
||||
|
||||
The road-compiler repository owns export and all editing/compiler internals. osmWorkflow owns ZIP safety,
|
||||
contract validation, Blender materials and downstream scene construction. Neither repository reads the
|
||||
other's source, configuration, overrides or runtime implementation.
|
||||
|
||||
The ZIP is immutable input and the host's `_pipeline/native-road-import/<sha256>` is disposable cache.
|
||||
The host rejects unsafe archive topology before writing files, then validates v1.1 `manifest.json` and
|
||||
area identity before starting Blender. Stage manifests record the ZIP hash and resolved package records.
|
||||
|
||||
P5A publishes a fixture/exporter first. P5B uses that fixture to remove compiler CLI installation and
|
||||
invocation from the host. The migration is intentionally a contract-preserving transport change: rendered
|
||||
geometry and material behavior must remain at the P3 baseline.
|
||||
@@ -0,0 +1,3 @@
|
||||
{"file":".trellis/spec/pipeline/native-road-package.md","reason":"宿主与编译器之间的现有文件契约。"}
|
||||
{"file":".trellis/spec/pipeline/external-tools.md","reason":"ZIP 导入必须在 Blender 副作用前完成安全校验。"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"导出、Node 导入、Blender 和 preview 的跨层数据流。"}
|
||||
14
.trellis/tasks/08-26-rc-p5-artifact-consumption/implement.md
Normal file
14
.trellis/tasks/08-26-rc-p5-artifact-consumption/implement.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# P5 Integration Plan
|
||||
|
||||
1. Complete P5A in `road-compiler`: deterministic Web/CLI ZIP export plus supported-area fixtures and a
|
||||
tagged compiler release.
|
||||
2. Complete P5B in `osmWorkflow`: safe importer, config migration, CLI/dependency removal and downstream
|
||||
descriptor migration.
|
||||
3. Run the shared end-to-end gate on `fengshu-er-road` and `nantaizi-lake-innovation-valley`:
|
||||
import ZIP -> Blender -> Cesium -> package -> preview -> area checks.
|
||||
4. Compare Blender scene digests to P3 control; run negative ZIP tests and legacy provider tests.
|
||||
5. Record contract docs in both repositories, commit each repository independently, then archive both
|
||||
children and this integration parent.
|
||||
|
||||
Integration rollback: restore the host's previous tagged compiler dependency and compile adapter. ZIP export
|
||||
is additive and its generated files remain valid standalone artifacts.
|
||||
95
.trellis/tasks/08-26-rc-p5-artifact-consumption/prd.md
Normal file
95
.trellis/tasks/08-26-rc-p5-artifact-consumption/prd.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# 道路包导入与纯消费管线
|
||||
|
||||
父任务:`.trellis/tasks/08-25-road-compiler-extraction/`
|
||||
|
||||
本任务是集成 parent:导出与导入分别在 road-compiler 和宿主仓库实现,只有两个子任务的
|
||||
端到端 ZIP 契约都通过后才完成。
|
||||
|
||||
## Goal
|
||||
|
||||
让 road-compiler Web 导出的 `native-road-package/v1.1` 成为 Blender 管线的唯一
|
||||
native-road 输入。宿主只校验并消费完整道路包,不再安装、调用或理解道路编译器。
|
||||
|
||||
用户价值:人工在 road-compiler Web 编辑后,导出道路包即可稳定交给 Blender / Cesium
|
||||
构建;编辑成果不再通过发布新的编译器代码版本传播。
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- P3 已使 Blender 通过 `manifest.json` 消费 `layers/*.geojson`,并拒绝无效 manifest、
|
||||
source/file 不一致和未知宿主材质槽。
|
||||
- 宿主当前仍通过 `scripts/build-area.js` 调用 `scripts/lib/road-compiler-cli.js`;后者安装并
|
||||
运行 `@osm-asset/road-compiler`,将 `RoadCompilerInput` 写入 `_pipeline/`。
|
||||
- native Blender 构建当前强制使用 `outputs/<area>/native-road/`,并在每次构建前重新编译;
|
||||
宿主还持有 `nativeRoad.edgeLines`、`junctionTemplates`、overrides 和 compiler runtime
|
||||
路径等编译器内部概念。
|
||||
- `osm2streets` 是显式 legacy/debug provider,应保持现有行为。
|
||||
- 有效验证区域只有 `fengshu-er-road` 与 `nantaizi-lake-innovation-valley`;
|
||||
`hanyang-block` 不参与。
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1 纯产物边界
|
||||
|
||||
- native provider 只接收一个完整 `native-road-package/v1.1` 目录。
|
||||
- 宿主不得依赖 `@osm-asset/road-compiler`,不得调用 compiler CLI,不得写
|
||||
`RoadCompilerInput`,不得读取或解释 compiler overrides / junction templates。
|
||||
- 宿主以 manifest 作为道路图层事实源,Blender 不增加 compiler 专用图层表。
|
||||
|
||||
### R2 ZIP 导入配置和校验
|
||||
|
||||
- 每个 native 区域配置一个道路包 ZIP 路径;路径相对于区域配置文件解析并可指向工作区外的
|
||||
road-compiler Web 导出文件。
|
||||
- ZIP 根目录必须直接包含 `manifest.json` 和契约定义的文件;不得接受多余顶层目录、绝对路径、
|
||||
`..` 路径或符号链接。
|
||||
- 在启动 Blender 前,宿主将 ZIP 安全解压到其私有 pipeline staging 目录,校验 manifest
|
||||
契约版本、`areaId` 和当前区域一致,并记录 ZIP hash 和展开文件记录到 stage manifest。
|
||||
- ZIP 永远只读:宿主不复制、覆盖或修改 road-compiler Web 的导出文件;成功或失败后仅清理
|
||||
自己的临时解压目录。
|
||||
- Blender 继续校验 manifest 的图层、source、semantic 角色、材质槽和 splitBy 规则。
|
||||
|
||||
### R3 后续阶段
|
||||
|
||||
- Cesium、package、preview 和区域诊断从导入道路包读取原生车道中心线、signals 和
|
||||
其他既有 native-road 输入;不重新编译或复制道路包。
|
||||
- build stage manifest 将道路包作为外部输入记录,而非宿主输出。
|
||||
|
||||
### R4 清理和迁移
|
||||
|
||||
- 删除宿主对 compiler npm 包、CLI 封装和编译脚本的生产依赖。
|
||||
- 删除或替换只测试宿主编译行为的测试;保留 package-contract、Blender 和下游消费测试。
|
||||
- 更新示例配置、README、pipeline spec 和道路包契约,明确 Web export -> host consume
|
||||
的单向关系。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] AC1 `package.json` / lockfile 和生产脚本不再引用 `@osm-asset/road-compiler` 或其 CLI。
|
||||
- [ ] AC2 两个有效区域均可从配置指定的已导出 ZIP 道路包运行 Blender、Cesium、package 和 preview,
|
||||
构建期间没有 compiler 子进程。
|
||||
- [ ] AC3 宿主在 Blender 前拒绝缺失包、错误 contract、错误 areaId 和无效 manifest;错误说明
|
||||
指向导入包和契约,而非 compiler 内部。
|
||||
- [ ] AC4 Blender scene digest 与 P3 control 基线一致;native semantic 层仍不生成几何。
|
||||
- [ ] AC5 stage manifest / diagnose / check area 将导入道路包记录为输入并能识别缺失或变更。
|
||||
- [ ] AC6 `osm2streets` legacy provider 行为未变。
|
||||
- [ ] AC7 文档和配置将人工编辑传播路径表述为 road-compiler Web export -> host consume。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- road-compiler Web 的编辑器功能、认证、部署或 UI 改造。
|
||||
- 修改道路几何、材质、Blender 建筑/植被/水体渲染。
|
||||
- 新的 manifest 破坏性版本或 lane graph IR。
|
||||
- 将道路包上传到远程制品库或实现同步服务。
|
||||
|
||||
## Key Decision
|
||||
|
||||
- Web 导出的是标准 ZIP;宿主配置直接指向 ZIP 而不是目录。ZIP 是跨仓库的不可变交付物,
|
||||
宿主只在私有 staging 目录中解压和消费,因而没有导出目录 ownership 或覆盖歧义。
|
||||
|
||||
## Subtask Map
|
||||
|
||||
| 子任务 | 仓库 | 交付物 | 依赖 |
|
||||
|---|---|---|---|
|
||||
| `rc-p5a-zip-export` | road-compiler | Web / CLI 可导出的 root-flat `native-road-package/v1.1` ZIP | 无 |
|
||||
| `rc-p5b-zip-import` | osmWorkflow | 配置、受控解压、校验和纯消费管线 | p5a 的 ZIP fixture |
|
||||
|
||||
跨任务验收:同一个 ZIP fixture 必须被 p5b 的两个有效区域构建消费;宿主不得保留
|
||||
compiler npm 或 CLI 耦合。
|
||||
29
.trellis/tasks/08-26-rc-p5-artifact-consumption/task.json
Normal file
29
.trellis/tasks/08-26-rc-p5-artifact-consumption/task.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"id": "rc-p5-artifact-consumption",
|
||||
"name": "rc-p5-artifact-consumption",
|
||||
"title": "道路包导入与纯消费管线",
|
||||
"description": "让宿主通过配置指向 road-compiler Web 导出的 ZIP 道路包,只校验和消费该版本化文件契约,不再安装或调用编译器。",
|
||||
"status": "planning",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-26",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "experiment/road-compiler-rethink",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [
|
||||
"08-26-rc-p5b-zip-import",
|
||||
"08-26-rc-p5a-zip-export"
|
||||
],
|
||||
"parent": "08-25-road-compiler-extraction",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
1
.trellis/tasks/08-26-rc-p5a-zip-export/check.jsonl
Normal file
1
.trellis/tasks/08-26-rc-p5a-zip-export/check.jsonl
Normal file
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
22
.trellis/tasks/08-26-rc-p5a-zip-export/design.md
Normal file
22
.trellis/tasks/08-26-rc-p5a-zip-export/design.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# P5A ZIP Export Design
|
||||
|
||||
The compiler owns a pure `exportNativeRoadPackage(result, destination)` function. It writes a
|
||||
temporary root-flat directory from the already compiled Web/CLI state, validates it with the same
|
||||
manifest/source validator used by compiler output, then writes a deterministic ZIP. The Web download
|
||||
handler and CLI are thin callers of this function.
|
||||
|
||||
ZIP layout:
|
||||
|
||||
```text
|
||||
manifest.json
|
||||
compiled.json
|
||||
diagnostics.json
|
||||
comparison.json
|
||||
traffic-signal-assemblies.json
|
||||
traffic-signals.json
|
||||
layers/<12 declared sources>.geojson
|
||||
```
|
||||
|
||||
`manifest.generator` is informational only. The host accepts solely the `contract`, `areaId`, and
|
||||
declared files. ZIP creation normalizes entry order and timestamps so the export is hash-stable.
|
||||
Failure never replaces a chosen output file.
|
||||
1
.trellis/tasks/08-26-rc-p5a-zip-export/implement.jsonl
Normal file
1
.trellis/tasks/08-26-rc-p5a-zip-export/implement.jsonl
Normal file
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
10
.trellis/tasks/08-26-rc-p5a-zip-export/implement.md
Normal file
10
.trellis/tasks/08-26-rc-p5a-zip-export/implement.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# P5A Execution Plan
|
||||
|
||||
1. Inspect workbench state ownership and compiler output writer; keep export sourced from the current
|
||||
compiled state, not host paths.
|
||||
2. Add deterministic root-flat ZIP writer plus validation and CLI entry point.
|
||||
3. Add Web download action using the same writer.
|
||||
4. Add fixtures for root layout, content determinism, manifest/source parity and edited-state export.
|
||||
5. Export both supported-area fixtures for P5B, run compiler tests, then publish a compiler release.
|
||||
|
||||
Rollback: remove the export entry point; no host behavior changes in this subtask.
|
||||
34
.trellis/tasks/08-26-rc-p5a-zip-export/prd.md
Normal file
34
.trellis/tasks/08-26-rc-p5a-zip-export/prd.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Web 导出原生道路 ZIP 包
|
||||
|
||||
## Goal
|
||||
|
||||
让 road-compiler Web/CLI 以 root-flat native-road-package/v1.1 ZIP 导出人工编辑后的道路包。
|
||||
|
||||
## Requirements
|
||||
|
||||
- 从 Web 当前编辑状态导出完整道路包,而不是源代码、npm 包或仅 overrides。
|
||||
- ZIP 根直接包含 `manifest.json`,并包含全部 manifest 声明的 `layers/<source>.geojson`、
|
||||
`compiled.json`、`traffic-signals.json` 和 preview 所需的 signal / diagnostics 文档。
|
||||
- `manifest.json` 的 `contract` 保持 `native-road-package/v1.1`;新增可选 `generator` 元数据
|
||||
记录应用版本和 revision,但不得让宿主依赖该版本。
|
||||
- Web 提供用户可见的下载操作;CLI 提供等价、可测试的 ZIP 导出入口。
|
||||
- ZIP 条目顺序、JSON 序列化和压缩时间戳必须确定性,重复导出未编辑内容的文件 hash 一致。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] 从 Web 编辑后的区域可下载一个 root-flat ZIP,且无需宿主或 npm 发布。
|
||||
- [x] CLI fixture 导出 ZIP 后,解压内容通过 compiler 的 `validatePublishedLayers()`。
|
||||
- [x] ZIP 具有恰好一份 manifest 声明的 layer source,无额外 compiler source / overrides 文件。
|
||||
- [ ] 两个有效区域导出的 ZIP 可被 p5b 导入并完成 Blender/Cesium/preview 构建。
|
||||
- [x] 不修改 `compiled.json` 与 v1.1 manifest 的既有渲染语义。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 远程存储、上传、项目历史或多用户协作。
|
||||
- 修改道路编辑器交互、几何算法或 manifest 主版本。
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- ZIP 是单个标准交付物,根目录平铺 package 文件,不包裹 area-id 顶层目录。
|
||||
- ZIP 面向宿主消费,只携带已编译渲染/预览数据;编辑源、OSM 与 overrides 留在 road-compiler
|
||||
项目中,不泄露为宿主依赖。
|
||||
26
.trellis/tasks/08-26-rc-p5a-zip-export/task.json
Normal file
26
.trellis/tasks/08-26-rc-p5a-zip-export/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "rc-p5a-zip-export",
|
||||
"name": "rc-p5a-zip-export",
|
||||
"title": "Web 导出原生道路 ZIP 包",
|
||||
"description": "让 road-compiler Web/CLI 以 root-flat native-road-package/v1.1 ZIP 导出人工编辑后的道路包。",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-26",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "experiment/road-compiler-rethink",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-26-rc-p5-artifact-consumption",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
1
.trellis/tasks/08-26-rc-p5b-zip-import/check.jsonl
Normal file
1
.trellis/tasks/08-26-rc-p5b-zip-import/check.jsonl
Normal file
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
19
.trellis/tasks/08-26-rc-p5b-zip-import/design.md
Normal file
19
.trellis/tasks/08-26-rc-p5b-zip-import/design.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# P5B ZIP Import Design
|
||||
|
||||
`scripts/lib/native-road-package.js` becomes the single host adapter. It resolves the config-relative
|
||||
ZIP path, hashes it, validates central-directory entry names and metadata, and extracts only accepted
|
||||
regular files to `_pipeline/native-road-import/<sha256>/`. It validates the unpacked manifest and
|
||||
returns an immutable package descriptor:
|
||||
|
||||
```text
|
||||
{ zipFile, zipRecord, rootDir, manifest, records }
|
||||
```
|
||||
|
||||
The descriptor replaces `area.outputs.nativeRoadDir` as native input for Blender, native preview traffic,
|
||||
diagnostics and stage manifests. The configured ZIP and unpacked cache are inputs, never published area
|
||||
outputs. Cache invalidation is by ZIP SHA-256; cleanup only deletes importer-owned hash directories.
|
||||
|
||||
The importer rejects any non-root-flat layout, duplicate path, symlink, unsupported compression/error,
|
||||
path traversal, invalid manifest, area mismatch or declared/published mismatch before Blender starts.
|
||||
The native compiler CLI dependency and host-owned `nativeRoad` compilation options are removed. Legacy
|
||||
`osm2streets` remains an explicit separate provider.
|
||||
1
.trellis/tasks/08-26-rc-p5b-zip-import/implement.jsonl
Normal file
1
.trellis/tasks/08-26-rc-p5b-zip-import/implement.jsonl
Normal file
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
12
.trellis/tasks/08-26-rc-p5b-zip-import/implement.md
Normal file
12
.trellis/tasks/08-26-rc-p5b-zip-import/implement.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# P5B Execution Plan
|
||||
|
||||
1. Add config normalization for `nativeRoadPackage`, migrate two supported configs and the template.
|
||||
2. Add the tested ZIP importer with bounded extraction, root-flat/path/symlink validation, SHA cache and
|
||||
manifest/area validation.
|
||||
3. Route Blender, preview traffic, stage manifests and diagnostics through the imported descriptor.
|
||||
4. Delete compiler CLI/compile production paths and package dependency; replace their tests with ZIP fixture
|
||||
and negative importer tests.
|
||||
5. Update docs/specs, run native/legacy unit suites, build two exported packages, then run Blender parity.
|
||||
|
||||
Rollback: restore the old compiler dependency and native compile path in one revert; ZIP exports remain
|
||||
non-destructive standalone files.
|
||||
40
.trellis/tasks/08-26-rc-p5b-zip-import/prd.md
Normal file
40
.trellis/tasks/08-26-rc-p5b-zip-import/prd.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# ZIP 道路包安全导入与纯消费
|
||||
|
||||
## Goal
|
||||
|
||||
让 osmWorkflow 配置、校验、解压并消费 road-compiler Web 导出的 ZIP 道路包,移除 compiler runtime 耦合。
|
||||
|
||||
## Requirements
|
||||
|
||||
- 区域配置以 `nativeRoadPackage` 指向 ZIP,路径相对于该区域配置文件;native provider 不再
|
||||
接受 compiler options、overrides 或输出目录作为输入。
|
||||
- 宿主在任何 Blender / Cesium 副作用前,验证 ZIP 文件、条目数量/大小、root-flat 文件路径、
|
||||
无重复路径、无符号链接、无绝对路径与 `..` 路径。
|
||||
- 仅将已验证的文件解压至 `<area>/_pipeline/native-road-import/<zip-sha256>/`;解压目录是宿主
|
||||
私有缓存,绝不写回 ZIP 或 road-compiler 导出目录。
|
||||
- 共享 importer 为 Blender、preview traffic、diagnose/check 和 stage manifest 提供同一包路径与
|
||||
文件记录;不能让下游自行按字符串拼接另一份 native-road 目录。
|
||||
- 移除 compiler package、CLI wrapper、compile script、编译器输入配置、以及只覆盖旧调用链的测试。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 两个有效区域均从 ZIP 完成 `blender,cesium,package,preview`,构建日志没有 compiler CLI。
|
||||
- [ ] 缺 ZIP、错误 ZIP、nested root、path traversal、symbolic link、错误 contract、错误 areaId 和
|
||||
manifest/source 不匹配均在启动 Blender 前失败。
|
||||
- [ ] stage manifest、`diagnose:area` 与 `check:area` 记录 ZIP hash 和已验证的导入目录;ZIP
|
||||
修改后会被判 stale。
|
||||
- [ ] `package.json`、lockfile、生产脚本与宿主测试不再引用 `@osm-asset/road-compiler`。
|
||||
- [ ] Blender digest 对 P3 control 基线一致;legacy `osm2streets` 路径的单元测试不变。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- p5a 的 ZIP 生成与 Web 下载实现。
|
||||
- ZIP 远程下载、解密、签名验证或制品库管理。
|
||||
- 改道路内容、材质或 legacy QGIS 行为。
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- ZIP 是只读输入;宿主缓存的生命周期和目录 ownership 完全属于 `_pipeline/`。
|
||||
- ZIP 解码由明确声明的 Node ZIP 库完成,而不是依赖系统 `unzip`;这使路径/符号链接校验可控并
|
||||
可在单元测试中覆盖。
|
||||
- 只有 `native-road-package/v1.1` 可被导入;契约升级必须另开兼容任务。
|
||||
26
.trellis/tasks/08-26-rc-p5b-zip-import/task.json
Normal file
26
.trellis/tasks/08-26-rc-p5b-zip-import/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "rc-p5b-zip-import",
|
||||
"name": "rc-p5b-zip-import",
|
||||
"title": "ZIP 道路包安全导入与纯消费",
|
||||
"description": "让 osmWorkflow 配置、校验、解压并消费 road-compiler Web 导出的 ZIP 道路包,移除 compiler runtime 耦合。",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-26",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "experiment/road-compiler-rethink",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-26-rc-p5-artifact-consumption",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "native-road-compiler",
|
||||
"title": "Native road compiler workbench",
|
||||
"description": "",
|
||||
"status": "in_progress",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
@@ -11,7 +11,7 @@
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-13",
|
||||
"completedAt": null,
|
||||
"completedAt": "2026-08-18",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
@@ -19,7 +19,11 @@
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [
|
||||
"08-14-native-road-lane-markings"
|
||||
"08-14-native-road-lane-markings",
|
||||
"08-17-native-road-control-markings",
|
||||
"08-17-native-road-center-lines",
|
||||
"08-17-native-rounded-junctions",
|
||||
"08-18-native-traffic-signal-parity"
|
||||
],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,35 @@
|
||||
# Native Lane Separator Style Overrides Design
|
||||
|
||||
## Contract
|
||||
|
||||
The override is identified by the stable `roadId` plus the adjacent lane pair:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "车道分隔线:road:way/123:forward:1-2",
|
||||
"kind": "lane-separator-style",
|
||||
"roadId": "road:way/123:forward",
|
||||
"leftLaneIndex": 1,
|
||||
"rightLaneIndex": 2,
|
||||
"color": "white",
|
||||
"pattern": "dashed"
|
||||
}
|
||||
```
|
||||
|
||||
The same `yellow|white` and `dashed|solid` enums are reused. A default is
|
||||
white dashed. The generated Polygon stores `color`, `pattern` and
|
||||
`effective_style`; source IDs remain unchanged.
|
||||
|
||||
## Rendering
|
||||
|
||||
The existing lane separator polygon represents the whole lane-pair path.
|
||||
`solid` retains it as one continuous feature. `dashed` samples regular dash
|
||||
polygons along the shared centreline with deterministic spacing and preserves
|
||||
junction cutback. White uses the existing `lane_separators` material; yellow
|
||||
uses a native-only yellow material route. Workbench separates lane separators
|
||||
from turn arrows so their styles remain selectable and visible.
|
||||
|
||||
## Compatibility
|
||||
|
||||
No override means current visual default. Road edges, curbs and centre lines
|
||||
are untouched. Existing `native-road-overrides/v1` files remain valid.
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Validate and resolve per-lane-pair style overrides.
|
||||
2. Generate default/dashed/solid style geometry and effective properties.
|
||||
3. Add the native Blender yellow lane-separator material route.
|
||||
4. Add Workbench selection, Chinese editor and automatic staging.
|
||||
5. Test compiler, Workbench, Blender catalog and Nantaizi native build.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Native lane separator style overrides
|
||||
|
||||
## Goal
|
||||
|
||||
Make native same-direction lane separators selectable and persistently editable
|
||||
in Road Workbench, using the style override pattern proven for centre lines.
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- `compileLaneMarkings()` writes one continuous `0.12m` Polygon separator
|
||||
between each adjacent lane pair, identified by `road_id`, `left_lane_index`
|
||||
and `right_lane_index`.
|
||||
- Lane separators currently have fixed light marking material and are displayed
|
||||
together with turn arrows in Workbench.
|
||||
- `native-road-overrides.json` already supports validated, persistent centre
|
||||
line styles by logical native segment. The new type must not alter road-edge,
|
||||
curb or sidewalk geometry.
|
||||
|
||||
## Initial Requirements
|
||||
|
||||
- Selecting a lane separator must show its lane-pair source and a Chinese
|
||||
style editor.
|
||||
- The first style catalog should mirror centre lines: white/yellow and
|
||||
dashed/solid.
|
||||
- The effective style must be generated into GeoJSON, visible in Workbench,
|
||||
and consumed by native Blender/Cesium.
|
||||
- Default output must remain the current white dashed-style separator.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Road-edge lines, curbs, sidewalks, double lines, partial ranges, lane-specific
|
||||
legal restrictions and osm2streets layers.
|
||||
|
||||
## Key Decision
|
||||
|
||||
Each override applies only to the selected adjacent lane pair. This preserves
|
||||
separate marking semantics on roads with three or more lanes.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Selecting a separator exposes a Chinese style editor for its adjacent
|
||||
lane pair; choosing a style stages it automatically.
|
||||
- [ ] Save and regeneration preserve the per-pair style across reloads while
|
||||
unedited separators retain the default.
|
||||
- [ ] GeoJSON, Workbench, Blender and Cesium show the same effective style.
|
||||
- [ ] Tests cover validation, defaults, per-pair isolation, automatic staging
|
||||
and native scene consumption.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-lane-separator-styles",
|
||||
"name": "native-lane-separator-styles",
|
||||
"title": "Native lane separator style overrides",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,64 @@
|
||||
# Native Road Centre-Line Style Overrides Design
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
Workbench selects a generated centre-line dash
|
||||
-> segment_id identifies the logical road segment
|
||||
-> staged center-line-style override
|
||||
-> native-road-overrides.json
|
||||
-> compileCenterLines resolves default or effective style
|
||||
-> center_lines.geojson polygons with style fields
|
||||
-> Workbench / Blender / Cesium
|
||||
```
|
||||
|
||||
The authoritative edit is a new override kind, not a mutation of
|
||||
`center_lines.geojson`. It has a deterministic ID based on `segmentId`, so a
|
||||
later recompilation replaces the segment's style rather than accumulating
|
||||
records.
|
||||
|
||||
## Override Contract
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "道路中心线:segment:way/123/1",
|
||||
"kind": "center-line-style",
|
||||
"segmentId": "segment:way/123/1",
|
||||
"color": "yellow",
|
||||
"pattern": "dashed"
|
||||
}
|
||||
```
|
||||
|
||||
`validateOverrides()` accepts only known model segment IDs and the finite
|
||||
string enums `yellow|white` and `dashed|solid`. A style override applies once
|
||||
to the paired forward/backward native roads for that segment. The existing
|
||||
schema version remains `native-road-overrides/v1` because this is an additive
|
||||
kind and old files remain valid.
|
||||
|
||||
## Geometry and Material
|
||||
|
||||
- Default remains yellow dashed: 2m dash, 2m gap, 0.25m width.
|
||||
- `solid` generates deterministic adjacent 2m pieces with no gap. Pieces still
|
||||
undergo the same junction cutback and control-marking exclusion as dashed
|
||||
lines; this avoids creating a solid polygon across an excluded crossing.
|
||||
- Style fields `color`, `pattern`, `dash_length_m`, `dash_gap_m`, and
|
||||
`effective_style` are stored on every generated polygon.
|
||||
- Blender currently maps all `center_lines` to one yellow material, so the
|
||||
native adapter must support a white centre-line material route without
|
||||
changing legacy osm2streets layers. The route must preserve the existing
|
||||
yellow material for default and yellow overrides.
|
||||
|
||||
## Workbench UX
|
||||
|
||||
Selecting a centre-line dash shows a compact Chinese style panel in the
|
||||
existing form area. A select control presents the four named choices. Choosing
|
||||
one stages an override; the existing 保存 / 保存并重新生成 actions remain the
|
||||
only persistence and generation actions. The panel also shows whether the
|
||||
style is default or overridden and identifies the native road segment.
|
||||
|
||||
## Compatibility and Rollback
|
||||
|
||||
Unedited segments generate byte-compatible geometry style defaults apart from
|
||||
the added style properties. Oneway/service filtering and control priority are
|
||||
unchanged. Selecting `--road-provider osm2streets` remains a full rollback.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Add validated `center-line-style` overrides and a helper which resolves the
|
||||
effective default/override style by segment ID.
|
||||
2. Generate yellow/white and dashed/solid centre-line geometry while retaining
|
||||
cutback and control-marking exclusion behaviour; publish effective style
|
||||
properties.
|
||||
3. Extend the native Blender layer adapter/material handling to distinguish
|
||||
white from yellow centre-line features without affecting legacy layers.
|
||||
4. Add Workbench style controls, staging, Chinese selection evidence, and
|
||||
immediate regenerated-layer display.
|
||||
5. Add focused compiler, override, Workbench, Blender catalog, and build-stage
|
||||
tests; compile/check Nantaizi and build Blender/Cesium/preview without the
|
||||
package stage.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
npm run test:native-road
|
||||
npm run test:road-workbench
|
||||
python3 -m unittest discover blender/tests
|
||||
npm run test:build-stages
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview --road-provider native
|
||||
```
|
||||
|
||||
## Rollback
|
||||
|
||||
Remove the override records or choose `--road-provider osm2streets`; no legacy
|
||||
output is modified.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Native road center line style overrides
|
||||
|
||||
## Goal
|
||||
|
||||
Allow a Road Workbench user to select a generated native road centre line and
|
||||
persistently override its marking style, without turning the generated GeoJSON
|
||||
into the source of truth.
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- Native `center_lines.geojson` currently contains automatic yellow dashed
|
||||
polygons (`2m` dash, `2m` gap, `0.25m` width), each tied to a `segment_id`.
|
||||
- A displayed dash can already be selected and exposes its segment provenance,
|
||||
but has no editing controls.
|
||||
- `native-road-overrides.json` is the persistent authority for existing road
|
||||
and connection edits. The Workbench already stages, validates, saves, then
|
||||
recompiles those overrides.
|
||||
- The generated layer is consumed by both Workbench and native Blender/Cesium;
|
||||
styling must therefore be compiled geometry and use the existing
|
||||
`center_lines` material path, not a Workbench-only display tint.
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1: Selecting a centre-line dash must expose a Chinese style editor for its
|
||||
logical target and show the current effective style.
|
||||
- R2: Supported styles must include at least solid/dashed and white/yellow
|
||||
marking colours.
|
||||
- R3: Style choices must be stored in `native-road-overrides.json`, validated,
|
||||
reapplied during compilation, and survive a future Workbench launch.
|
||||
- R4: The regenerated GeoJSON must carry source traceability and effective
|
||||
style fields so Workbench, Blender and Cesium show the same marking.
|
||||
- R5: Default automatic centre lines remain unchanged for segments without an
|
||||
override; one-way/service exclusions and control-marking avoidance remain
|
||||
authoritative.
|
||||
|
||||
## Initial Scope Boundary
|
||||
|
||||
- Editing individual dash polygons is out of scope: they are derived pieces,
|
||||
not user-owned objects.
|
||||
- Per-segment control is recommended for the first version because current
|
||||
native segments already stop at junctions and have stable IDs.
|
||||
- Double-line semantics, legal `overtaking` inference, hand-drawn partial
|
||||
ranges, and changes to osm2streets output are out of scope unless explicitly
|
||||
accepted during planning.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] A user can select a centre line, choose a supported style in Chinese,
|
||||
save it, regenerate, and see the result immediately in the Workbench.
|
||||
- [ ] After reload and recompilation, the selected segment retains its style
|
||||
while unedited segments retain the automatic yellow dashed default.
|
||||
- [ ] Native Blender/Cesium uses the same effective style and does not require
|
||||
osm2streets geometry.
|
||||
- [ ] Tests cover schema validation, style geometry, default fallback,
|
||||
persistence/API wiring, and Workbench selection/edit controls.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- The first version applies one override to the complete native segment between
|
||||
junctions. Individual dash and partial-range editing are deferred.
|
||||
- The initial style catalog is four explicit choices: yellow dashed, white
|
||||
dashed, yellow solid, and white solid. Double-line semantics are deferred.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-road-center-line-styles",
|
||||
"name": "native-road-center-line-styles",
|
||||
"title": "Native road center line style overrides",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,50 @@
|
||||
# Native Road Centre Lines Design
|
||||
|
||||
## Architecture
|
||||
|
||||
The native compiler adds one independent polygon layer:
|
||||
|
||||
```text
|
||||
canonical bidirectional segment + trimmed OSM centreline
|
||||
-> repeated 2m x 0.25m dash polygons
|
||||
-> native-road/layers/center_lines.geojson
|
||||
-> Workbench / native Blender adapter / Cesium
|
||||
```
|
||||
|
||||
It does not read osm2streets `center_lines.geojson`. The legacy layer is a
|
||||
visual baseline only.
|
||||
|
||||
## Placement
|
||||
|
||||
- A candidate must have exactly forward and backward native roads for the same
|
||||
`segmentId`, neither be `highway=service`, and have a valid trimmed OSM
|
||||
centreline.
|
||||
- Dashes use the legacy observed dimensions: 2m length, 0.25m width, with a
|
||||
deterministic 2m gap. The first dash starts at a fixed segment-local offset
|
||||
so rebuilds do not drift.
|
||||
- The line is trimmed with the same `junctionPlans` cutback used by native lane
|
||||
centreline generation. Any dash intersecting a crosswalk or stop line is
|
||||
omitted, preserving the control-marking priority already established.
|
||||
- One-way and service segments have no generated feature. A degenerate source
|
||||
line produces a diagnostic rather than malformed geometry.
|
||||
|
||||
## Contracts
|
||||
|
||||
`layers/center_lines.geojson` is a Polygon FeatureCollection. Every feature
|
||||
has `native_id`, `segment_id`, `directional_road_ids`, `osm_way_ids`,
|
||||
`dash_index`, `dash_length_m`, `dash_gap_m`, `placement_rule`, and
|
||||
`provenance="native-road-center-line/v1"`.
|
||||
|
||||
`compiled.json.layers.centerLines`, comparison count
|
||||
`nativeCenterLineFeatures`, and `build-area.js` required native records use the
|
||||
same filename. `catalog.NATIVE_ROAD_LAYERS` maps source `center_lines` to the
|
||||
existing `center_lines` material layer.
|
||||
|
||||
The Workbench loads this source into the marking overlay, exposes a Chinese
|
||||
toggle, and identifies it as `道路中心虚线` on selection.
|
||||
|
||||
## Compatibility
|
||||
|
||||
The change is additive within `native-road/`; `--road-provider osm2streets`
|
||||
remains unaffected and is rollback. A native Blender build treats a missing
|
||||
layer as an input error rather than silently omitting it.
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Add centre-dash constants and a pure native compiler routine based on the
|
||||
shared bidirectional segment model and junction-trimmed centreline.
|
||||
2. Filter invalid, one-way, service, and control-marking-conflicting dashes;
|
||||
record actionable diagnostics for invalid geometry only.
|
||||
3. Write `center_lines.geojson`, compilation/comparison counts, native build
|
||||
records, required-layer checks, and Blender material adapter mapping.
|
||||
4. Add Workbench API state, Chinese toggle, summary count, rendering, and
|
||||
source evidence for a selected centre dash.
|
||||
5. Add focused native and Workbench tests for generation, skips, control
|
||||
avoidance, provenance, and file/API contracts.
|
||||
6. Run native/unit/workbench/build-stage tests, compile/check Nantaizi, then
|
||||
validate `blender,cesium,preview --road-provider native` without `package`.
|
||||
|
||||
## Rollback
|
||||
|
||||
Choose `--road-provider osm2streets`; no legacy layer or published package is
|
||||
modified.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Native road center lines
|
||||
|
||||
## Goal
|
||||
|
||||
Restore the existing osm2streets-style road-centre dashed markings in the
|
||||
native-road provider before treating native output as ready for broader quality
|
||||
gates or new-area validation.
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- Nantaizi's existing `osm2streets_web_out/center_lines.geojson` has 885
|
||||
`type="center line"` polygons. Its sampled dash geometry is approximately
|
||||
2.00m long by 0.25m wide.
|
||||
- The legacy extraction excludes centre lines that overlap crosswalk zones and
|
||||
that fall on service driving polygons (`build-osm2streets-qgis.js:474-480`).
|
||||
- Native output currently has only same-direction `lane_separators`; it has no
|
||||
`center_lines.geojson`, so a bidirectional road's directional carriageways
|
||||
lack their visual and semantic divider.
|
||||
- Existing Blender already owns a `center_lines` / `Center Line` material
|
||||
layer. Native must adapt to it rather than creating a second scene registry.
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1: For supported non-service, bidirectional native road segments, emit a
|
||||
`center_lines.geojson` dashed centre divider derived from the canonical OSM
|
||||
centreline and the segment's two directional carriageways.
|
||||
- R2: Match the established visual baseline: yellow 0.25m-wide, 2m-long
|
||||
dashes, clipped away from ordinary junction cutbacks and control markings.
|
||||
- R3: Preserve source traceability: each dash records its native segment,
|
||||
source OSM way, involved directional roads, placement interval/rule, and a
|
||||
dedicated provenance value.
|
||||
- R4: Add the layer to compilation records, Road Workbench display/selection
|
||||
in Chinese, and the existing Blender/Cesium `center_lines` material path.
|
||||
- R5: Do not render a centre divider on one-way or `highway=service` segments;
|
||||
report invalid geometry instead of fabricating a divider.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Native Nantaizi output contains a valid `layers/center_lines.geojson`
|
||||
with source-traceable 2m x 0.25m dashed polygons.
|
||||
- [ ] No native centre dash intersects a generated crosswalk or vehicle stop
|
||||
line, and no dash reaches into the supported junction surface cutback.
|
||||
- [ ] The Workbench can toggle and select centre lines, presenting their
|
||||
source and rule in Chinese rather than calling them lane separators.
|
||||
- [ ] Native `blender,cesium,preview` consumes the layer using the existing
|
||||
`center_lines` material and does not run `package`.
|
||||
- [ ] Tests cover two-way generation, one-way/service skips, control-marking
|
||||
avoidance, provenance, output contract, and Workbench wiring.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Centre-line editing overrides, solid/double-centre-line semantics, arbitrary
|
||||
OSM `overtaking` interpretation, lane colouring changes, and other areas.
|
||||
|
||||
## Key Decision
|
||||
|
||||
This task deliberately matches the existing stable visual layer first. It does
|
||||
not claim to infer a country-wide legal marking taxonomy from sparse OSM tags.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-road-center-lines",
|
||||
"name": "native-road-center-lines",
|
||||
"title": "Native road center lines",
|
||||
"description": "Align native-road output with the existing center_lines layer before broader quality work.",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-13-native-road-compiler",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,66 @@
|
||||
# Native Road Control Markings Design
|
||||
|
||||
## Architecture
|
||||
|
||||
The native compiler remains the source of truth. It extends its canonical OSM
|
||||
parse with marked crossing nodes, then derives two additive polygon layers from
|
||||
the crossing evidence, native directed lanes, and native junction plans:
|
||||
|
||||
```text
|
||||
OSM crossing node + native directed lane centerlines + junction plans
|
||||
|
|
||||
+-- crosswalks.geojson: six zebra stripe polygons per safe crossing
|
||||
|
|
||||
+-- vehicle_stop_lines.geojson: one safe approach stop-line polygon
|
||||
```
|
||||
|
||||
No osm2streets GeoJSON is read. Existing crossing geometry helpers may be
|
||||
extracted or adapted only when they operate on native lane data and retain
|
||||
native provenance.
|
||||
|
||||
## Source And Placement
|
||||
|
||||
- A source node is eligible only when `highway=crossing` and
|
||||
`crossing:markings` is not `no`, `none`, or `unmarked`.
|
||||
- The compiler finds native roads containing the crossing's OSM node and uses
|
||||
the nearest compatible directed lane centerline to obtain the road tangent.
|
||||
- Crosswalk stripes are perpendicular to that tangent and constrained to the
|
||||
native road width. Existing fixed zebra dimensions are retained initially:
|
||||
six 0.45m stripes with 0.45m gaps, 0.45m stripe width, and a highway-based
|
||||
stripe length.
|
||||
- A stop line is generated only when the crossing can be associated with a
|
||||
supported junction approach and a safe outside-of-junction side. Otherwise
|
||||
the crosswalk may remain valid but the missing stop line is diagnostic.
|
||||
- Duplicate nearby crossing nodes use a stable cluster representative so one
|
||||
physical crosswalk does not produce duplicate stripes.
|
||||
|
||||
## Contracts
|
||||
|
||||
`layers/crosswalks.geojson` and `layers/vehicle_stop_lines.geojson` are Polygon
|
||||
FeatureCollections. Each feature records its crossing OSM node, source OSM way,
|
||||
native directed road/lane when available, direction, placement method, and
|
||||
`native-road-crosswalk/v1` or `native-road-stop-line/v1` provenance.
|
||||
|
||||
The Workbench API returns both layers as `state.layers.crosswalks` and
|
||||
`state.layers.vehicleStopLines`. Its browser map uses separate toggleable
|
||||
layers and selection evidence; the scene-preview toggle leaves real control
|
||||
markings visible.
|
||||
|
||||
`catalog.NATIVE_ROAD_LAYERS` maps the sources to existing `crosswalks` and
|
||||
`vehicle_stop_lines` material layers. This native adapter must not add them to
|
||||
the osm2streets `ROAD_LAYERS` / `SCENE_LAYERS` registry.
|
||||
|
||||
## Compatibility And Rollback
|
||||
|
||||
The new files are additive under `native-road/layers/`. Existing osm2streets
|
||||
output and QGIS input are unchanged. Selecting `--road-provider osm2streets`
|
||||
remains rollback. A native Blender build treats a missing new layer as an error
|
||||
rather than silently omitting a visible marking.
|
||||
|
||||
## Risks
|
||||
|
||||
- Crossing nodes can be detached from a routable road or lie on an ambiguous
|
||||
multi-road segment. These become diagnostics rather than guessed geometry.
|
||||
- A physical crossing split into several OSM nodes must deduplicate stably.
|
||||
- A crosswalk near a non-supported junction may get stripes but no valid stop
|
||||
line; this difference must be exposed in workbench provenance.
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Extend the native OSM parse/model with marked crossing-node evidence while
|
||||
preserving existing road IDs and parser behavior.
|
||||
2. Add native crossing clustering, tangent resolution from directed lane
|
||||
centerlines, stripe geometry, safe stop-line placement, and source
|
||||
diagnostics.
|
||||
3. Persist the two new layers in `compile-native-roads.js`, comparison counts,
|
||||
native build records, required native layer checks, and existing Blender
|
||||
material mappings.
|
||||
4. Add Workbench API/state fields, Chinese layer toggles, selection evidence,
|
||||
and summary counts while retaining scene-preview behavior.
|
||||
5. Add focused fixtures for marked and unmarked crossings, duplicate cluster
|
||||
handling, a missing native-lane diagnostic, and output layer contracts.
|
||||
6. Run native/unit/workbench/build-stage tests, compile/check Nantaizi, then
|
||||
build `blender,cesium,preview --road-provider native` without `package/`.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
npm run test:native-road
|
||||
npm run test:road-workbench
|
||||
npm run test:build-stages
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json \
|
||||
--stages blender,cesium,preview --road-provider native
|
||||
```
|
||||
|
||||
Rollback is selecting `--road-provider osm2streets`; no legacy output path is
|
||||
changed.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Native road control markings
|
||||
|
||||
## Goal
|
||||
|
||||
Give Nantaizi's native-road provider inspectable, source-traceable crosswalk
|
||||
and vehicle stop-line geometry, so intersection control markings do not depend
|
||||
on the osm2streets render output.
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- Native road output already owns road surfaces, sidewalks, lane separators,
|
||||
repeated direction arrows, and explicit junction-turn arrows.
|
||||
- Nantaizi's OSM input contains explicit marked crossings, including zebra and
|
||||
traffic-signal crossings. The existing osm2streets output currently has 48
|
||||
crosswalk-stripe polygons and 8 stop-line polygons.
|
||||
- `build-osm2streets-qgis.js:1067` derives these markings from crossing nodes
|
||||
plus osm2streets driving lanes. Native must not read that rendered geometry;
|
||||
it can reuse only the tested geometry rules after adapting them to native
|
||||
directed lanes and junction plans.
|
||||
- Existing Blender materials already provide `crosswalks` and
|
||||
`vehicle_stop_lines`; native may map into them without changing the legacy
|
||||
osm2streets layer registry.
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1: Native compilation uses explicit marked OSM crossing nodes only, and
|
||||
emits crosswalk-stripe polygons plus approach stop lines only where safe
|
||||
native directed-road placement exists.
|
||||
- R2: Each generated feature preserves crossing node, OSM road, native road or
|
||||
lane, direction, placement method, and relevant junction provenance.
|
||||
- R3: The Road Workbench independently toggles, selects, and describes native
|
||||
crosswalks and stop lines in Chinese.
|
||||
- R4: Native Blender and Cesium builds consume both layers with the existing
|
||||
control-marking materials; no `package/` publication is part of validation.
|
||||
- R5: Missing compatible road context, ambiguous geometry, and unsupported
|
||||
crossing inputs are diagnostics; the compiler must not invent a crossing.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Nantaizi native output contains valid `crosswalks.geojson` and
|
||||
`vehicle_stop_lines.geojson` features with source-traceable properties,
|
||||
without reading osm2streets rendered layers.
|
||||
- [ ] A Workbench user can toggle and select either marking type and see the
|
||||
crossing node, associated road/direction, and placement evidence.
|
||||
- [ ] Native Blender/Cesium output contains both marking types using existing
|
||||
materials, after `blender,cesium,preview --road-provider native` and without
|
||||
publishing `package/`.
|
||||
- [ ] Tests cover a marked crossing, an unmarked crossing skip, a missing or
|
||||
ambiguous native-road placement skip, and output-layer contract checks.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Hand-placed control-marking overrides, traffic-signal state-machine changes,
|
||||
freehand polygon editing, processing another region, and importing
|
||||
osm2streets-rendered crosswalk geometry.
|
||||
|
||||
## Key Decision
|
||||
|
||||
The first version is explicit-OSM-only. This is feasible for Nantaizi and
|
||||
keeps control markings evidence-backed; missing data remains a diagnostic for
|
||||
OSM improvement rather than a silent geometric guess.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-road-control-markings",
|
||||
"name": "native-road-control-markings",
|
||||
"title": "Native road control markings",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-13-native-road-compiler",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Native road edge markings
|
||||
|
||||
## Goal
|
||||
|
||||
TBD.
|
||||
|
||||
## Requirements
|
||||
|
||||
- TBD
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] TBD
|
||||
|
||||
## Notes
|
||||
|
||||
- Keep `prd.md` focused on requirements, constraints, and acceptance criteria.
|
||||
- Lightweight tasks can remain PRD-only.
|
||||
- For complex tasks, add `design.md` for technical design and `implement.md` for execution planning before `task.py start`.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-road-edge-markings",
|
||||
"name": "native-road-edge-markings",
|
||||
"title": "Native road edge markings",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Check compiler and workbench marking contracts."}
|
||||
{"file":".trellis/spec/blender/testing.md","reason":"Run required Blender test suite."}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"Verify browser, compiler, and Blender consume the same output."}
|
||||
@@ -0,0 +1,22 @@
|
||||
# Design
|
||||
|
||||
The compiler owns marking semantics. `center-line-style` remains segment
|
||||
scoped because a bidirectional road segment shares one centre-line decision.
|
||||
`edge-line-style` is scoped to `{ roadId, side }`, since a directional
|
||||
carriageway has independently editable left and right outside edges.
|
||||
|
||||
`double: true` is valid only with yellow solid centre lines. Generation emits
|
||||
two 0.32m-separated parallel polygons for each normal solid mark, preserving
|
||||
junction cutback and crosswalk/stop-line clearance. This keeps the existing
|
||||
solid line implementation and avoids a separate geometry pipeline.
|
||||
|
||||
Road edge lines use the existing road-edge offset geometry. A solid style is
|
||||
one continuous buffered offset line; a dashed style uses deterministic 2m
|
||||
marks at 4m spacing. Both carry `effective_style`, source IDs, side, and
|
||||
native provenance.
|
||||
|
||||
The Workbench uses one marking form with an explicit selected target type.
|
||||
Only a centre-line target exposes the double-yellow option; lane separators
|
||||
and edge lines retain the ordinary colour/pattern choices. Staged changes pass
|
||||
through the existing save and regenerate flow, so browser, compiler and
|
||||
Blender read the same persisted override.
|
||||
@@ -0,0 +1,3 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Native-road output, override, and stage contracts."}
|
||||
{"file":".trellis/spec/blender/asset-generation.md","reason":"Native provider adapter and Blender output requirements."}
|
||||
{"file":".trellis/spec/guides/artifact-parity-guide.md","reason":"Intentional render-output change validation."}
|
||||
@@ -0,0 +1,10 @@
|
||||
# Implementation
|
||||
|
||||
1. Extend override validation and native marking generation for explicit
|
||||
double-yellow centre lines and per-side edge-line styles.
|
||||
2. Extend the Chinese Workbench selection/form flow without adding another
|
||||
editor or output format.
|
||||
3. Add compiler and Workbench regression coverage for valid and invalid
|
||||
payloads, generated geometry, and layer adapter completeness.
|
||||
4. Compile/check Nantaizi, run native/Workbench/build-stage/Blender tests,
|
||||
then run the native Blender/Cesium/preview chain.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Native road marking semantics
|
||||
|
||||
## Goal
|
||||
|
||||
Finish the native road marking model so the Nantaizi Road Workbench can
|
||||
persist meaningful centre-line and road-edge-line styles, regenerate native
|
||||
geometry deterministically, and deliver the same result to Blender/Cesium.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Add an explicit double-yellow-solid centre-line semantic. It must not be a
|
||||
generic double-line switch that permits invalid colour/pattern combinations.
|
||||
- Keep all marking edits area-local in `native-road-overrides.json`; generated
|
||||
GeoJSON remains derived output.
|
||||
- Make road edge lines selectable and style-editable in the Chinese Road
|
||||
Workbench, alongside existing centre lines and lane separators.
|
||||
- Preserve the default Nantaizi output unless a user supplies an override.
|
||||
- Native geometry must remain independent of osm2streets render geometry.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] A `center-line-style` override with `double: true`, `yellow`, and
|
||||
`solid` emits two offset centre-line polygons with an explicit effective
|
||||
style, while invalid double combinations are rejected.
|
||||
- [ ] An `edge-line-style` override targets one directional road side and
|
||||
supports white/yellow plus solid/dashed styles.
|
||||
- [ ] The Workbench exposes Chinese selection feedback, stages the correct
|
||||
override payload, and only shows the double-yellow choice for centre lines.
|
||||
- [ ] Nantaizi native compile/check, Workbench/native tests, Blender pure
|
||||
tests, and native Blender/Cesium/preview build pass.
|
||||
|
||||
## Notes
|
||||
|
||||
- Keep `prd.md` focused on requirements, constraints, and acceptance criteria.
|
||||
- Lightweight tasks can remain PRD-only.
|
||||
- For complex tasks, add `design.md` for technical design and `implement.md` for execution planning before `task.py start`.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-road-marking-semantics",
|
||||
"name": "native-road-marking-semantics",
|
||||
"title": "Native road marking semantics",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-17",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Native geometry correctness and output contracts."}
|
||||
{"file":".trellis/spec/blender/testing.md","reason":"Blender validation requirements."}
|
||||
@@ -0,0 +1,18 @@
|
||||
# Design
|
||||
|
||||
Each approach contributes its two carriageway-edge points at the common
|
||||
cutback distance. Points are ordered around the junction node. For each pair
|
||||
from adjacent approaches, the compiler samples a deterministic quadratic
|
||||
Bezier whose control point follows the pedestrian-side curb arc toward the
|
||||
junction. Rounded plans use a larger cutback than the legacy straight envelope
|
||||
so this visible curb shape still contains all turning connectors. Approach road
|
||||
surfaces terminate at the same cutback, so they cannot cover the junction
|
||||
outline in 3D output.
|
||||
|
||||
The curve is accepted only when the support intersection is finite, the pair
|
||||
belongs to different approaches, and the resulting ring remains valid and
|
||||
contains all published connector coordinates. Otherwise the original straight
|
||||
chord remains for that corner and the plan reports a mixed/fallback boundary.
|
||||
|
||||
Lane connectors remain a separate vehicle-path layer. This task changes only
|
||||
the road/intersection outline and sidewalk-corner shape.
|
||||
@@ -0,0 +1,2 @@
|
||||
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Native geometry and build-stage contracts."}
|
||||
{"file":".trellis/spec/guides/artifact-parity-guide.md","reason":"Intentional geometry output change validation."}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Implementation
|
||||
|
||||
1. Build a rounded junction boundary from ordered approach-edge records with
|
||||
tangent support-line intersections and deterministic curve samples.
|
||||
2. Expose boundary mode/provenance and preserve containment fallback.
|
||||
3. Extend focused native-road tests for curved ordinary intersections.
|
||||
4. Validate Nantaizi compile/check, workbench tests, and native 3D build.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Rounded native road junctions
|
||||
|
||||
## Goal
|
||||
|
||||
Replace the octagonal native junction outline with smooth, tangentially joined
|
||||
road-edge corners for ordinary Nantaizi T and cross junctions.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Junction surface boundaries must connect adjacent approach carriageway edges
|
||||
with a smooth outward curve rather than a straight octagonal chord.
|
||||
- Sidewalk corner surfaces must use the same rounded boundary concept so road
|
||||
and pedestrian geometry do not disagree visually.
|
||||
- Preserve a deterministic straight-edge fallback and an explicit diagnostic
|
||||
when a corner cannot be safely constructed.
|
||||
- Do not change lane connector semantics or derive geometry from osm2streets.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] Ordinary cross/T fixtures generate rounded junction polygons with more
|
||||
than the prior eight straight boundary vertices and `boundary_mode` records
|
||||
the chosen style.
|
||||
- [x] Connector containment remains valid and degenerate geometry falls back
|
||||
without publishing self-intersecting polygons.
|
||||
- [x] Nantaizi compile/check and native Blender/Cesium/preview succeed.
|
||||
|
||||
## Verification Record
|
||||
|
||||
- Native geometry and Workbench tests passed.
|
||||
- Nantaizi native compile/check passed with `ok=true`.
|
||||
- Native Blender/Cesium/preview verification was completed manually in a
|
||||
working Blender environment.
|
||||
|
||||
## Notes
|
||||
|
||||
- Keep `prd.md` focused on requirements, constraints, and acceptance criteria.
|
||||
- Lightweight tasks can remain PRD-only.
|
||||
- For complex tasks, add `design.md` for technical design and `implement.md` for execution planning before `task.py start`.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-rounded-junctions",
|
||||
"name": "native-rounded-junctions",
|
||||
"title": "Rounded native road junctions",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-17",
|
||||
"completedAt": "2026-08-18",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-13-native-road-compiler",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,51 @@
|
||||
# Design
|
||||
|
||||
## Provider Boundary
|
||||
|
||||
`build-area.js` will resolve a native provider by default. Native stages consume only OSM, native
|
||||
road overrides, and `native-road/` outputs. Legacy `intermediates`, `reimport`, and osm2streets
|
||||
Blender input remain behind explicit legacy selection and keep their existing output contract.
|
||||
|
||||
## Native Data Flow
|
||||
|
||||
```text
|
||||
OSM + native-road-overrides.json
|
||||
-> compile-native-roads.js
|
||||
-> native-road/layers/*.geojson + compiled.json + traffic-signals.json
|
||||
-> Blender generate_scene.py --native-road --traffic-signals
|
||||
-> .blend
|
||||
-> Cesium/package GLB + manifest + runtime/traffic-signals.json
|
||||
-> preview HTML + optional native vehicle route
|
||||
```
|
||||
|
||||
The native compiler is the sole authority for road geometry, lane semantics, intersection surfaces,
|
||||
stop lines, and signal runtime. No native stage reads `geojsonDir` or legacy assembly files.
|
||||
|
||||
## Preview Route
|
||||
|
||||
Add a native route adapter that derives route segments from `compiled.json` model roads/endpoints and
|
||||
native lane/connector geometry. If the native route cannot be built for a valid area, preview remains
|
||||
usable without vehicles and records a warning; missing legacy osm2streets lane polygons is never a
|
||||
hard failure on the native path.
|
||||
|
||||
## Package Runtime
|
||||
|
||||
The package stage will copy the native-road signal runtime into its staging runtime directory and
|
||||
declare it in the package manifest. Preview receives the package-relative runtime URI. Legacy signal
|
||||
runtime publication remains conditional on the legacy provider.
|
||||
|
||||
## Configuration And Compatibility
|
||||
|
||||
`roadProvider` defaults to `native`; native stages are the default stage set. QGIS config fields and
|
||||
legacy stage aliases remain accepted for explicit migration/debug commands, but native manifests and
|
||||
docs do not claim them as inputs.
|
||||
|
||||
The Workbench may continue to read an existing `geojsonDir` only for an explicitly labeled reference
|
||||
layer and comparison counters. Those reads are optional, isolated from native compile state, and must
|
||||
never become required inputs for `/api/state`, native editing, package publication, or preview.
|
||||
|
||||
## Rollback
|
||||
|
||||
The original QGIS/osm2streets branch baseline is tagged
|
||||
`qgis-original-baseline-20260818` on `main`. Reverting the native-only work can therefore use the tag
|
||||
or switch to `main`; no destructive cleanup of legacy scripts is required.
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Add native provider/default-stage contracts and explicit legacy provider routing.
|
||||
2. Make package publication and stage manifests consume native runtime artifacts.
|
||||
3. Replace preview's hard dependency on osm2streets lane/network/intersection files with a native
|
||||
route adapter and optional-route behavior.
|
||||
4. Audit Blender, Cesium, preview, diagnostics, and area config for native-path legacy reads; retain
|
||||
only the Workbench's optional, clearly labeled osm2streets reference layer.
|
||||
5. Update README/config/spec documentation and add dependency-boundary regression tests.
|
||||
6. Run native-only tests with legacy directories absent, then run focused legacy compatibility tests.
|
||||
7. Run area compile/check plus available Blender/Cesium/preview validation; record any environment-only
|
||||
limitation without weakening native contracts.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
npm run test:native-road
|
||||
npm run test:traffic-signals
|
||||
npm run test:road-workbench
|
||||
npm run test:preview-assets
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
node --check scripts/build-area.js
|
||||
```
|
||||
|
||||
The final gate must also exercise a native-only config/output directory without QGIS/osm2streets
|
||||
intermediates and assert package/runtime/preview outputs.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Complete native-only pipeline replacement
|
||||
|
||||
## Goal
|
||||
|
||||
Make the native road compiler the primary and complete production path. A normal build of an
|
||||
area must generate Blender, Cesium/package, and preview outputs directly from OSM plus native
|
||||
artifacts without installing or running QGIS, GDAL, or osm2streets.
|
||||
|
||||
The existing QGIS/osm2streets workflow remains available only as an explicit legacy/debug path and
|
||||
an optional Workbench reference layer. It is protected by the `qgis-original-baseline-20260818` tag
|
||||
on `main`.
|
||||
|
||||
## Confirmed Current Gaps
|
||||
|
||||
- `normalizeAreaConfig()` defaults `stages.intermediates` to true and `blender.roadProvider` to
|
||||
`osm2streets`.
|
||||
- `build-area.js` sends `intermediates` to `build-osm2streets-qgis.js` and `reimport` to GDAL/QGIS.
|
||||
- Native Blender input exists, but `preview` still requires `osm2streets_web_out/lane_polygons.geojson`,
|
||||
`network.json`, and `intersection_surface.geojson` for vehicle-route generation.
|
||||
- Package/runtime publication still copies `outputs.<area>.trafficSignals` from the legacy GeoJSON
|
||||
directory instead of the native-road runtime artifact.
|
||||
- README, config templates, stage names, and manifest descriptions still present QGIS as the normal
|
||||
workflow.
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1: Native is the default road provider and default build stages do not invoke QGIS, GDAL, or
|
||||
osm2streets.
|
||||
- R2: Native build generates all required road, lane, marking, junction, stop-line, and traffic-signal
|
||||
inputs consumed by Blender and preview; no native stage reads `osm2streets_web_out`.
|
||||
- R3: Native package publication copies runtime traffic signals and any other runtime descriptors
|
||||
from native-road outputs, with stable package-relative URIs.
|
||||
- R4: Native preview route generation uses native lane/road topology and native intersection geometry,
|
||||
or explicitly omits the optional vehicle route when native route data is unavailable; it must not
|
||||
fail because legacy osm2streets files are absent.
|
||||
- R5: QGIS/osm2streets stages remain callable only through an explicit legacy provider/stage selection
|
||||
and are not part of native defaults or native manifests. Workbench may display their existing
|
||||
GeoJSON as a clearly labeled reference/comparison layer, but native editing, compile, package, and
|
||||
preview results must not depend on it.
|
||||
- R6: Documentation and config templates describe native-only as the primary workflow and clearly
|
||||
mark the legacy path as transitional/debug-only.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] A clean native-only area build succeeds with QGIS absent and no `osm2streets-js-node` runtime
|
||||
call, producing `.blend`, package manifest/GLB, runtime traffic signals, and preview HTML.
|
||||
- [ ] Native preview opens when `osm2streets_web_out/` is absent; optional vehicle cruise either uses
|
||||
native route data or is omitted with a visible non-fatal diagnostic.
|
||||
- [ ] Native package manifest contains runtime traffic-signal anchors sourced from
|
||||
`native-road/traffic-signals.json`; disabled signals remain absent from runtime.
|
||||
- [ ] Workbench edits survive save, native recompile, package publication, and preview reload without
|
||||
QGIS or osm2streets files.
|
||||
- [ ] Explicit legacy QGIS/osm2streets commands still pass their existing focused tests, but no longer
|
||||
run when using native defaults.
|
||||
- [ ] Workbench remains usable when the optional osm2streets reference directory is absent; only the
|
||||
reference layer/comparison counters degrade to unavailable.
|
||||
- [ ] Tests assert the native-only dependency boundary and the QGIS baseline tag is documented.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Reimplementing QGIS editing features in the native compiler beyond existing Workbench controls.
|
||||
- Deleting legacy scripts or historical output files in this task; they remain rollback/debug tooling.
|
||||
- Changing Blender/Cesium visual semantics unrelated to removing the input dependency.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "native-only-pipeline-replacement",
|
||||
"name": "native-only-pipeline-replacement",
|
||||
"title": "Complete native-only pipeline replacement",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-18",
|
||||
"completedAt": "2026-08-18",
|
||||
"branch": null,
|
||||
"base_branch": "feature/native-road-compiler",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,70 @@
|
||||
# Design
|
||||
|
||||
## Authority And Boundaries
|
||||
|
||||
The native compiler remains the only road authority. The existing traffic-signal runtime remains the
|
||||
only signal authority. The new feature adds a route/simulation layer beside those artifacts; it does
|
||||
not alter signal geometry, phase grouping, or Blender static assets.
|
||||
|
||||
```text
|
||||
native-road/compiled.json + native-road/layers/*
|
||||
-> native route compiler
|
||||
-> _preview/<area>-traffic-simulation.json
|
||||
-> Cesium preview runtime
|
||||
|
||||
package/runtime/traffic-signals.json
|
||||
-> signal phase clock + stop events
|
||||
```
|
||||
|
||||
The descriptor is intentionally package-adjacent, not embedded in the GLB. A consumer can copy the
|
||||
package and descriptor to another platform and reproduce the simulation without running Node.
|
||||
|
||||
## Descriptor Contract
|
||||
|
||||
Schema: `native-preview-traffic-simulation/v1`.
|
||||
|
||||
Required top-level fields:
|
||||
|
||||
- `areaId`, `coordinateSystem` (`WGS84` route coordinates, `ENU` model placement), `generatedAt`
|
||||
- `source` with relative artifact references and SHA-256 records
|
||||
- `settings` with speed, acceleration, deceleration, reaction time, vehicle length, and minimum gap
|
||||
- `routes[]` with ordered WGS84 coordinates, cumulative distances, connector/maneuver IDs, and
|
||||
`stops[]`
|
||||
- `signals[]` copied by stable `signal_uid` from the runtime contract, including phase group and
|
||||
stop-line association; no regenerated pose fields
|
||||
- `migration` describing schema version, coordinate conversion, update-loop expectations, and
|
||||
compatibility notes
|
||||
|
||||
The descriptor is deterministic for the same OSM, overrides, native compiler version, and settings.
|
||||
|
||||
## Route Construction
|
||||
|
||||
Use native directional roads and published connector geometry. Build a small number of deterministic
|
||||
closed demonstration routes that cross real connectors and retain lane/road IDs. Route generation
|
||||
must reject routes with missing geometry or disabled connectors and write structured diagnostics.
|
||||
Native stop lines are matched by `road_id`/`lane_id` where available, with a bounded geometric
|
||||
fallback recorded in the stop record. No fixed-width or legacy polygon fallback is allowed.
|
||||
|
||||
## Browser Simulation
|
||||
|
||||
Keep the existing signal phase functions and `signalData` payload. Replace the current per-vehicle
|
||||
independent distance advance with a shared simulation state:
|
||||
|
||||
- each vehicle has route distance, speed, desired speed, status, and active stop reason;
|
||||
- signal stop constraints are evaluated before the native stop line for red/yellow phases;
|
||||
- leader constraints are evaluated on the same route and wrap around the loop;
|
||||
- acceleration/deceleration clamps advance toward the minimum of desired speed, signal limit, and
|
||||
leader-safe speed;
|
||||
- vehicle position and orientation remain Cesium callback properties, with no per-frame allocation
|
||||
of heavyweight Cesium objects;
|
||||
- incident-card behavior remains local to each vehicle and must not mutate the descriptor or package.
|
||||
|
||||
Diagnostics expose route count, signal count, stopped vehicles, and queue length in the existing
|
||||
preview diagnostics panel.
|
||||
|
||||
## Migration And Rollback
|
||||
|
||||
Document the descriptor, runtime signal schema, coordinate conversion, and a reference update loop in
|
||||
`docs/native-preview-traffic-simulation.md`. Include a JSON example and a consumer checklist. Legacy
|
||||
preview route files remain readable only through the existing legacy provider path; native generation
|
||||
must not depend on them. Removing the new descriptor restores the existing no-cruise native preview.
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,37 @@
|
||||
# Implementation Plan
|
||||
|
||||
1. Add a native route compiler module that consumes `native-road/compiled.json` and native layers,
|
||||
emits `native-preview-traffic-simulation/v1`, and records source hashes and migration metadata.
|
||||
2. Wire native preview generation to write the descriptor and keep legacy osm2streets route
|
||||
generation unchanged.
|
||||
3. Extend the browser preview with shared deterministic vehicle state, signal stop constraints,
|
||||
leader following, queue diagnostics, and graceful empty-route behavior.
|
||||
4. Preserve the existing signal phase, `signal_uid`, pose, dynamic lens, countdown, disabled-signal,
|
||||
and incident-card contracts; add focused regression tests for each boundary.
|
||||
5. Add `docs/native-preview-traffic-simulation.md` with schema, example, coordinate rules, update
|
||||
loop, migration checklist, and rollback notes.
|
||||
6. Run focused Node tests, native compile/check, a native build with no legacy directory, and browser
|
||||
preview validation in the available Blender/Cesium environment.
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```bash
|
||||
node --check scripts/lib/native-preview-traffic-simulation.js
|
||||
node --check scripts/build-area.js
|
||||
npm run test:native-road
|
||||
npm run test:traffic-signals
|
||||
npm run test:preview-assets
|
||||
npm run test:native-preview-traffic
|
||||
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json
|
||||
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,compress,package,preview
|
||||
```
|
||||
|
||||
## Risk Controls
|
||||
|
||||
- Do not change `scripts/lib/traffic-signals.js` geometry or phase semantics unless a test proves a
|
||||
migration bug; prefer adapter code.
|
||||
- Do not add route data to the static package manifest; it belongs beside `_preview/` and is optional.
|
||||
- If native route generation produces no valid route, emit a valid descriptor with diagnostics and
|
||||
keep preview usable.
|
||||
- Keep all route and simulation math unit-testable without Cesium.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user