67 Commits

Author SHA1 Message Date
9a8dbc1a74 feat: 枫树二路 2026-08-18 17:15:17 +08:00
5403936ae4 feat: add native preview traffic simulation 2026-08-18 16:57:27 +08:00
0bc949bc24 fix: clear stale legacy preview route 2026-08-18 14:29:13 +08:00
6a0437ff83 chore: record journal 2026-08-18 14:17:08 +08:00
3cad384b25 chore(task): archive 08-18-native-only-pipeline-replacement 2026-08-18 14:15:45 +08:00
9e0e25aadf feat: make native road pipeline the default 2026-08-18 14:15:08 +08:00
e9ab1f03eb chore(task): plan native-only pipeline replacement 2026-08-18 13:59:42 +08:00
1154de3ba1 chore: record journal 2026-08-18 11:49:30 +08:00
c057258085 chore(task): archive 08-13-native-road-compiler 2026-08-18 11:48:34 +08:00
41d32457f2 chore(task): archive 08-18-native-traffic-signal-parity 2026-08-18 11:47:16 +08:00
5accc0a4b1 feat: complete native traffic signal workflow 2026-08-18 11:46:19 +08:00
7204c28161 chore: record journal 2026-08-18 09:27:57 +08:00
a63a3ae0d8 chore(task): archive 08-17-native-rounded-junctions 2026-08-18 09:26:58 +08:00
f7e71cfe9c fix: disable native edge lines by default 2026-08-18 09:07:29 +08:00
e41bfd13ec feat: smooth native junction boundaries 2026-08-17 17:39:07 +08:00
46542c5f4e chore: record journal 2026-08-17 16:09:50 +08:00
1896884900 chore(task): archive 08-17-native-road-marking-semantics 2026-08-17 16:09:37 +08:00
9bb97d4507 feat: add native road marking semantics 2026-08-17 16:08:56 +08:00
bd38aa55d3 chore(task): archive 08-17-native-road-edge-markings 2026-08-17 15:26:59 +08:00
b318f7b641 feat: add native road edge markings 2026-08-17 15:25:18 +08:00
d065dcdaf3 chore: record journal 2026-08-17 14:47:24 +08:00
472a83c670 chore(task): archive 08-17-native-lane-separator-styles 2026-08-17 14:46:09 +08:00
bc045177a5 feat: add native lane separator style overrides 2026-08-17 14:45:48 +08:00
8409d2c6c3 chore: record journal 2026-08-17 13:34:25 +08:00
5f59ad0ef0 chore(task): archive 08-17-native-road-center-line-styles 2026-08-17 13:33:30 +08:00
47efb78f1e feat: add native center line style overrides 2026-08-17 13:29:54 +08:00
8bfc02e66e chore: record journal 2026-08-17 10:45:52 +08:00
9d42181246 chore(task): archive 08-17-native-road-center-lines 2026-08-17 10:44:50 +08:00
fb863dafb2 feat: add native road center lines 2026-08-17 10:44:01 +08:00
7dc5c947a3 chore: record journal 2026-08-17 10:12:27 +08:00
866b59acf8 chore(task): archive 08-17-native-road-control-markings 2026-08-17 10:10:38 +08:00
4c4f4534c0 feat: add native road control markings 2026-08-17 10:01:44 +08:00
ea8a3622b9 chore: record journal 2026-08-14 18:06:00 +08:00
03a8d6159b chore(task): archive 08-14-native-road-lane-markings 2026-08-14 18:05:43 +08:00
1b9829d9ed feat: add native road direction arrows 2026-08-14 18:05:21 +08:00
822e6ef936 feat: report native junction quality metrics 2026-08-14 16:21:26 +08:00
3eea12c6ea fix: stabilize native road Cesium preview 2026-08-14 16:07:55 +08:00
3d57655497 feat: inspect native junctions in workbench 2026-08-14 15:31:48 +08:00
65cf8b96d9 feat: add native road compiler provider 2026-08-14 15:19:57 +08:00
e1f3fc10ca fix: persist and render native sidewalks 2026-08-14 10:53:20 +08:00
f43a122ff7 fix: accept inferred road widths in workbench 2026-08-14 10:43:47 +08:00
3e22a1872e feat: clarify road movement editing feedback 2026-08-14 10:38:22 +08:00
1da985c932 feat: inspect native road movements 2026-08-14 10:26:35 +08:00
817e361477 feat: prioritize road review diagnostics 2026-08-14 10:22:37 +08:00
25287777f6 feat: add native road quality check 2026-08-14 10:18:08 +08:00
a556c0fc97 feat: add canonical road movements 2026-08-14 10:15:47 +08:00
1876472bf8 feat: improve native road diagnostics and review 2026-08-14 10:07:16 +08:00
707e7f82f9 feat: migrate road workbench to OpenLayers 2026-08-14 09:38:56 +08:00
850e9eb344 feat: add lane-level turn controls 2026-08-14 09:22:38 +08:00
df81a22ce8 feat: compile native road lanes and junctions 2026-08-14 09:09:48 +08:00
b5fa4482f0 feat: add native road compiler workbench 2026-08-13 18:01:20 +08:00
ddd15f68b3 chore: record journal 2026-08-12 11:30:54 +08:00
aa5b1f85c8 chore(task): archive 08-12-vehicle-incident-info 2026-08-12 11:30:54 +08:00
761a52646c feat(preview): add vehicle incident cards 2026-08-12 11:29:13 +08:00
f13d890ffb fix(assets): publish controllable traffic signal runtime 2026-08-12 10:17:34 +08:00
41a0e81a6f chore: record journal 2026-08-12 09:09:44 +08:00
eff7a01b2b chore(task): archive 08-11-asset-package-contract 2026-08-12 09:07:57 +08:00
0102ffbb3c docs(assets): document reusable asset package contract 2026-08-12 09:06:04 +08:00
f2b8d79f5d fix(package): record published primary model 2026-08-11 17:52:59 +08:00
db0fba5bb5 fix(pipeline): preserve package manifest ownership 2026-08-11 17:45:16 +08:00
c925890848 fix(preview): resolve package assets from manifest 2026-08-11 16:31:55 +08:00
f385009043 feat(assets): publish reusable area packages 2026-08-11 16:07:07 +08:00
b4a81331e8 chore: record journal 2026-08-11 15:31:34 +08:00
e0cdf9e00b chore(task): archive 08-11-interactive-area-cli 2026-08-11 15:31:34 +08:00
0790cbd0a9 feat(pipeline): make compressed assets the default delivery 2026-08-11 15:30:04 +08:00
b68be063ad feat(cli): add interactive area build menu 2026-08-11 14:08:22 +08:00
d26921c6d1 fix(reimport): retain intersection IDs for cruise routes 2026-08-11 13:25:54 +08:00
148 changed files with 23487 additions and 459 deletions

View File

@@ -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,7 +54,7 @@ cp config/examples/template.json config/areas/my-area.json
| `turnLaneArrows` | | 见下 | 从 OSM `turn:lanes:*` 生成自定义车道箭头的发布开关 |
| `osm2streets` | | 见下 | 透传给 osm2streets 的选项 |
| `blender` | | 见下 | Blender 侧选项 |
| `compress` | | 见下 | 显式 `compress` 阶段的 GLB 压缩选项 |
| `compress` | | 见下 | 默认交付压缩阶段的 GLB 压缩选项 |
| `budget` | | 见下 | 区域 GLB 性能与体量预算 |
| `outputs` | | 从 `id` 推导 | 输出路径覆盖,逃生舱 |
@@ -65,15 +65,17 @@ 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供随后发布使用 |
| `package` | `true` | 将通过校验的静态模型原子发布到 `package/` |
| `preview` | `true` | 基于已发布 package 写验证预览及 `_preview/` 动态资源 |
`reimport``preview``compress` **在这里配也没用**——`normalizeAreaConfig` 把它
硬编码为 `false`,只能靠 `--stages` 显式请求。
`reimport` **在这里配也没用**——`normalizeAreaConfig` 把它硬编码为 `false`,只能靠
`--stages` 显式请求`compress``package``preview` 始终默认开启
> 恢复动作reimport、补丁动作preview和替代产物动作compress不该被一份
> 配置文件变成默认行为。
> 恢复动作reimport不该被一份配置文件变成默认行为;压缩、发布和验证预览是标准交付链的一部分。
`--stages` 会整体覆盖这里的默认值。
@@ -130,11 +132,13 @@ cp config/examples/template.json config/areas/my-area.json
|---|---|---|
| `treeStyle` | `"natural"` | 合法值见 `generate_scene.py``TREE_STYLES``natural``procedural``shapespark` |
| `officeOverrides` | `""` | 旧名 `office_overrides` 仍被接受 |
| `roadProvider` | `"native"` | Blender 道路来源。`"native"` 时仅使用 `native-road/` 的道路、路口和人行道面;`"osm2streets"` 仅用于显式 legacy/debug 构建。 |
### `compress`
只影响显式 `--stages compress`。默认压缩链是 texture resize + WebP transcode
不覆盖默认 `<area-id>.glb`
完整构建和显式 `--stages compress` 都使用此配置。默认压缩链是 texture resize + WebP
transcode成功后替换**package staging** 中的主 GLB 与 manifest未压缩源只保留在构建临时目录
只有随后的 `package` 阶段才会原子发布到 `package/`
| 字段 | 默认 | 说明 |
|---|---|---|
@@ -162,14 +166,16 @@ cp config/examples/template.json config/areas/my-area.json
### `outputs`(逃生舱)
默认全部从 `id` 推导`<outputRoot>/<id>/<fileStem>.<ext>`。需要定制时逐项覆盖:
默认全部从 `id` 推导。发布给下游的静态资产固定在
`<outputRoot>/<id>/package/`(含 `models/` 与可控信号灯 `runtime/`),临时静态资产在
`_pipeline/package-staging/`,预览专用车辆/路线资源在 `<outputRoot>/<id>/_preview/`。需要定制时逐项覆盖:
```json
{
"outputs": {
"areaDir": "/absolute/path/to/custom-area",
"blend": "/absolute/path/to/custom.blend",
"glb": "/absolute/path/to/custom.glb",
"packageDir": "/absolute/path/to/custom-package",
"cesiumPreview": "/absolute/path/to/custom-preview.html"
}
}
@@ -177,9 +183,14 @@ cp config/examples/template.json config/areas/my-area.json
可覆盖的键(`scripts/lib/area-config.js``areaDir``fileStem``geojsonDir``gpkg`
`qgisProject``qgisPreview``blend``render``glb``metadata``cesiumPreview`
`compressedFileStem``compressedGlb``compressedMetadata``compressedCesiumPreview`
`vehicleRoute``vehicleModel``pipelineDir``stageManifestDir`
静态发布路径另有 `packageDir``packageStagingDir``packageManifest`
`packageStagingManifest``packageModelDir``packageStagingModelDir`
`packagePrimaryGlb`;预览路径另有 `previewDir``previewDescriptor`
`trafficSimulation`native preview 的可迁移仿真描述符)。除非在迁移旧调用,
不要覆盖 `glb` / `metadata`:它们是 staging 内部路径,不是下游资产入口。
**优先改 `fileStem` 或 `areaDir`**——它们能一次性影响全部派生路径。逐个覆盖容易漏。
---
@@ -221,7 +232,8 @@ cp config/examples/template.json config/areas/my-area.json
| 布尔字段用 `\|\|` 兜底 | `false` 被翻转 |
| 给新字段造顶层平铺别名 | 扩大历史包袱 |
| 逐个覆盖 `outputs` 而不用 `fileStem` | 漏掉某个产物路径 |
| `stages` 里配 `reimport` / `preview` / `compress` | 无效,被硬编码为 false |
| `glb` / `metadata` 当作下游入口 | 它们位于 staging应只读取 `package/manifest.json` |
| 在 `stages` 里配 `reimport` | 无效,被硬编码为 false |
| 加数值字段不做范围校验 | 错配置在中途才崩,输出已被破坏 |
---

View File

@@ -21,13 +21,14 @@
全部是 CommonJS`package.json``"type": "commonjs"`),无构建步骤、无 TypeScript、
零运行时依赖(唯一依赖 `osm2streets-js-node` 只被 `build-osm2streets-qgis.js` 用)。
## 可选 GLB 压缩阶段
## 默认 GLB 压缩阶段
### 1. Scope / Trigger
`compress`显式请求的替代产物阶段,不属`all`,也不覆盖默认 `<area-id>.glb`
它用于在已有 Cesium GLB 上生成并列压缩 GLB、metadata 和预览页。当前主路径是
texture resize + WebP transcode。
`compress`完整构建的默认阶段,`cesium``package` 之间。它在
`_pipeline/package-staging/` 的主 GLB 上执行 texture resize + WebP transcode并替换 staged
主 GLB 与 staged manifest不触碰已发布的 `package/`,也不重写 preview HTML。未压缩版本只在
`<areaDir>/_pipeline/compress-*` 临时目录中存在,成功或失败后都会清理。
`scripts/compress-glb.js` 是该阶段调用的低层脚本,也可单独运行做实验。
@@ -62,20 +63,17 @@ npm run compress:glb -- --input in.glb --output out.glb [options]
### 3. Contracts
- 输入必须是现有 `.glb` 文件;`--output` 必须不同于 `--input`
- 输出是并列压缩 GLB默认构建产物不被替换
- `compress` 阶段依赖默认 `glb``metadata` `cesiumPreview` 已存在。
- 低层脚本仍输出到与输入不同的路径;`build-area.js` 使用临时输入/输出路径,避免原地压缩
- `compress` 阶段依赖 staged `glb``metadata` 已存在,并在全部临时交付文件生成和校验后
`renameSync` 替换它们。
- 默认链固定为 `gltf-transform resize -> gltf-transform webp`
- `compress` 阶段默认 `textureSize=768`;低层脚本单独运行时默认 `--texture-size 1024`
- `build-area.js` 默认写:
- `<fileStem>-compressed-webp768.glb`
- `<fileStem>-compressed-webp768.json`
- `<fileStem>-compressed-webp768-cesium-preview.html`
- 伴生 metadata 的 `asset``id="main"` 资产 URL 改为压缩 GLB 文件名;其余资产(包括
`category="semantic"` 的 Cesium 分类检查 GLB必须原样保留。
- preview HTML 只替换 `window.OSM_ASSET_PREVIEW_CONFIG``glbName` /
`metadataName` 和 loading 文案,不改 preview runtime。
- `build-area.js` 不会写并列 `-compressed-*` 交付物;`package` 是唯一能发布静态文件到
`package/` 的阶段。
- staged manifest 的 `assets[*].uri` 必须保持 package-relative`models/<area>.glb`;分类
layer GLB 必须原样保留。
- 成功时 stdout 打印 `GLB_COMPRESS_DONE <json>`包含压缩前后大小、image /
non-image bytes、结构计数、扩展metadata / preview 输出路径。
non-image bytes、结构计数、扩展metadata 输出路径。
### 4. Validation & Error Matrix
@@ -86,27 +84,24 @@ npm run compress:glb -- --input in.glb --output out.glb [options]
| `--output` 等于 `--input` | 抛错,避免覆盖源 GLB |
| 数值参数超范围 | 抛错并指出合法范围 |
| `gltf-transform` 退出非零 | 抛错并带上 status / signal |
| `--preview` 没有 `--metadata` | 抛错,因为 preview 必须指向存在的 metadata |
| preview HTML 找不到配置块 | 抛错,不做猜测替换 |
| `--stages compress` 但默认 preview 不存在 | `Cesium preview not found: <path>` |
| `--stages compress` 但 staged manifest 不存在 | `Cesium metadata not found: <path>` |
### 5. Good/Base/Bad Cases
- Good: `--stages cesium,compress` 先重导默认 GLB再生成并列压缩产物
- Good: `--texture-size 768 --metadata --preview` 生成压缩 GLB、metadata、HTML
源 GLB 保持不变。
- Good: 默认构建先导出 Cesium再压缩 staged 资产,最后由 `package` 发布
- Good: `--stages compress` 对已有 staged 交付物重新压缩,临时源 GLB 不会留在输出目录。
- Base: 只传 `--input --output` 生成压缩 GLB不生成伴生文件。
- Bad: 使用 `--meshopt` 后没有做 Cesium 兼容性验证就当默认产物发布。
### 6. Tests Required
- `node --check scripts/compress-glb.js`
- `npm run test:compress-glb`:断言压缩 metadata 只替换主资产,不丢失语义资产
- `npm run test:compress-glb`:断言压缩 manifest 只替换主资产,不丢失语义 layer
- `node --check scripts/build-area.js`
- 对目标区域跑一次 `npm run compress:glb -- ... --metadata --preview`
- 对目标区域跑一次 `npm run build:area -- --stages compress`
- `node scripts/glb-digest.js <compressed.glb>` 确认可解析结构和扩展
- 浏览器/Cesium 预览压缩 HTML确认 `EXT_texture_webp` 在目标环境可加载
- 对目标区域跑一次 `npm run compress:glb -- ... --metadata`
- 对目标区域跑一次默认 `npm run build:area`
- `node scripts/glb-digest.js <area>.glb` 确认可解析结构和扩展
- 浏览器/Cesium 预览标准 HTML确认 `EXT_texture_webp` 在目标环境可加载
### 7. Wrong vs Correct
@@ -116,10 +111,110 @@ Wrong:
npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a.glb
```
Correct:
Correct for the low-level script:
```bash
npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a-compressed-webp768.glb
npm run compress:glb -- --input outputs/a/a.glb --output /tmp/a-compressed.glb
```
---
## 可复用资产包发布
### 1. Scope / Trigger
`package` 将已验证的 static staging 提升为下游可消费的唯一交付边界:
`<areaDir>/package/`。它不复制大文件到第二个发布目录;成功时将整个 staging 目录原子 rename
为 package。车辆、路线和动态信号属于预览能力必须留在 `<areaDir>/_preview/`,不能进入 package。
### 2. Signatures
```bash
npm run build:area -- --config config/areas/<area>.json --stages cesium,compress,package
npm run build:area -- --config config/areas/<area>.json --stages preview
node scripts/test-package-contract.js
node scripts/test-package-examples.js
```
下游入口永远是:
```text
outputs/<area>/package/manifest.json
```
### 3. Contracts
发布 manifest 使用 `schema: "osm-asset-package/v1"`,至少包含:
```json
{
"schema": "osm-asset-package/v1",
"packageVersion": "1.0.0",
"areaId": "example",
"coordinateSystem": { "axes": "ENU", "units": "meters", "x": "east", "y": "north", "z": "up" },
"placement": { "longitude": 114.3, "latitude": 30.5, "height": 0, "headingCorrectionDegrees": -90 },
"bounds": { "minLon": 114.2, "minLat": 30.4, "maxLon": 114.4, "maxLat": 30.6 },
"assets": [{
"id": "main",
"role": "scene",
"category": "scene",
"uri": "models/example.glb",
"defaultLoad": true,
"integrity": { "bytes": 123, "sha256": "..." }
}]
}
```
- `coordinateSystem` 固定为 ENU meters`placement` 是 WGS84 锚点与 heading correction。
- 必须恰有一个 `role="scene"` / `category="scene"` / `defaultLoad=true` 主资产roads、buildings、
vegetation、water 是 `role="layer"``defaultLoad=false` 的可选语义层。
- `assets[*].uri``runtime[*].uri` 只能是包内正向相对路径,禁止绝对路径、反斜杠和 `..`
- `runtime` 声明可编程运行时资产;当前至少包含 `traffic-signals` 锚点 JSON、
`traffic-signals-dynamic` 灯珠 GLB以及两个 `traffic-signals-countdown-*` 倒计时 GLB。
它们是资产包的一部分,不属于 `_preview/`
- `package` 在晋升前调用 `validateManifest()`;再为每个模型写 bytes 与 SHA-256然后复验。
- `package` 是唯一拥有最终 package file records 的 stage。`cesium` / `compress` 只能拥有 staging
或计算摘要,不能留下指向最终 package 文件的 outputs record。
- 下游代码和 preview 都从 manifest URL 解析 `assets[*].uri`。参考
`examples/cesium-asset-package.js``examples/three-asset-package.js`
### 4. Validation & Error Matrix
| 条件 | 结果 |
|---|---|
| staging manifest 缺失或无效 JSON | `package` 失败,不替换上次成功 package |
| URI 是绝对路径、含 `..``\\` | `validateManifest()` 失败 |
| ENU / WGS84 字段缺失或越界 | `validateManifest()` 失败 |
| 没有或多于一个 scene asset | `validateManifest()` 失败 |
| manifest 声明的模型文件缺失 | `validateManifest()` 失败 |
| rename 发布异常 | 尝试恢复 `<package>.previous`,保留原 package |
### 5. Good/Base/Bad Cases
- Good: 下游只带走 `package/`,按 manifest 的 WGS84 placement 放置主模型,按需加载 layer。
- Base: 只有 main scene 的区域仍是合法 package没有几何的语义类别不写空 GLB。
- Bad: 将 `_preview/` 的车辆模型或路线加进 package它们是本项目的验证运行时信号灯 runtime 则必须发布。
### 6. Tests Required
- `npm run test:package-contract`schema、URI、placement、唯一 scene 和模型存在性。
- `npm run test:package-examples`Cesium / Three.js resolver 都相对 manifest 解析 URI。
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium,compress,package,preview`
- `npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
-`package/` 复制到其他目录后,重新运行 manifest validator所有 `assets[*].uri` 必须仍能解析。
### 7. Wrong vs Correct
Wrong:
```js
const modelUrl = asset.uri; // Relative to preview HTML by accident.
```
Correct:
```js
const modelUrl = new URL(asset.uri, new URL(manifestUrl, window.location.href)).href;
```
---
@@ -272,6 +367,341 @@ if (!networkSaysIntersection && (roadCounts.get(endpoint.id) || 0) < 3) return n
fs.writeFileSync(diagnosticsPath, `${JSON.stringify(diagnostics, null, 2)}\n`);
```
## Native 道路标线
### 1. 范围与触发条件
`node scripts/compile-native-roads.js --config <area>` 将原生道路标线写入
`outputs/<area>/native-road/layers/`。它不读取 osm2streets 渲染几何;只复用
`turn-lane-arrows.js` 中已测试的箭头模板。工作台 `GET /api/state` 原样服务这些
GeoJSONnative Blender 构建通过 `catalog.NATIVE_ROAD_LAYERS` 消费它们。
### 2. 图层契约
| 文件 | 语义 | 必需 provenance | Blender material layer |
|---|---|---|---|
| `lane_separators.geojson` | 同向相邻车道的分隔线 | `native-road-lane-separator/v1` | `lane_separators` |
| `direction_arrows.geojson` | 沿定向车道重复的直行方向箭头 | `native-road-direction-arrow/v1` | `lane_arrows_webscale` |
| `turn_arrows.geojson` | 明确 `turn:lanes` 的路口动作箭头 | `native-road-turn-arrow/v1` | `lane_arrows_webscale` |
方向箭头必须带 `road_id``lane_id``osm_way_ids``direction``lane_index`
`sequence``distance_along_lane_meters``placement_interval_meters`。转向箭头
必须带 `maneuver``placement_distance_meters`。两者不能共用 provenance 或假装为
彼此:前者表达沿路行驶方向,后者表达路口处允许动作。
### 3. 放置与错误矩阵
| 条件 | 结果 |
|---|---|
| 车道长度不足以容纳两端 14m 缓冲 | 不生成道路方向箭头 |
| 可用车道长度 | 从 14m 起按 32m 间距生成 `through` 箭头 |
| OSM 未提供 `turn:lanes` | 不生成路口转向箭头 |
| `turn:lanes` 存在但动作不受已测试模板支持 | 记录诊断,不猜测动作 |
| native Blender 构建缺任一图层文件 | 在 `ensureNativeRoadLayers()` 失败,不能静默漏画 |
### 4. 必需测试
- `npm run test:native-road`:方向箭头的 Polygon、provenance、间距以及无标签道路
不生成路口转向箭头。
- `npm run test:road-workbench`:方向箭头开关、选择溯源和概览标签。
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview --road-provider native`:日志必须列出
`direction_arrows``turn_arrows`,且不运行 `package`
### 5. 错误与正确写法
错误:把默认 `through` 当作路口 `turn:lanes` 动作,统一写入 `turn_arrows.geojson`
正确:道路方向箭头进入 `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` 返回该 FeatureCollectionnative
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. 范围与触发条件
@@ -607,7 +1037,7 @@ const gate = classifyAreaQuality(result);
### 1. Scope / Trigger
Stage manifest 是区域构建阶段或独立验证通过后的机器可读产物契约。它覆盖预检记录与完整区域链:
`preflight``intermediates``reimport``blender``cesium``preview``compress`
`preflight``intermediates``reimport``blender``cesium``compress``package``preview`
它用于诊断产物是否存在、是否 stale、体量是否超预算以及后续 `check:area` /
增量构建判断。
@@ -629,6 +1059,7 @@ Manifest 路径固定:
<areaDir>/_pipeline/stages/cesium.manifest.json
<areaDir>/_pipeline/stages/preview.manifest.json
<areaDir>/_pipeline/stages/compress.manifest.json
<areaDir>/_pipeline/stages/package.manifest.json
```
代码入口:
@@ -721,9 +1152,18 @@ File records use this shape:
- `summary.glb.extensionsUsed`
- `summary.budget`effective limits、usage 和 violationswarnings 来自同一个预算评估
`cesium` 会调用 preview 生成函数,但 preview HTML / route / vehicle model 的 freshness
所有权属于独立 `preview` manifest。否则单跑 `--stages preview` 会把 Cesium manifest
错误判 stale。
`cesium` 只拥有 staging 内的静态模型与 manifest完成后必须依次由 `compress``package`
交付。preview HTML / route / vehicle model 的 freshness 所有权属于独立 `preview` manifest
`compress` manifest 不记录最终文件 records它们位于可被 `package` rename 的 staging 目录。
`package` manifest 是唯一记录 `package/manifest.json`、主 GLB 与 package 目录的 stage manifest
避免发布后 compress manifest 因路径迁移而立刻 stale。
`package` manifest:
- `inputs.stagingManifest`:发布后的同一 manifest 文件记录
- `outputs.packageDir``outputs.manifest``outputs.primaryGlb`
- `summary.assets``summary.packageDir`
### Preview Assembly Boundary
@@ -758,14 +1198,11 @@ runtime 与 HTML并写 preview manifest。预览内容实现不得回流到
`compress` manifest:
- `inputs.glb`
- `inputs.metadata`
- `inputs.cesiumPreview`
- `outputs.compressedGlb`
- `outputs.compressedMetadata`
- `outputs.compressedCesiumPreview`
- `outputs.glb`
- `outputs.metadata`
- `outputs.cesiumPreview`
- `summary.sourceGlb`
- `summary.compressedGlb`
- `summary.glb`
- `summary.options`
- `summary.compressionRatio`
- `summary.savedBytes`
@@ -791,8 +1228,8 @@ Manifest files are written atomically via `*.tmp` then `renameSync`.
- Good: `--stages intermediates` 成功后写 `intermediates.manifest.json`,诊断显示
`ok intermediates`
- Good: `--stages blender` 成功后写 `blender.manifest.json`,诊断显示 `ok blender`
- Good: `--stages cesium` 成功后写 `cesium.manifest.json``preview.manifest.json`
- Good: `--stages preview` 只更新 preview manifest不让 cesium manifest stale。
- Good: `--stages cesium,compress,package,preview` 成功后依序写四份各自拥有的 manifest。
- Good: `--stages preview` 只更新 preview manifest不让 package manifest stale。
- Good: `--stages compress` 成功后写 `compress.manifest.json`summary 记录压缩比和节省字节。
- Base: 旧产物没有当前 ownership 路径的 manifest诊断显示 expected manifest missing
提示重跑对应阶段。
@@ -811,6 +1248,7 @@ Manifest files are written atomically via `*.tmp` then `renameSync`.
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages intermediates`
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender`
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium`
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages compress,package,preview`
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages preview`
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages compress`
- `npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
@@ -926,18 +1364,19 @@ 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` |(显式 legacyOSM → 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` |
| `cesium` | 场景 → GLB + 元数据 + 预览页 | `.blend` | `.glb``.json`、预览 HTML 及其静态资源 |
| `preview` | 只补生成预览页 | `.glb``.json` | 预览 HTML 及其静态资源 |
| `compress` | 生成并列压缩 Cesium 产物 | `.glb``.json`、默认预览 HTML | `-compressed-webp*.glb/json/html` |
| `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` |
| `preview` | 用已发布 manifest 生成验证预览 | `package/manifest.json`、动态输入 | 预览 HTML、静态 runtime、`_preview/` 动态资源 |
调度是顶层的阶段 `if``build-area.js` 开头),顺序固定,**阶段之间不传内存状态,
只通过磁盘产物耦合**。这就是单跑某个阶段能work 的原因。
`cesium` 阶段结束时会直接调 `writeCesiumPreview(area)``build-area.js:285`),所以
`preview` 只在"已有 GLB、只想重生成 HTML"时才需要单独跑
完整构建中 `preview``package` 后运行。单跑 `preview` 用于已发布 package 但想重生成
验证 UI 或 `_preview/` 动态资源的情况
### 别名
@@ -947,16 +1386,15 @@ 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"],
// Legacy intermediates/reimport are deliberately absent from 'all'.
all: ["blender", "cesium", "compress", "package", "preview"],
```
`preview` 同样不在 `all` 里——`cesium` 已经包含它。`compress` 也不`all` 里——
它生成的是替代压缩产物,不是 baseline GLB
`compress``package``preview` `all` 中,确保完整构建以压缩后的可复用静态 package
以及可运行的验证预览交付
### `intermediates` 与 `reimport` 互斥
@@ -966,9 +1404,8 @@ all: ["intermediates", "blender", "cesium"],
这是**显式拒绝而不是警告**——两者同时开,无论谁先跑,另一个的工作都白做。
`normalizeAreaConfig``stages.reimport``stages.preview` `stages.compress` 硬编码为 `false`
**不能从配置文件打开**,只能靠 `--stages` 显式请求
恢复动作、补丁动作和替代产物动作都不该被一份配置文件变成默认行为。
`normalizeAreaConfig`只有 `stages.reimport` 硬编码为 `false`,只能靠 `--stages` 显式请求;
`stages.compress``stages.package` `stages.preview` 为默认 `true`
---
@@ -1005,7 +1442,7 @@ parity 校验依赖 stage 的 stdout 标记来判断阶段是否跑到(如 `SC
| 在阶段函数里现拼输出路径 | 路径规则出现第二份定义 |
| 低层脚本直接读 `config/areas/*.json` | 打破两层配置边界 |
| 布尔配置用 `\|\|` 兜底 | `false` 被翻转成默认值 |
| 让 `reimport` / `preview` 能从配置文件默认开启 | 恢复动作变成常规行为 |
| 让 `reimport` 能从配置文件默认开启 | 恢复动作变成常规行为 |
| 新阶段忘了 `ensureFile` 前置校验 | 单跑时报底层堆栈而非人话 |
| 改 stage 的 stdout 标记 | 静默破坏 parity 契约 |
| 顺手把多份 `parseArgs` 合并 | 扩大 diff且独立入口的独立性是刻意的 |

View File

@@ -120,6 +120,56 @@ SCENE_LAYERS.forEach((layer, index) => {
## Blender 调用
### macOS Blender 4.5 的 Metal 启动兼容
#### 1. Scope / Trigger
`export_cesium.py` 在 macOS 的 Blender 4.5.12 后台启动时,可能在 Python 脚本加载前的 Metal 扩展探测中崩溃;这不是场景或道路数据错误。
#### 2. Signatures
Cesium 阶段的调用参数必须包含:
```text
--background --factory-startup --debug-gpu-force-workarounds --python blender/export_cesium.py -- ...
```
#### 3. Contracts
`--debug-gpu-force-workarounds` 是 Blender 的官方 CLI 参数。它只约束导出进程的 GPU 扩展探测,不改变 `.blend`、GeoJSON 或导出脚本的输入输出契约。
#### 4. Validation & Error Matrix
| 情况 | 结果 |
|---|---|
| 缺少该参数且启动时崩在 Metal 初始化 | 不应归因于道路数据;补齐参数后重跑 Cesium 阶段 |
| 参数存在且 `CESIUM_EXPORT_DONE` / stage manifest 写出 | 继续 GLB digest 与预览验证 |
#### 5. Good / Base / Bad Cases
- Good: 保留 `--factory-startup`,并在 Cesium 导出加入 workaround。
- Base: Blender 场景阶段未受影响时,不额外改变其启动参数。
- Bad: 为绕过启动崩溃删除 `--factory-startup`,这会重新引入本机偏好和 addon 的不确定性。
#### 6. Tests Required
- `npm run test:build-stages` 断言导出参数仍包含 workaround。
- 对目标区域运行 `--stages blender,cesium,preview`,并用 `glb-digest.js` 解析输出。
#### 7. Wrong vs Correct
Wrong:
```text
--background --python blender/export_cesium.py
```
Correct:
```text
--background --factory-startup --debug-gpu-force-workarounds --python blender/export_cesium.py
```
### 两种调用姿势
| 阶段 | 参数 | 出处 |

View File

@@ -30,7 +30,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.pyQGIS Python
@@ -45,23 +45,27 @@ 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]───────▶ compile-native-roads.js + Blender + blender/generate_scene.py
默认读 .osm + native-road/
│ → <id>.blend + <id>.png
│ → _pipeline/stages/blender.manifest.json
├─[cesium]────────▶ Blender + blender/export_cesium.py
│ 读 .blend → <id>.glb + <id>.json
→ 并自动执行 preview
│ 读 .blend → _pipeline/package-staging/models/<id>.glb
+ staged manifest动态预览 GLB 写入 _preview/
│ → _pipeline/stages/cesium.manifest.json
├─[preview]───────▶ 生成 <id>-cesium-preview.html
+ 拷贝 lib/cesium-preview.{js,css}
│ + 车辆巡航路线与模型
│ → _pipeline/stages/preview.manifest.json
├─[compress]──────▶ 压缩 staging 内主 GLB 并更新 staged manifest
→ _pipeline/stages/compress.manifest.json
─[compress]──────▶ 生成并列压缩 GLB / metadata / preview
_pipeline/stages/compress.manifest.json
─[package]───────▶ 校验 manifest 与全部静态模型,原子发布 package/
package/manifest.json + package/models/*.glb
│ → _pipeline/stages/package.manifest.json
└─[preview]───────▶ 生成 <id>-cesium-preview.htmlnative 可无车辆路线)
│ + 拷贝 lib/cesium-preview.{js,css}
+ _preview/ 车辆巡航路线、模型、动态信号
│ → _pipeline/stages/preview.manifest.json
```
**阶段之间只通过磁盘产物耦合**不传内存状态。这是单跑任意阶段能work 的前提。
@@ -87,7 +91,7 @@ config/areas/<id>.json
| 文件 | 行数 | 职责 |
|---|---|---|
| `build-area.js` | 约 530 | 主入口区域配置读取、阶段调度、preview 文件写入和 stage manifest ownership |
| `build-area.js` | 主入口:区域配置读取、阶段调度、package 发布、preview 文件写入和 stage manifest ownership |
| `diagnose-area.js` | 36 | 快速诊断入口:调用共享 area diagnostics 并打印完整报告 |
| `check-area.js` | 74 | 区域质量门入口:调用共享 area diagnostics输出 PASS/FAIL 并设置退出码 |
| `lib/area-diagnostics.js` | 776 | 共享区域诊断事实源OSM、产物、metadata、stage manifest、GLB digest 和质量门分类 |
@@ -99,6 +103,7 @@ config/areas/<id>.json
| `lib/vehicle-route.js` | 约 180 | 从 OSM 提取确定性预览巡航路线 |
| `lib/vehicle-model.js` | 约 150 | 生成内嵌 buffer 的预览车辆 glTF |
| `lib/area-preview.js` | 约 110 | 复制 preview runtime、生成 HTML 与转义配置注入 |
| `lib/package-contract.js` | package manifest 校验、相对 URI 与 SHA-256 完整性记录 |
| `lib/cesium-preview.js` / `.css` | 672 / 230 | 预览页运行时,见 [../preview/](../preview/index.md) |
| `normalize-lane-arrows.py` | 182 | 合并 osm2streets 的三角网箭头(跑在 QGIS Python 里) |
| `parity.js` | 270 | 产物一致性校验驱动 |

View File

@@ -8,8 +8,8 @@
## 定位
预览层是**验证性的,不是产物本身**。它加载 `cesium` 阶段导出的 `.glb` + `.json`
用来确认资产在真实 Cesium 里的样子。改这一层**不会**改变 Blender/GLB 资产。
预览层是**验证性的,不是产物本身**。它加载 `package/manifest.json`,用来确认已发布资产
在真实 Cesium 里的样子。改这一层**不会**改变 package 内的 Blender/GLB 静态资产。
车辆巡航同理——README 里写明它是"用于验证高精度巡航可用性的预览层功能"。
@@ -53,9 +53,14 @@ const config = window.OSM_ASSET_PREVIEW_CONFIG || {}; // :4
**加一个新的可配置项**`cesiumPreviewHtml()` 里加进注入的 JSONJS 侧从 `config` 读,
两边都要动。
`build-area.js` 只保留 GLB / metadata 依赖检查、写入顺序和 preview manifest ownership
`glbName``metadataName` 都是 `package/manifest.json`。浏览器读取 package manifest 后,
每个 `assets[*].uri` 必须相对**manifest 文件**解析,绝不能相对 preview HTML 解析;否则将
错误请求 `outputs/<area>/models/...` 而不是 `outputs/<area>/package/models/...`
`build-area.js` 只保留已发布 package / 动态输入的依赖检查、写入顺序和 preview manifest ownership
不要把 HTML 模板、runtime copy 或转义实现移回阶段调度器。路线 JSON 与车辆 glTF 分别由
`vehicle-route.js``vehicle-model.js` 生成,二者都是不启动外部工具的 Node 模块。
交通信号灯 runtime 则从 package manifest 的 `runtime` 读取;预览只负责驱动其状态,不拥有这些文件。
---
@@ -185,6 +190,21 @@ let baseStatus = "";
这会让用户误判材质和模型质量。若确实需要性能模式,应做成显式开关,而不是默认牺牲
预览清晰度。
### 车辆事件仅属于预览会话
点击车辆的信息卡可在 `normal``breakdown``accident` 三态间切换。这是验证交互,
不得写入 `package/`、路线 JSON 或 OSM。状态必须附着在 `addCruiseVehicle()` 返回的记录上;
`createTrafficAwarePositions()` 只在状态为 `normal` 时推进已有的 route distance恢复正常从
当前停点继续。
- `breakdown`:黄色扳手 label路线保留原色。
- `accident`:红色警示 label路线设为红色。
- `normal`:隐藏 label恢复原路线颜色。
车辆实体以 `properties.vehicleId` 标识;点击拾取必须只处理此属性,不能把静态模型、路线或
信号灯当作车辆。Cesium InfoBox 在本预览中关闭,信息卡必须使用 HTML/CSS并把新增 DOM 句柄
集中在 `cesium-preview.js` 顶部。
### 单资产 vs 多资产的开关
```js

View File

@@ -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
完全一致,最终画面仍会随离锚点距离产生横向偏移。

View 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."}

View File

@@ -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.

View 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."}

View File

@@ -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.

View File

@@ -0,0 +1,38 @@
# Native preview traffic simulation and migration contract
## Goal
Extend the native road compiler preview with a deterministic, portable vehicle traffic demonstration that consumes native road topology and the already-validated traffic-signal runtime. The result must be useful as a visual validation tool and as a migration reference for Cesium, Blender, or another platform consuming the generated area package.
## Confirmed Baseline
- Native road compilation is the production authority; QGIS/osm2streets is not a runtime input.
- The previous traffic-signal work is the source of truth and must remain compatible: deterministic `signal_uid`, `phase_group`, `mast_heading_deg`, `face_heading_deg`, stop-line anchors, shared `pose.*`, dynamic lens/countdown assets, and disabled-signal filtering.
- Existing browser preview already has signal phase timing, signal visualization, route stop matching, vehicle incident cards, and a missing-route fallback. Native preview currently omits the route.
- Native road output contains compiled roads/connectors and `vehicle_stop_lines.geojson`; the native package contains `runtime/traffic-signals.json`.
- The feature is preview-level deterministic behavior, not a legal navigation or microscopic traffic simulator.
## Requirements
- R1: Generate a native route artifact from `native-road/compiled.json` and native layer geometry; it must not read `osm2streets_web_out`, QGIS files, or legacy route files.
- R2: Preserve and consume the existing traffic-signal contract without deriving a second signal layout in the browser.
- R3: Vehicles must stop before a red/yellow signal at the native stop line, resume on green, and expose the active stop reason in preview diagnostics.
- R4: Multiple vehicles on the same route must maintain a configurable minimum gap; a stopped front vehicle must cause following vehicles to decelerate and queue rather than overlap it.
- R5: Route, signal, vehicle, stop-event, and following-distance data must be written as versioned, package-adjacent JSON with stable ENU/WGS84 and migration metadata. Another platform must be able to consume the artifact without executing the Node compiler.
- R6: The browser preview must degrade gracefully when there are no valid routes or signals; the primary scene remains usable and diagnostics explain the missing optional capability.
- R7: Documentation must describe the data flow, schemas, coordinate conventions, signal identity, stop-line association, update loop, and migration/rollback guidance.
## Acceptance Criteria
- [ ] Native build emits a versioned route/traffic-simulation descriptor under the area output and preview loads it without any legacy directory present.
- [ ] At least one route crosses a native connector and contains a native stop-line association.
- [ ] A red/yellow phase visibly stops a vehicle before the stop line; green releases it.
- [ ] Two or more vehicles never overlap and maintain the configured minimum gap while cruising and while queued at a signal.
- [ ] Disabled native signals are absent from runtime control and do not create vehicle stops.
- [ ] Preview diagnostics report route count, signal count, stopped vehicles, queue length, and the active simulation descriptor version.
- [ ] Migration documentation and focused tests allow another consumer to reproduce route, signal, stopping, and gap behavior from generated assets alone.
- [ ] Existing traffic-signal tests and native road tests remain green; legacy provider behavior is unchanged.
## Decided Scope
- Simulation fidelity: MVP is a deterministic preview simulation with configurable speed, acceleration/deceleration, reaction time, and minimum gap. A full lane-changing, collision, priority, and multi-intersection traffic engine is deferred.

View File

@@ -0,0 +1,26 @@
{
"id": "native-preview-traffic-simulation",
"name": "native-preview-traffic-simulation",
"title": "Native preview traffic simulation and migration contract",
"description": "",
"status": "in_progress",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-18",
"completedAt": null,
"branch": null,
"base_branch": "feature/native-road-compiler",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1,4 @@
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Verify package stage ordering, manifest freshness, and external-tool boundaries."}
{"file":".trellis/spec/config/index.md","reason":"Verify all new output paths remain centralized in area-config."}
{"file":".trellis/spec/preview/index.md","reason":"Verify preview remains a consumer of package assets, not part of the published package."}
{"file":".trellis/spec/guides/artifact-parity-guide.md","reason":"Assess intentional output-contract changes and asset verification scope."}

View File

@@ -0,0 +1,135 @@
# Asset Package Contract Design
## Product Boundary
`outputs/<area-id>/` remains the build workspace. Its `package/` child is the only
publishable subtree and may be copied unchanged to another project. The package has
no dependency on the parent directory, repository checkout, absolute local paths,
QGIS, Blender, or the Cesium preview.
```
outputs/<area-id>/
package/ # publishable boundary
manifest.json # asset-package/v1
models/
<area-id>.glb # compressed complete static scene
roads.glb
buildings.glb
vegetation.glb
water.glb
<area-id>-cesium-preview.html # local verification only
_preview/ # vehicles, routes, dynamic signal runtime
_pipeline/ # staging, manifests and build diagnostics
osm2streets_web_out/, *.gpkg, *.qgz, *.blend, *.png
```
The package does not duplicate the GLBs: Cesium export and compression target the
package model paths. Build staging lives under `_pipeline/` and is removed before a
successful package is published.
## Manifest v1
The JSON root uses `schema: "osm-asset-package/v1"`. Required top-level fields are:
| Field | Contract |
|---|---|
| `schema`, `packageVersion`, `areaId` | Stable identity and schema evolution point |
| `coordinateSystem` | Literal local ENU contract: X east, Y north, Z up, meters |
| `placement` | WGS84 anchor longitude/latitude/height plus heading correction degrees |
| `bounds` | WGS84 `minLon`, `minLat`, `maxLon`, `maxLat` |
| `assets` | Deterministic list of declared publishable assets |
| `sceneStats` | Optional descriptive OSM-derived counts, not loading-critical |
Each asset has `id`, `role`, `category`, `uri`, and `defaultLoad`. `uri` is a
forward-slash relative path inside `package/`; it must not be absolute, start with
`/`, contain `..`, or resolve outside the package. Roles are `scene` for the complete
static scene and `layer` for separately loadable semantic subsets. Categories are
fixed in v1: `scene`, `roads`, `buildings`, `vegetation`, `water`.
`models/<area-id>.glb` is the only `scene` entry and defaults to load. Layer GLBs are
optional alternatives for selective loading and default off; consumers must not load
both the complete scene and overlapping layers unless intentionally composing them.
The manifest deliberately excludes `source_osm`, `source_geojson`, arbitrary Cesium
JavaScript, vehicle routes, vehicle models, dynamic signal states, countdown models,
preview runtime paths, build timestamps and desktop paths. File digests and sizes may
be added under an optional `integrity` object only after they are computed from final
compressed files.
## Pipeline Design
### 1. Static export seed
`blender/export_cesium.py` continues to own static GLB generation, WGS84 bounds,
anchor and semantic collection selection. It must emit data suitable for a manifest
seed, not a preview metadata document. Static traffic-signal geometry already in the
main scene remains part of the static scene; animated traffic signal and countdown
collections are not package assets.
### 2. Package staging and compression
The build orchestrator owns output paths through `normalizeAreaConfig()`. It adds
explicit package paths and exports static GLBs to a package staging directory beneath
`_pipeline/`. Compression works only on staged package files and rewrites the staged
manifest's main GLB URI after successful compression. It never needs to copy or alter
preview HTML.
After path validation, file existence checks, and manifest validation, the package
stage promotes the whole staging directory to `outputs/<area-id>/package/`. Promotion
is directory-level and replaces a previous package only after the new one is valid.
On failure, the previous published package remains usable and staging is retained or
reported for diagnosis according to existing pipeline failure conventions.
### 3. Preview adapter
The existing Cesium preview remains a verification tool outside the package. It reads
the package manifest via its relative path, then loads preview-only route, vehicle and
dynamic-signal descriptors from `_preview/`. Dynamic traffic assets are declared in a
preview-specific descriptor, never appended to package `assets`. This preserves
existing high-precision cruise validation without making it a downstream requirement.
### 4. Stage and compatibility behavior
Canonical order becomes `intermediates/reimport -> blender -> cesium -> compress ->
package -> preview`. `package` is included in the full default build and is callable
explicitly to validate/publish existing staged static exports. `preview` stays an
optional/verification stage, not a package dependency.
Existing `npm run build:area -- --config ... --stages ...` remains supported. Root
level legacy GLB/JSON/HTML files are not deleted by the migration; they are not read
as a fallback by the new package contract. A clean full build produces the package as
the canonical downstream asset source.
## Consumer Examples
Examples live in repository source, outside individual packages:
- Cesium: fetch `manifest.json`, derive the ENU frame from `placement`, apply heading
correction, then load `assets` by `uri`.
- Three.js: load the selected model URL relative to the manifest URL; expose the same
ENU placement object to the host application's georeferencing adapter. It must not
imply that Three.js alone converts ENU to WGS84.
Both examples load the complete scene and demonstrate selecting a single `layer`.
They validate that URLs are resolved relative to the manifest rather than the current
page or repository root.
## Validation and Rollback
Unit tests validate schema fields, category/role legality, package-relative URI rules,
and rejection of omitted/extra files. An integration fixture constructs a minimal
package and verifies that Cesium and Three.js example resolvers choose identical
relative URLs and placement values. A target-area package test checks final compressed
GLB parsing and manifest references.
The package stage writes a stage manifest containing only final published records and
their integrity. Diagnostic checks distinguish missing package, invalid package, and
stale package. Rollback is to the prior `package/` directory; legacy preview outputs
remain untouched throughout rollout.
## Deferred Decisions
- Rich per-feature semantics, routing graph delivery and simulation inputs are not in
v1; they should be introduced through a later schema version only when a downstream
consumer requires them.
- Asset coverage improvements are downstream of this contract work.

View File

@@ -0,0 +1,4 @@
{"file":".trellis/spec/pipeline/cli-and-stages.md","reason":"Stage ordering, manifests, external-command boundary, and compression contracts."}
{"file":".trellis/spec/config/index.md","reason":"Area output-path normalization and configuration contract."}
{"file":".trellis/spec/preview/index.md","reason":"Preview config injection and browser runtime compatibility."}
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"Blender-to-Node-to-browser coordinate and artifact contract risks."}

View File

@@ -0,0 +1,108 @@
# Asset Package Contract Implementation Plan
## Phase 0: Contract Lock-In
- [ ] Add a package-contract module that owns v1 schema constants, allowed roles and
categories, URI containment validation, manifest construction and validation.
- [ ] Add focused unit fixtures for valid manifests and each invalid condition:
missing schema/placement/bounds, malformed coordinates, invalid role/category,
absolute or traversing URI, duplicate asset ID, missing primary scene, and referenced
file outside the package.
- [ ] Document `package/` as the sole downstream boundary in README and pipeline/config
specs; retain a clear distinction between release assets and preview artifacts.
Gate: manifest v1 can be created and structurally validated without invoking Blender,
Cesium or QGIS.
## Phase 1: Package Paths and Static Export
- [ ] Extend `scripts/lib/area-config.js` with paths for `packageDir`, package manifest,
package model directory, primary GLB and semantic layer GLBs; keep all path derivation
in this module.
- [ ] Change `blender/export_cesium.py` metadata output to a static manifest seed:
bounds, ENU placement and static scene/layer declarations only. Remove local source
paths and inline Cesium source code from the published data.
- [ ] Direct static GLB output to package staging paths and retain dynamic signal/countdown
output as preview-only paths outside package staging.
- [ ] Update `build-area.js`, semantic asset checks, stage manifests and diagnostics to
consume the new path contract rather than constructing paths locally.
Gate: a Cesium export produces a valid static manifest seed and every declared static
asset exists under package staging; no preview-only asset appears in it.
## Phase 2: Compression and Atomic Publication
- [ ] Refactor the default compression stage to operate on staged package files,
preserving the final manifest shape while replacing only the primary GLB bytes and
integrity data.
- [ ] Add a `package` stage after `compress`; validate every asset and atomically promote
staging to `outputs/<area-id>/package/`.
- [ ] Update canonical stage ordering, `all`, interactive CLI labels and defaults.
- [ ] Ensure a failed compression/package validation never replaces an existing published
package; record diagnostics sufficient to identify the failed source/staging path.
Gate: a full build creates one publishable package with compressed primary GLB and no
duplicate static GLBs at the area root.
## Phase 3: Preview Adapter and Compatibility
- [ ] Change preview HTML generation to reference `package/manifest.json` relative to
the area output root.
- [ ] Move route/vehicle/dynamic-signal discovery into a preview-only descriptor beneath
`_preview/`, and make `cesium-preview.js` merge it after loading the package manifest.
- [ ] Preserve existing preview controls and graceful behavior when preview-only data is
absent; preview must still render the package static scene.
- [ ] Update `diagnose:area`, `check:area` and stage manifest freshness to distinguish
package artifacts from preview-only files.
Gate: the local Cesium preview loads the published package and continues to show
optional cruise/dynamic preview content without adding either to package manifest.
## Phase 4: Downstream Consumption Proof
- [ ] Add Cesium example code that resolves the manifest URL, validates placement and
loads the full scene or selected layers.
- [ ] Add Three.js example code with a manifest-relative resolver and an explicit ENU
placement handoff to the host's georeferencing integration.
- [ ] Add automated resolver tests so neither example can regress to page-relative,
desktop-absolute or repository-relative URLs.
- [ ] Publish consumer documentation including overlap rules for complete scene vs layers.
Gate: examples run from a copied `package/` directory and require no files outside it.
## Phase 5: Full Verification and Migration
- [ ] Run focused Node tests for package schema, stage resolution, compression metadata,
preview behavior, diagnostics and asset budgets.
- [ ] Run the full default build for the primary area, then `check:area` and package
integrity validation.
- [ ] Inspect the packaged GLB structure and validate the Cesium example in browser;
verify Three.js resolver output with the same manifest.
- [ ] Capture expected intentional output-contract changes in docs/changelog and update
relevant Trellis pipeline/config/preview specs.
- [ ] Preserve legacy root-level artifacts during rollout; document that downstream
consumers must migrate to `package/manifest.json` rather than treating legacy files
as fallback.
## High-Risk Boundaries
- `blender/export_cesium.py` to Node: static metadata fields and semantic asset file
names are a cross-runtime contract.
- Node packaging to preview runtime: preview-only dynamic data must never silently enter
the package manifest.
- Compression to publication: paths must remain package-relative and a partial write must
not replace the last valid package.
- Consumers: ENU axes and heading correction are load-bearing; examples must not create
a second, incompatible georeferencing convention.
## Validation Commands
```bash
npm run test:build-stages
npm run test:compress-glb
npm run test:preview-assets
npm run test:budgets
npm run test:preflight
npm run check:area -- --config config/areas/nantaizi-lake-innovation-valley.json
node scripts/glb-digest.js outputs/nantaizi-lake-innovation-valley/package/models/nantaizi-lake-innovation-valley.glb
```

View File

@@ -0,0 +1,61 @@
# Reusable asset package contract
## Goal
将当前面向 Cesium 预览的区域输出升级为可由多个下游项目稳定消费的静态资产包。资产包
必须有版本化 manifest、仅使用包内相对引用并明确发布资产与本项目调试/预览产物的边界。
## Confirmed Facts
- `blender/export_cesium.py` 已生成 `<area>.json`,包含主 GLB、roads/buildings/
vegetation/water 分层 GLB、WGS84 anchor、ENU 坐标约定、heading correction 和 OSM bounds。
- 该 JSON 同时包含桌面绝对 `source_osm` / `source_geojson` 路径、Cesium 代码片段,以及
动态信号灯和倒计时预览资产;因此不是可发布的下游契约。
- `outputs/<area>/` 还包含 QGIS、GeoJSON、Blender、Cesium HTML/runtime、车辆巡航和自检文件
它是构建工作目录,不是干净的发布目录。
- 当前默认 Cesium GLB 已在构建末端压缩,且 metadata 中的资产 URL 使用同目录相对文件名。
- 项目目标是生成可复用资产;交通仿真、跟车和信号调度属于下游运行时能力,不属于资产包首版。
## Requirements
- R1定义一个版本化 `manifest.json` v1描述区域 ID、WGS84 anchor、ENU 轴向与 heading
correction、WGS84 bounds、发布资产、语义类别和包内相对路径。
- R2资产包只含可复用的静态交付资产主场景和道路、建筑、植被、水体等分层模型必须有
明确的角色、加载语义和默认行为。
- R3manifest 和包内文件不得包含桌面绝对路径、构建临时目录、Cesium HTML/runtime、
QGIS、GeoJSON、`.blend`、车辆巡航或动态交通信号调度依赖。
- R4在不破坏现有区域预览、诊断和构建中间产物的前提下增加明确的资产包发布阶段。
- R5提供 Cesium 与 Three.js 的最小加载示例,均从 manifest 读取包内相对 URL并按相同的
WGS84/ENU 契约放置主场景或分层资产。
- R6定义发布资产、可选静态资产和本项目仅检查产物的分类规则并由测试验证。
- R7完整构建后的资产包应可独立复制到其他项目而无需本仓库的 `outputs` 目录结构或本地路径。
## Acceptance Criteria
- [ ] 目标区域生成一个可独立分发的资产包目录,其中仅有 manifest 和 manifest 引用的发布资产。
- [ ] manifest 的 schema version、坐标契约、bounds、所有资产类别和 URL 可由程序校验。
- [ ] 所有 manifest URL 都是安全的包内相对路径;不得含绝对路径、`..` 或未声明文件。
- [ ] 主 GLB 与每个发布分层资产都能在 Cesium 和 Three.js 示例中按 manifest 正确放置与加载。
- [ ] 车辆、巡航路线、Cesium preview HTML/runtime、QGIS/GeoJSON/`.blend` 和动态信号调度资产
不会进入发布包。
- [ ] 既有 `outputs/<area>/` 预览工作流继续可用,现有非交互 build 命令保持兼容。
- [ ] 发布包缺文件、manifest 路径越界、坐标字段无效或资产类别不合法时,构建/校验非零退出。
## Proposed Delivery Phases
1. 契约与目录边界:冻结 manifest v1 schema、发布目录结构、资产角色和坐标定义。
2. 资产包发布阶段:从现有 Cesium 导出与压缩结果收集、校验并写入独立包目录。
3. 下游消费证明Cesium / Three.js 示例仅依赖 manifest 与包内容,并覆盖主场景和按类别加载。
4. 质量门与迁移:增加结构、路径隔离、坐标和加载验证;保留旧预览输出并记录迁移规则。
## Key Decision
- 发布包根目录固定为 `outputs/<area-id>/package/`。该目录是可整体复制给下游项目的唯一
发布边界GLB 和 manifest 直接生成或移动到这里,避免与工作目录再保留一套大模型副本。
本项目的预览页可通过包内相对路径读取发布资产,但 HTML/runtime 本身不属于发布包。
## Out Of Scope
- 交通流、车辆行为、信号相位控制、路口调度和其他运行时仿真。
- 在本任务中扩展 OSM 到建筑、路灯、标志、植被等资产覆盖率或生成质量。
- 删除既有 `outputs/<area>/` 中由用户保留的历史调试产物。

View File

@@ -0,0 +1,26 @@
{
"id": "asset-package-contract",
"name": "asset-package-contract",
"title": "Reusable asset package contract",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-11",
"completedAt": "2026-08-12",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View 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."}

View File

@@ -0,0 +1,54 @@
# Interactive Area Build CLI Design
## Scope
Add a zero-dependency terminal interface for choosing an area configuration and one or more existing
pipeline stages. It delegates execution to the existing `build-area.js` entry point and does not change
the pipeline's stage implementations or generated artifacts.
## Architecture
```text
npm run build
-> scripts/interactive-build.js
-> terminal menus (TTY only)
-> node scripts/build-area.js --config <area> --stages <canonical-list>
-> existing build pipeline
```
Extract the stage metadata, aliases, canonical execution order, and mutual-exclusion validation from
`build-area.js` into a CommonJS module under `scripts/lib/`. Both the interactive script and
`build-area.js` consume this module, making the menu's options the same source of truth as execution.
## Interaction
1. Discover and sort `config/areas/*.json`; present a single-select area menu.
2. Present a multi-select stage menu with descriptions and selection markers.
3. Arrow keys move focus, space toggles, enter confirms, and Ctrl-C/Escape cancels.
4. The menu prevents or reports the `intermediates`/`reimport` conflict before spawning a build.
5. The selected stages are normalized to canonical pipeline order and passed to `build-area.js`.
The interactive entry requires both stdin and stdout to be TTYs. Otherwise it exits non-zero with an
instruction to use `npm run build:area -- --config ... --stages ...`; it never falls back to defaults.
## Compatibility
- `build-area.js` remains the execution owner and keeps its CLI flags and defaults.
- `npm run build:area` continues to invoke it directly.
- `npm run build` changes only from the former default build alias to the interactive wrapper.
- A successful `cesium` selection continues to generate preview output through the existing behavior;
selecting `preview` explicitly remains valid for a standalone preview refresh.
## Validation
- Unit-test stage normalization, alias expansion, ordering, and mutual exclusion.
- Unit-test config discovery and non-TTY refusal without launching a build.
- Syntax-check both CLI entry scripts.
- Manually exercise the menu in a TTY: select an area, select unordered stages, confirm the canonical
command preview, and cancel before execution.
## Risks And Rollback
Raw terminal input must always restore raw mode and cursor state on confirmation, cancellation, and
errors. The wrapper only spawns the unchanged existing build script, so rollback is deleting the new
interactive entry and restoring the `build` package script.

View 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."}

View File

@@ -0,0 +1,13 @@
# Interactive Area Build CLI Implementation Plan
1. Extract stage definitions, aliases, canonical ordering, and conflict validation from
`scripts/build-area.js` into a shared CommonJS module.
2. Update `build-area.js` to consume the shared resolver without changing non-interactive semantics.
3. Add `scripts/interactive-build.js` with TTY detection, area discovery, raw-mode selection menus,
cancellation handling, and child-process delegation to `build-area.js`.
4. Change `package.json` so `npm run build` calls the new interactive script while `build:area` remains
unchanged.
5. Add focused Node tests for stage planning and non-TTY behavior; do not invoke Blender, QGIS, or a
real area build in tests.
6. Run syntax checks, the focused tests, existing preflight/preview tests, and manually exercise the
interactive flow in a TTY.

View File

@@ -0,0 +1,53 @@
# Interactive area build CLI
## Goal
提供一个交互式区域构建入口,减少手写 `npm run build:area -- --config ... --stages ...`
的频率用户可选择区域和一个或多个构建阶段CLI 按既有阶段约束执行。
## Confirmed Facts
- 当前 `scripts/build-area.js` 支持 `intermediates``reimport``blender``cesium`
`preview``compress` 六个阶段,并接受逗号分隔的 `--stages`
- `intermediates``reimport` 互斥:前者会从 OSM 重建 GeoPackage后者回导 QGIS 编辑。
- 执行顺序固定为 `intermediates/reimport -> blender -> cesium -> preview -> compress`
`cesium` 当前会自动写入 preview 产物。
- 项目没有终端交互依赖;现有命令行调用必须继续可用于脚本和自动化。
## Requirements
- R1提供交互式入口列出可用区域配置并允许用户选择目标区域。
- R2交互式入口允许一次选择多个阶段并清晰展示阶段名称与用途。
- R3选择互斥阶段时必须阻止执行并解释原因不得静默选择其中之一。
- R4执行多个阶段时必须使用既有依赖顺序而非用户勾选顺序。
- R5保留 `npm run build:area -- --config ... --stages ...` 的现有非交互行为与语义。
- R6无 TTY、取消输入或无效输入时必须安全退出不启动任何构建。
- R7TTY 交互使用方向键移动、空格多选、回车确认;菜单应显示当前选择状态。
- R8`npm run build` 启动交互式入口;`npm run build:area` 继续保留为非交互入口。
## Acceptance Criteria
- [ ] 用户可从终端选择区域和多项阶段,无需手写配置路径或逗号分隔阶段名。
- [ ] 选择 `intermediates``reimport`CLI 明确报互斥错误且不运行构建。
- [ ] 选择乱序的多个阶段时,实际执行顺序仍遵循既有管线顺序。
- [ ] 既有非交互 `build:area` 调用保持兼容。
- [ ] 取消或在非交互环境调用不会触发构建。
- [ ] 用户可用方向键、空格和回车完成区域及阶段选择。
- [ ] `npm run build` 启动交互菜单,`npm run build:area` 的现有调用保持不变。
## Key Decisions
- 采用终端原生多选菜单,而非输入逗号分隔编号;避免引入运行时依赖。
- `npm run build` 作为日常交互入口;`npm run build:area` 继续服务自动化和精确调用。
## Out Of Scope
- 修改各 stage 的构建业务逻辑、依赖关系或产物格式。
- 将巡航路线、QGIS 图层编辑或区域配置编辑纳入本次交互流程。
- GUI 或浏览器界面的构建控制台。
## 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`.

View File

@@ -0,0 +1,26 @@
{
"id": "interactive-area-cli",
"name": "interactive-area-cli",
"title": "Interactive area build CLI",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-11",
"completedAt": "2026-08-11",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View 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."}

View File

@@ -0,0 +1,48 @@
# Design: Vehicle Incident And Information Preview
## Boundary
This is browser-only Cesium preview state. It changes neither generated OSM data nor `package/` assets.
Vehicle routes and models remain `_preview/` resources; refresh resets all vehicle event state.
## Data Model
Each object returned by `addCruiseVehicle()` gains:
```js
{
id: "vehicle-1",
modelName: "car_a01_002",
status: "normal" | "breakdown" | "accident",
incidentNote: "",
motion: { state },
markerEntity
}
```
The current route distance remains the single motion source. The clock tick leaves it unchanged while
status is not `normal`; returning to normal resumes from that position.
## Interaction Flow
1. Vehicle Cesium Entities carry a `vehicleId` property.
2. A `ScreenSpaceEventHandler` picks a clicked entity, resolves its vehicle, synchronizes the existing
vehicle selector, and opens a positioned information card.
3. The card displays vehicle number, model family, route label/length, configured speed, state, and note.
4. Card controls set normal/breakdown/accident and persist only in the in-memory vehicle object.
5. State changes update marker visibility, the route material, card content, and Cesium render request.
## Visual Contract
- Normal: no marker; original route color; motion active.
- Breakdown: yellow wrench label above the vehicle; route retains its original color; motion frozen.
- Accident: red warning-triangle label above the vehicle; route material becomes red; motion frozen.
- Marker positions use the same vehicle `CallbackProperty`, so they move with the vehicle before an event
and remain at the incident location while stopped.
## Compatibility And Risks
- Picking must ignore routes, static models, and signal entities.
- No route must retain the existing disabled cruise controls and never show a card.
- The card must be HTML/CSS UI, not Cesium InfoBox, because the viewer deliberately disables InfoBox.
- The feature must not alter signal runtime behavior or vehicle asset URLs.

View 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."}

View File

@@ -0,0 +1,17 @@
# Implementation Plan
1. Add an initially hidden vehicle information card to `area-preview.js` and responsive card styles to
`cesium-preview.css`.
2. Extend the browser runtime vehicle records with stable identity, parsed model metadata, incident status,
note, and a marker entity.
3. Gate route-distance advancement on normal state; add status transition helper for marker, route material,
and card refresh.
4. Add Cesium click picking and card controls. Keep the existing selector synchronized for follow/route use.
5. Add focused assertions to `test-preview-assets.js` for card wiring, status behavior, and marker/route
contracts.
6. Run syntax checks, preview test, stage test, target-area preview regeneration, and `check:area`.
## Rollback
Revert only the preview HTML/CSS/runtime/test files. No package, Blender, route, or OSM product files should
need rollback.

View File

@@ -0,0 +1,38 @@
# Vehicle incident and information preview
## Goal
在 Cesium 验证预览中,让使用者能够查看每辆巡航车辆的基础信息,并人为标记车辆事故/故障状态,直观看到该状态对车辆和路线展示的影响。
## Confirmed Facts
- 车辆是 `scripts/lib/cesium-preview.js` 创建的 Cesium Entity当前已有车型、路线、巡航速度、
跟随、显隐与路线选择。
- 车辆模型和路线都属于 `_preview/` 验证资源,不是 area package 的下游资产契约。
- 当前不存在车辆业务资料、事故状态、碰撞检测或自动事件推演。
- 项目定位是资产生成与验证,不能将交通流/事故仿真作为本任务的范围。
## Requirements
- 每辆预览车辆必须有稳定的显示编号、车型、当前路线、设定速度和行驶状态。
- 点击场景中的车辆后,必须弹出该车辆的信息卡,展示基础信息与当前事件状态;信息卡内完成状态与说明编辑。
- 使用者必须能将选中车辆在正常、故障和事故状态之间切换,并能提供简短事件说明。
- 故障和事故是不同状态:二者均停止巡航;故障使用黄色标记,事故使用红色标记并提示路线中断。
- 故障使用车辆顶部黄色扳手标记;事故使用红色警示三角标记,并将该车辆当前路线改为红色。
- 非正常车辆必须有可见的场景标记;恢复正常后继续按既有路线巡航。
- 事故/故障仅在当前浏览器预览会话中存在;刷新或重建预览后不保留。
## Out of Scope
- 自动碰撞检测、车辆间物理碰撞、自动事故生成。
- 信号配时、交通流、调度策略或持久化事件记录。
-`package/` 加入车辆、路线或事故数据。
## Acceptance Criteria
- [ ] 用户可点击任一可见车辆,弹出其基础信息、状态和事件说明;现有车辆选择控件仍可用于路线和跟随。
- [ ] 用户可标记故障或事故,并看到车辆停止、状态文本和明显的场景标记。
- [ ] 故障显示黄色扳手;事故显示红色警示三角且当前路线变红。
- [ ] 用户可恢复正常,车辆继续其现有路线;其他车辆不受影响。
- [ ] 无可用路线时,车辆信息和事故控件按既有禁用语义处理。
- [ ] 预览资产、路线与现有信号灯 runtime 继续可加载;不新增下游 package 内容。

View File

@@ -0,0 +1,26 @@
{
"id": "vehicle-incident-info",
"name": "vehicle-incident-info",
"title": "Vehicle incident and information preview",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-12",
"completedAt": "2026-08-12",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1,3 @@
{"file":".trellis/spec/pipeline/index.md","reason":"Check command ownership, disk artifact boundaries, and legacy pipeline compatibility."}
{"file":".trellis/spec/preview/index.md","reason":"Check browser workbench state, error handling, and no-build browser constraints."}
{"file":".trellis/spec/config/index.md","reason":"Check new area output/config normalization and compatibility."}

View File

@@ -0,0 +1,110 @@
# Native Road Compiler Workbench Design
## Architecture
The native compiler is an additive pipeline path. It owns a separate output
directory under an area and never writes into `osm2streets_web_out/`.
```text
OSM XML + native-road-overrides.json
|
v
Canonical Road Model
roads / endpoints / junction candidates / provenance
|
v
Native Geometry Compiler
road surfaces / initial junction surfaces / diagnostics
|
+--> native-road/compiled.json
+--> native-road/layers/*.geojson
+--> native-road/diagnostics.json
+--> native-road/comparison.json
|
v
Road Workbench HTTP service
browser map + inspect/edit/save API
```
The canonical model is the authority. Render layers, browser display data, and
future Blender compatibility adapters are derived from it.
## Commands And Ownership
- `npm run road:compile -- --config <area-config>` performs no browser work.
It reads OSM plus the persisted override file, writes a staged native-road
result, validates it, and atomically promotes the result directory.
- `npm run road:workbench -- --config <area-config>` compiles first unless
`--no-compile` is supplied, then starts a local HTTP server scoped to that
one area.
- The server exposes read-only compiler artifacts and one explicit save API
for validated overrides. It does not expose arbitrary filesystem paths.
- Existing `build:area`, `intermediates`, QGIS, Blender, Cesium, and package
paths remain unchanged in the first iteration.
## Data Contracts
### Canonical road model
Each road direction carries a stable ID derived from OSM identifiers, source
way IDs, endpoint node IDs, centerline, explicit/inferred attributes, applied
override IDs, and diagnostics. Junction candidates likewise use their OSM node
ID when available. Values include provenance such as `tag:lanes:forward`,
`inferred:highway-default`, or `override:<id>`.
Each `Movement` is a stable semantic record joining a connection, source and
target road/lane, turn class, provenance, and an optional connector geometry.
`geometryStatus="connector"` publishes a connector curve;
`geometryStatus="continuous"` means the lane centerlines meet at the node and
does not invent a zero-length curve; `deferred-too-long` retains the movement
while withholding unsafe geometry.
### Override file
`<area>/native-road-overrides.json` is versioned and human-reviewable. It
contains an array of uniquely identified changes whose targets are stable road
or endpoint IDs. Supported v1 records are `road` parameter overrides and
`junction-connection` decisions. The save endpoint validates schema, target
existence, finite values, and duplicate/conflicting edits before atomic write.
### Compiler artifacts
`<area>/native-road/compiled.json` is the workbench's single read model and
contains the canonical `movements` list.
`layers/` contains generated GeoJSON with source/provenance properties.
`diagnostics.json` contains severity, stable subject ID, source IDs, rule,
message, and optional geometry. `comparison.json` reports counts and coverage
against available osm2streets layers; it does not claim quality solely from
visual differences.
## Browser Workbench
The browser uses OpenLayers as its sole GIS runtime, served directly from the
local allowlisted `node_modules` packages with a browser import map. The map
renders fit-to-data OSM centerlines, native surfaces, optional osm2streets
reference layers, diagnostics, selected-object provenance, movements, and
overrides. This provides mature map selection and hit detection without a
framework or bundler.
The user can select a road or endpoint, edit only v1 fields, inspect the
resulting override record, explicitly save it, and recompile/reload. Saved
state is visibly differentiated from unsaved state. The workbench must not
offer freehand final-polygon editing, since that would break reproducibility.
## Geometry And Validation
V1 produces road segments from projected centerline offsets and terminal
cross-sections. It only generates a junction surface when endpoints satisfy
the supported ordinary T/cross shape and geometry checks; otherwise it emits a
diagnostic rather than inventing an invalid polygon. Validation detects
dangling endpoints, unclosed/self-intersecting rings, non-finite coordinates,
unsupported multi-level intersections, and source/topology ambiguity. Small
numerical cleanup may be explicit and recorded; semantic failures are never
silently repaired.
## Compatibility And Rollout
The first compiler's layers use existing render-layer names where meaningful,
but are stored separately. A later, explicitly enabled Blender provider option
may consume native layers after comparison gates pass. Delete/replace behavior
is out of scope; rollback is selecting the existing osm2streets pipeline.

View File

@@ -0,0 +1,3 @@
{"file":".trellis/spec/pipeline/index.md","reason":"Native compiler commands, artifacts, and area config extend the Node pipeline while preserving legacy stages."}
{"file":".trellis/spec/preview/index.md","reason":"The browser workbench is a new DOM runtime and must follow local preview loading and state conventions where applicable."}
{"file":".trellis/spec/config/index.md","reason":"New native-road output paths and config behavior extend the normalized area contract."}

View File

@@ -0,0 +1,32 @@
# Implementation Plan
1. Add area output/config normalization and command entrypoints for the native
compiler, preserving existing stage behavior and paths.
2. Implement a shared OSM road parser and canonical road/endpoints model with
source provenance, explicit versus inferred properties, and stable IDs.
3. Implement v1 override schema, validation, load/apply behavior, atomic save,
and focused unit tests.
4. Implement projected road segment geometry, supported T/cross junction
detection, native GeoJSON artifact emission, diagnostics, and comparison
summary.
5. Implement a dependency-free local workbench server and browser UI with
selection, provenance display, v1 parameter/topology editing, explicit save,
compile/reload, and error states.
6. Add native compiler tests using focused fixtures plus nantaizi analysis;
run existing relevant Node tests to confirm legacy behavior remains intact.
7. Compare nantaizi and at least one supplied problematic OSM sample. Record
metrics, unsupported cases, and follow-up work in task research.
## Validation
```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 test:build-stages
npm run test:preflight
npm run test:preview-assets
```
Browser validation includes loading the workbench, editing a road parameter,
saving, verifying the override file, recompiling, reloading, and confirming
provenance identifies the saved override.

View File

@@ -0,0 +1,93 @@
# Native road compiler workbench
## Goal
Build an incremental native road compiler for Chinese urban and campus OSM
data that can progressively exceed osm2streets in geometry quality,
explainability, and repeatable correction. The existing osm2streets pipeline
must remain usable while the native compiler is developed and compared.
The first deliverable is a browser-based Road Workbench. It must expose the
native compiler's source data, generated geometry, and diagnostics, allow
users to make small semantic/topology corrections, persist those corrections
as versionable overrides, and reload them automatically in later runs.
## Confirmed Facts
- Nantaizi currently works relatively well because its OSM data received
deliberate supplemental tagging; it still has missing boundaries and
polygons that cannot be closed.
- Other tested OSM inputs expose osm2streets sensitivity to input structure
and leave too much opaque, final-polygon repair work in QGIS.
- Existing Blender consumes the nine GeoJSON render layers from
`osm2streets_web_out/`; QGIS GeoPackage edits can currently be reimported
only as a whole batch.
- The repository has no existing interactive browser editing service. Existing
Cesium preview is a static, generated verification page.
## Requirements
- R1: Add a native-road-compiler path without replacing or regressing the
existing osm2streets path.
- R2: Parse OSM into a canonical, source-traceable road model with stable
references to OSM ways and nodes, explicit values versus inferred values,
and diagnostics.
- R3: Compile at least ordinary road segments and the initial supported
junction subset into the existing render-layer contract, allowing existing
Blender/Cesium consumers to be reused.
- R4: Provide a browser Road Workbench that overlays raw OSM topology,
generated geometry, osm2streets comparison geometry when available, and
compiler diagnostics.
- R5: The workbench must permit scoped user adjustments and save them to an
area-local, human-reviewable override file. Future compile and workbench
runs must load that file automatically.
- R6: Each generated object and diagnostic must be traceable to OSM source
IDs, compiler rule/inference evidence, and relevant override IDs.
- R7: Validate topology and geometry before publishing generated layers;
report unresolved semantic errors instead of silently disguising them as
geometric repair.
- R8: Develop against nantaizi plus problem inputs and report native versus
osm2streets comparison metrics.
## Scope Boundaries
- First implementation targets Chinese urban/campus roads, ordinary road
segments, T/cross junctions, directed/multi-lane roads, and data already
tagged in nantaizi where possible.
- Existing Blender, Cesium export, package format, building, vegetation, and
water generators are out of scope unless a compatibility adapter requires a
narrowly scoped change.
- Directly editing final render polygons is not the intended correction model;
generated layers remain derived output.
- Complex interchanges, arbitrary multilayer junctions, and full worldwide OSM
coverage are deferred until driven by concrete samples.
## Acceptance Criteria
- [ ] A native compile command produces a canonical road model, generated
layers, diagnostics, and comparison artifacts for a configured area without
changing the osm2streets output path.
- [ ] A browser command serves a Road Workbench for an area and clearly shows
source topology, generated output, diagnostics, provenance, and saved
overrides.
- [ ] A user can make the agreed first-scope override edits in the browser,
save them explicitly, and receive a durable area-local override artifact.
- [ ] Re-running compile or reopening the workbench applies saved overrides
automatically and exposes their provenance.
- [ ] The compiler reports invalid/unclosed geometry, dangling road ends,
and unresolved junction/lane ambiguity with source IDs.
- [ ] Nantaizi and at least one known problematic area can run through the
native analysis/preview path, with comparison metrics captured rather than
a claim based only on visual inspection.
## Key Decisions
- The first browser editing surface supports road parameters (width, directed
lane counts, left/right sidewalk state) plus junction endpoint
connect/disconnect decisions.
- Turn restrictions, stop lines, and crosswalk placement are deferred until
the compiler has a validated road/junction editing loop.
- Overrides are a versioned, human-reviewable JSON artifact owned by the area,
not edits to generated polygon layers.
- Native output and osm2streets output remain parallel during development;
neither silently overwrites the other.

View File

@@ -0,0 +1,50 @@
# Two-Area Native Road Comparison
## Runs
2026-08-14:
```bash
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
npm run build:area -- --config config/areas/hanyang-block.json --stages intermediates
npm run road:compile -- --config config/areas/hanyang-block.json
```
`comparison.json` is a coverage and diagnostic record. Feature counts are not
a geometry-quality score: osm2streets and the native compiler segment roads at
different levels.
| Area | Directional roads | Native surfaces | Native junctions | Movements | Internal ends | Manual candidates | osm2streets road surfaces |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| nantaizi-lake-innovation-valley | 34 | 19 | 6 | 46 | 4 | 2 | 50 |
| hanyang-block | 475 | 352 | 20 | 369 | 161 | 83 | 1826 |
## Observed Failure Modes
The hanyang osm2streets/QGIS run completed, but its log reported repeated:
- roads trimmed into oblivion;
- degenerate intersections that could not be collapsed because layers, names,
highway types, or lane specifications differ;
- intersection polygon requests with no roads.
The native compiler did not hide the related uncertainty. It reported 161
internal road ends. Eighty-three have one or more nearby, direction-compatible
candidate departures within 35 metres; these remain suggestions for explicit
review rather than automatic topology edits. The initial five one-way junctions
that lacked connector curves now publish road-surface envelopes based on their
semantic movements. Across hanyang, 369 movements are identified: 243 require
a connector curve, while 126 are continuous at their OSM node and intentionally
have no separate geometry.
## Resulting Priorities
1. Keep manual candidate suggestions and semantic overrides as the correction
path for near-miss topology. Do not bulk-connect candidates.
2. Expand ordinary junction support from 3/4 physical approaches only after
identifying a repeated unsupported topology; one-way movements that are
continuous at a node are already supported without fake connector geometry.
3. Add an inspectable movement artifact so turn geometry is not the only
representation of a road-to-road movement.
4. Use a visual review of a few explicit hanyang diagnostics before changing
connection-distance or road-class rules.

View File

@@ -0,0 +1,32 @@
{
"id": "native-road-compiler",
"name": "native-road-compiler",
"title": "Native road compiler workbench",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-13",
"completedAt": "2026-08-18",
"branch": null,
"base_branch": "feature/native-road-compiler",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [
"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": [],
"notes": "",
"meta": {}
}

View 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."}

View File

@@ -0,0 +1,77 @@
# Native Lane Markings Design
## Architecture
The native compiler remains the source of truth. It derives two new polygon
layers alongside its existing surface, sidewalk, lane-centerline, and
connector outputs:
```text
canonical directed roads + lane centerlines + junction cutbacks
|
+-- lane separators: paint polygons between adjacent same-direction lanes
|
+-- direction arrows: repeated through-arrow template on directed lanes,
| outside the reserved junction marking zone
|
+-- turn arrows: tested existing template, anchored to an incoming lane
only when that lane has an explicit supported turn:lanes value
```
No OSM2streets rendered geometry is consumed by this path. The existing arrow
template library is reused only as a geometry/style asset, so the native lane
ID and OSM tags remain the evidence for placement.
## Contracts
- `layers/lane_separators.geojson`: polygon FeatureCollection. Each feature
records `native_id`, directed `road_id`, adjacent lane indices, source OSM
ways, and `native-road-lane-separator/v1` provenance.
- `layers/turn_arrows.geojson`: polygon FeatureCollection. Each feature records
its `native_id`, `road_id`, `lane_id`, OSM way IDs, direction, lane index,
maneuver, template asset, placement distance, and placement provenance.
- Unsupported turn values, a missing usable incoming-lane segment, or an
insufficient pre-junction placement distance create a diagnostic and no
arrow geometry.
- `layers/direction_arrows.geojson`: polygon FeatureCollection. Each feature
records the native lane and directed road, OSM way IDs, a stable sequence
index, its distance along the lane, and `native-road-direction-arrow/v1`
provenance. It uses the tested `through` template but is not a turn claim.
- The workbench serves both layers, draws them separately from its current
centerline/connector debug layer, and selects them by `native_id`.
- `catalog.NATIVE_ROAD_LAYERS` maps the two native sources to the existing
`lane_separators` and `lane_arrows_webscale` Blender materials. The native
adapter does not extend the osm2streets scene-layer registry.
## Placement
An incoming lane is oriented in driving direction. A turn arrow is sampled
from that lane's endpoint backwards by the configured safe distance, staying
outside the junction cutback. Its template basis uses the sampled lane tangent;
therefore it is on and aligned with the lane rather than the OSM centerline or
a screen-space direction. Multiple template rings remain separate polygons.
Lane separators are narrow polygons centered between adjacent lane centerlines
on a single directional carriageway. They stop at the same junction cutbacks as
the lane centerlines. A one-lane direction produces none.
Direction arrows are sampled at a fixed road-scale interval along the same
directed lane centerline. Their candidates exclude both endpoint buffers and
the turn-arrow reserve at the incoming end. This preserves a readable repeated
direction cue without overlapping a turn instruction at a junction.
## Compatibility And Rollback
All new files are additive under `native-road/layers/`. The existing
osm2streets/QGIS layer contract and `package/` are unchanged. Selecting
`--road-provider osm2streets` remains rollback. Missing native marking files
are a native Blender build error rather than a silent omission.
## Risks
- OSM turn tags can be incomplete or incompatible with the inferred lane
count. These are diagnostics, not guessed arrows.
- Very short approaches can have no safe position before the cutback. They are
skipped with a source-traceable diagnostic.
- Blender and Cesium need a real native build to verify the mesh/material
contract, not only GeoJSON unit tests.

View 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."}

View File

@@ -0,0 +1,31 @@
# Implementation Plan
1. Add reusable template-placement helper to the existing turn-arrow module,
preserving its supported-asset gate and output ring shape.
2. Extend the native compiler with lane-separator, repeated road-direction
arrow, and explicit-turn-arrow polygon generation plus source diagnostics
and layer persistence.
3. Extend native output records/counts and Blender adapter mappings. Reuse
existing `lane_separators` and `lane_arrows_webscale` materials only.
4. Add Workbench layers, toggles, selection/provenance inspector entries, and
preserve the existing Workbench-only direction triangle behavior.
5. Add focused fixtures for supported turn placement, unsupported maneuver,
short approach skip, and separator geometry; run native/workbench/build
stage tests.
6. Build Nantaizi with `blender,cesium,preview --road-provider native`, inspect
the final preview, and confirm no `package/` publication occurred.
## Validation
```bash
npm run test:native-road
npm run test:road-workbench
npm run test:turn-lane-arrows
npm run test:build-stages
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
```
Rollback is selecting `--road-provider osm2streets`; no existing output path
is replaced.

View File

@@ -0,0 +1,74 @@
# Native road lane markings and turn arrows
## Goal
Complete the native-road visual language for Nantaizi before any cross-area
migration: lane separators, travel-direction markers, and OSM-backed turn
arrows must be inspectable in the Road Workbench and visible in Blender and
Cesium output.
## Confirmed Facts
- Native output currently contains directed lane centerlines and connector
curves, but it does not emit paintable lane-separator or turn-arrow polygons.
- The existing osm2streets path has tested arrow templates in
`scripts/lib/turn-lane-arrows.js`, the `lane_arrows_webscale` material layer,
and a matching Blender material. Reuse these instead of introducing a second
arrow style.
- Earlier reviews established that direction markers must sit on the OSM / lane
centerline, use a clearly directional sharp triangle, and never be treated as
a road-surface decoration that drifts sideways.
- Turn arrows must follow the actual incoming lane and be placed before its
junction, with OSM source way, direction, lane index, and maneuver retained
as provenance.
- Scope remains Nantaizi only. Existing osm2streets output remains untouched.
## Requirements
- R1: Native compilation emits polygonal lane-separator markings derived from
its own directed lane geometry.
- R2: Native compilation emits turn-arrow polygons for supported OSM
`turn:lanes` maneuvers, using the existing tested arrow templates and the
exact native incoming-lane centerline for placement.
- R2a: Native compilation emits repeated straight-ahead direction-arrow
polygons along directed lanes, matching the visual role of osm2streets'
ordinary road arrows. These are a separate layer from turn arrows, retain
their own provenance, and leave a clear buffer around junction turn arrows.
- R3: Native output preserves provenance for every marking: native road/lane
ID, OSM way IDs, direction, lane number, maneuver, and placement method.
- R4: The Workbench renders markings in a separately controllable layer and
exposes those provenance fields on selection.
- R5: The native Blender adapter consumes native marking layers through the
existing lane-separator and lane-arrow material layers; Cesium must receive
the same geometry through the exported GLB.
- R6: Unsupported, unplaceable, or ambiguous arrow inputs become diagnostics;
the compiler must not invent a maneuver.
## Acceptance Criteria
- [ ] Nantaizi native output contains valid polygon GeoJSON for generated lane
separators, repeated road direction arrows, and every supported, explicitly
tagged turn arrow.
- [ ] A Workbench user can toggle, select, and inspect a generated marker and
see its lane, OSM, maneuver, and placement provenance.
- [ ] A selected direction marker is geometrically aligned to its directed lane
centerline; a selected turn arrow is on its incoming lane before the junction.
- [ ] Blender scene output and Cesium GLB contain native lane markings and
arrows with the existing visual material language.
- [ ] Unit tests cover a normal supported arrow, an unsupported maneuver, and
an unsafe/too-short placement; native compile and final Nantaizi visual build
pass without publishing `package/`.
## Out Of Scope
- Inventing turn arrows for untagged lanes, traffic-control semantics, changing
QGIS/osm2streets layers, or processing another area.
## Key Decision
- Sharp travel-direction triangles remain a Workbench-only inspection aid.
They explain raw OSM node order after a road is selected.
- Repeated `through` direction arrows are final road markings, distinct from
both those debug triangles and OSM-backed junction turn arrows. They are
placed on native lane centerlines at a fixed interval and enter the same
Blender/Cesium material layer as turn arrows.

View File

@@ -0,0 +1,26 @@
{
"id": "native-road-lane-markings",
"name": "native-road-lane-markings",
"title": "Native road lane markings and turn arrows",
"description": "Complete Nantaizi native lane separators, travel direction and turn-arrow geometry through Road Workbench, Blender and Cesium before cross-area migration.",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P1",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-14",
"completedAt": "2026-08-14",
"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": {}
}

View 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."}

View File

@@ -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.

View 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."}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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": {}
}

View 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."}

View File

@@ -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.

View 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."}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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": {}
}

View 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."}

View File

@@ -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.

View 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."}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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": {}
}

View 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."}

View File

@@ -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.

View 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."}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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": {}
}

View 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."}

View 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."}

View File

@@ -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`.

View File

@@ -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": {}
}

View File

@@ -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."}

View File

@@ -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.

View File

@@ -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."}

View File

@@ -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.

View File

@@ -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`.

View File

@@ -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": {}
}

View File

@@ -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."}

View File

@@ -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.

View File

@@ -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."}

View File

@@ -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.

View File

@@ -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`.

View File

@@ -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": {}
}

View 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."}

View File

@@ -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.

View 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."}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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": {}
}

View 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."}

View File

@@ -0,0 +1,55 @@
# Design
## Source Of Truth And Migration
The native compiler owns a versioned area-local signal model and override
artifact. It is generated from OSM controls and native geometry inputs, then
edited by the Workbench without QGIS. The existing
`traffic_signal_assemblies.geojson` is a migration adapter: it can be imported
into the native model and exported for legacy QGIS/reimport workflows, but a
native compile never requires it to exist.
The existing `buildTrafficSignalFeatures()` and validation functions remain the
compatibility implementation for initial generation and import/export. Imported
features retain their legacy `signal_uid` where valid; newly generated native
features use the same deterministic identity rule so downstream runtime IDs do
not fork.
## Workbench API And Editing
`GET /api/state` adds the normalized native signal model, source control
metadata, migration provenance, and derived runtime signal records. A signal
edit is represented as an atomic replacement of the validated native signal
override artifact through a dedicated signal save endpoint. A separate import
or export action handles the legacy QGIS collection; road overrides remain in
their existing file and schema.
The browser uses stable `signal_uid` values. It supports:
- generate: choose an OSM traffic-signal control and arm, then create the
deterministic assembly using the existing generator contract;
- move: update the Point coordinates while retaining stop-line/source fields;
- rotate: update `heading_deg` with normalized degrees;
- delete: remove the assembly from the editable collection;
- edit enabled state, display ID, mast reach, z offset, and phase group.
Every save validates uniqueness, identity, finite geometry, source references,
and field ranges before an atomic write. Deleted features are absent from the
runtime output; disabled features remain in the editable/QGIS layer but are
omitted by `buildTrafficSignalsFromFeatures()`.
## Delivery Flow
The native road compile result includes signal assemblies and derived runtime
metadata without adding them to road geometry layers. Native Blender/Cesium
stages consume `traffic_signals.json` and dynamic GLB inputs generated directly
from the native model. The legacy QGIS adapter may materialize the old GeoJSON,
but it is not in the native build's critical path.
## Compatibility And Rollback
QGIS reimport continues to read/export the compatibility GeoJSON while the
legacy path remains unchanged. If native signal editing fails validation, the
previous atomic native override remains in place and the user receives a
field-level error. Rollback is selecting the legacy provider or exporting the
last native state to the QGIS adapter.

View 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."}

View File

@@ -0,0 +1,31 @@
# Implementation Plan
1. Define the native signal model/override artifact and migration adapter;
reuse existing generation, validation, and deterministic signal UID rules.
2. Add QGIS import/export commands that translate
`traffic_signal_assemblies.geojson` to/from the native artifact without
making native compile depend on QGIS.
3. Extend native compile/workbench state to expose signal assemblies, OSM
controls/arms, and derived runtime provenance.
4. Add validated atomic signal save operations for generate, move, rotate,
delete, and field edits; preserve legacy ID compatibility.
5. Add Workbench map styling, selection, editing controls, dirty state, save,
compile/reload, and clear error handling for signal assemblies.
6. Ensure native Blender/Cesium/preview stages consume runtime signal data
generated from the native model, while legacy stages remain compatible.
7. Add focused traffic-signal, Workbench, migration, and cross-layer round-trip tests;
run the existing legacy traffic-signal and preview suites.
## Validation
```bash
npm run test:traffic-signals
npm run test:preview-assets
npm run test:road-workbench
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json
```
Manual acceptance must cover native-only generate/move/rotate/delete -> save ->
recompile -> runtime JSON and preview, plus QGIS import/export compatibility;
disabled signals must be omitted from runtime output.

View File

@@ -0,0 +1,55 @@
# Native traffic signal parity with QGIS
## Goal
Move traffic-signal ownership from the QGIS editing chain into the native road
compiler and Workbench. Existing QGIS signal assemblies remain import/export
compatibility data during migration, but native overrides become the long-term
source for generation, editing, and Blender/Cesium/preview delivery.
## Confirmed Facts
- QGIS currently edits `traffic_signal_assemblies.geojson`; this is the
migration input/output contract, not the desired long-term authority.
- `scripts/reimport-gpkg.js` validates and reimports that legacy layer.
- `scripts/build-area.js` currently derives runtime `traffic_signals.json` from
the edited assemblies; the native path must replace this dependency.
- Existing native road layers do not read traffic-signal artifacts. Historical
native Cesium work intentionally omitted signal runtime assets.
- Stable signal identity and editable fields already include `signal_uid`,
enabled state, source/control/approach IDs, arm direction, pose, mast reach,
and phase-group/runtime data.
## Requirements
- R1: Native compile/workbench must own a versioned area-local signal model and
override artifact, with an explicit one-time/import compatibility path from
existing QGIS assemblies.
- R2: Workbench state must expose signal provenance and the existing editable
signal fields using stable IDs, and support generating, moving, rotating,
and deleting signal assemblies.
- R3: Native compile and preview delivery must generate runtime signal data
directly from the native model, preserving enabled/disabled state, arm
direction/pose, and phase-group data without requiring QGIS.
- R4: A compatibility adapter must import/export the existing QGIS assembly
format during migration and preserve legacy signal IDs where possible.
## Scope Boundary
- Native signal model and overrides are the long-term source of truth.
- QGIS GeoJSON/GeoPackage support is transitional compatibility only; do not
make native compile depend on QGIS or regenerate native edits from QGIS.
- Do not redesign signal geometry, timing logic, or vehicle behavior in this
task.
## Acceptance Criteria
- [ ] A native compile/reopen round trip preserves edited signal assemblies,
stable IDs, enabled state, and provenance without QGIS running.
- [ ] The Road Workbench can generate, inspect, move, rotate, and delete signal
assemblies, then save durable edits without breaking QGIS reimport.
- [ ] Native Blender/Cesium/preview consume runtime signal data generated from
the native model, including disabled signals being omitted from runtime.
- [ ] QGIS import/export compatibility and legacy build stages continue to
pass while the migration adapter exists.
- [ ] Nantaizi has documented native-only and QGIS-imported round trips.

View File

@@ -0,0 +1,26 @@
{
"id": "native-traffic-signal-parity",
"name": "native-traffic-signal-parity",
"title": "Align native traffic signals with QGIS",
"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": "08-13-native-road-compiler",
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -8,8 +8,8 @@
<!-- @@@auto:current-status -->
- **Active File**: `journal-1.md`
- **Total Sessions**: 27
- **Last Active**: 2026-08-07
- **Total Sessions**: 39
- **Last Active**: 2026-08-18
<!-- @@@/auto:current-status -->
---
@@ -19,7 +19,7 @@
<!-- @@@auto:active-documents -->
| File | Lines | Status |
|------|-------|--------|
| `journal-1.md` | ~575 | Active |
| `journal-1.md` | ~845 | Active |
<!-- @@@/auto:active-documents -->
---
@@ -29,6 +29,18 @@
<!-- @@@auto:session-history -->
| # | Date | Title | Commits | Branch |
|---|------|-------|---------|--------|
| 39 | 2026-08-18 | Complete native-only pipeline replacement | `9e0e25a` | `feature/native-road-compiler` |
| 38 | 2026-08-18 | Complete native traffic signal workflow | `5accc0a` | `feature/native-road-compiler` |
| 37 | 2026-08-18 | Complete rounded native junction verification | `e41bfd1`, `f7e71cf` | `feature/native-road-compiler` |
| 36 | 2026-08-17 | Complete native road marking semantics | `9bb97d4` | `feature/native-road-compiler` |
| 35 | 2026-08-17 | Complete native lane separator styles | `bc04517` | `feature/native-road-compiler` |
| 34 | 2026-08-17 | Add center line style overrides | `47efb78` | `feature/native-road-compiler` |
| 33 | 2026-08-17 | Add native road center lines | `fb863da` | `feature/native-road-compiler` |
| 32 | 2026-08-17 | Native road control markings | `4c4f453` | `feature/native-road-compiler` |
| 31 | 2026-08-14 | Native road lane markings | `1b9829d` | `feature/native-road-compiler` |
| 30 | 2026-08-12 | Add vehicle incident preview cards | `761a526` | `main` |
| 29 | 2026-08-12 | Publish reusable area asset packages | `f385009`, `c925890`, `db0fba5`, `f2b8d79`, `0102ffb` | `main` |
| 28 | 2026-08-11 | 默认压缩交付 | `0790cbd` | `main` |
| 27 | 2026-08-07 | QGIS traffic signal editing and countdown stability | `e153a1c` | `main` |
| 26 | 2026-08-07 | 交通信号拓扑与部分构建同步修复 | `1c077a3` | `main` |
| 25 | 2026-08-06 | Cesium traffic signal countdowns | `0e1574f` | `main` |

View File

@@ -573,3 +573,273 @@ Implemented editable traffic signal assemblies in QGIS with stable IDs, position
### Status
[OK] **Completed**
## Session 28: 默认压缩交付
**Date**: 2026-08-11
**Task**: 默认压缩交付
**Branch**: `main`
### Summary
将 GLB 压缩纳入默认构建并使用标准产物路径交付,移除并列压缩输出契约;同步诊断、测试与文档。
### Git Commits
| Hash | Message |
|------|---------|
| `0790cbd` | (see git log) |
### Status
[OK] **Completed**
## Session 29: Publish reusable area asset packages
**Date**: 2026-08-12
**Task**: Publish reusable area asset packages
**Branch**: `main`
### Summary
Published the reusable osm-asset-package/v1 contract, moved static models under package/, kept dynamic cruise and signal assets in _preview/, fixed preview manifest-relative loading, and verified the target area quality gate.
### Git Commits
| Hash | Message |
|------|---------|
| `f385009` | (see git log) |
| `c925890` | (see git log) |
| `db0fba5` | (see git log) |
| `f2b8d79` | (see git log) |
| `0102ffb` | (see git log) |
### Status
[OK] **Completed**
## Session 30: Add vehicle incident preview cards
**Date**: 2026-08-12
**Task**: Add vehicle incident preview cards
**Branch**: `main`
### Summary
Added click-to-open vehicle information cards with normal, breakdown, and accident preview-only states, floating vehicle-anchored cards, route and motion changes, plus bundled repair and warning icons.
### Git Commits
| Hash | Message |
|------|---------|
| `761a526` | (see git log) |
### Status
[OK] **Completed**
## Session 31: Native road lane markings
**Date**: 2026-08-14
**Task**: Native road lane markings
**Branch**: `feature/native-road-compiler`
### Summary
Implemented native lane separators, repeated road direction arrows, explicit junction turn arrows, Workbench inspection/toggles, and Blender/Cesium adapter support for Nantaizi; validated native compile, checks, tests, and non-package visual build.
### Git Commits
| Hash | Message |
|------|---------|
| `1b9829d` | (see git log) |
### Status
[OK] **Completed**
## Session 32: Native road control markings
**Date**: 2026-08-17
**Task**: Native road control markings
**Branch**: `feature/native-road-compiler`
### Summary
Added native-source crosswalk and stop-line layers with workbench inspection, Blender/Cesium delivery, and control-marking arrow avoidance.
### Main Changes
- Compiled explicit marked OSM crossings into source-traceable native GeoJSON.
- Added Chinese workbench control-layer toggle, selection evidence, and summary counts.
### Git Commits
| Hash | Message |
|------|---------|
| `4c4f453` | (see git log) |
### Testing
- [OK] Passed native-road, road-workbench, turn-lane-arrows, build-stages, native compile/check, and Blender/Cesium/preview build.
### Status
[OK] **Completed**
### Next Steps
- Continue native road compiler workbench with the next user-approved priority.
## Session 33: Add native road center lines
**Date**: 2026-08-17
**Task**: Add native road center lines
**Branch**: `feature/native-road-compiler`
### Summary
Implemented Nantaizi native-road center_lines output, workbench selection/toggle, Blender adapter, control-marking avoidance, and cross-layer verification.
### Git Commits
| Hash | Message |
|------|---------|
| `fb863da` | (see git log) |
### Status
[OK] **Completed**
## Session 34: Add center line style overrides
**Date**: 2026-08-17
**Task**: Add center line style overrides
**Branch**: `feature/native-road-compiler`
### Summary
Added persistent native road centre-line colour and pattern overrides, automatic Workbench staging, continuous solid-line rendering, and Blender white material support.
### Git Commits
| Hash | Message |
|------|---------|
| `47efb78` | (see git log) |
### Status
[OK] **Completed**
## Session 35: Complete native lane separator styles
**Date**: 2026-08-17
**Task**: Complete native lane separator styles
**Branch**: `feature/native-road-compiler`
### Summary
Completed per lane-pair separator colour/pattern overrides, Workbench staging, Blender yellow material routing, and centre-line control clearance.
### Git Commits
| Hash | Message |
|------|---------|
| `bc04517` | (see git log) |
### Status
[OK] **Completed**
## Session 36: Complete native road marking semantics
**Date**: 2026-08-17
**Task**: Complete native road marking semantics
**Branch**: `feature/native-road-compiler`
### Summary
Added explicit double-yellow centre lines and per-side edge-line overrides; made all three marking classes selectable in the Chinese Road Workbench; synchronized Blender adapter coverage and verified Nantaizi native compile, quality gate, Blender/Cesium/preview manifests, and automated tests.
### Git Commits
| Hash | Message |
|------|---------|
| `9bb97d4` | (see git log) |
### Status
[OK] **Completed**
## Session 37: Complete rounded native junction verification
**Date**: 2026-08-18
**Task**: Complete rounded native junction verification
**Branch**: `feature/native-road-compiler`
### Summary
Recorded user-confirmed native Blender/Cesium/preview verification for rounded native junctions, checked off all acceptance criteria, and archived the completed 08-17-native-rounded-junctions task.
### Git Commits
| Hash | Message |
|------|---------|
| `e41bfd1` | (see git log) |
| `f7e71cf` | (see git log) |
### Status
[OK] **Completed**
## Session 38: Complete native traffic signal workflow
**Date**: 2026-08-18
**Task**: Complete native traffic signal workflow
**Branch**: `feature/native-road-compiler`
### Summary
Completed native traffic-signal ownership and Workbench editing. Added independent pole position, mast heading/reach, and face heading controls; removed native QGIS dependency; verified runtime preview, browser rendering, compile, tests, and quality gates. Archived native traffic signal and road compiler tasks.
### Git Commits
| Hash | Message |
|------|---------|
| `5accc0a` | (see git log) |
### Status
[OK] **Completed**
## Session 39: Complete native-only pipeline replacement
**Date**: 2026-08-18
**Task**: Complete native-only pipeline replacement
**Branch**: `feature/native-road-compiler`
### Summary
Made native road compilation the default provider and full-build stage set; decoupled package traffic signals and preview from QGIS/osm2streets, made diagnostics provider-aware, updated docs/specs/tests, and preserved explicit legacy reference/debug paths. Native road compile/check and all available Node tests passed. QGIS baseline remains tagged qgis-original-baseline-20260818.
### Git Commits
| Hash | Message |
|------|---------|
| `9e0e25a` | (see git log) |
### Status
[OK] **Completed**

106
README.md
View File

@@ -1,29 +1,37 @@
# OSM Asset Pipeline
把单个园区/片区 OSM XML 转为可消费的 Blender 场景和 Cesium GLB。osm2streets GeoJSON、GeoPackage、QGIS 工程和预览图都是中间资产,用来提供道路几何、调试标线效果,以及给 Blender/Cesium 生成提供输入
原始 QGIS/osm2streets 基线保留在 Git tag `qgis-original-baseline-20260818`,用于回撤和对照;当前分支的默认生产链路是 native-only
把单个园区/片区 OSM XML 转为可消费的 Blender 场景和 Cesium GLB。Native road compiler 是默认生产道路来源osm2streets GeoJSON、GeoPackage、QGIS 工程和预览图仅作为显式 legacy/debug 资产或 Workbench 参考层。
## 目标产物
每个区域默认输出到 `outputs/<area-id>/`
每个区域默认输出到 `outputs/<area-id>/`。其中只有 `package/` 是可复制给下游的发布边界
- `package/manifest.json`:版本化 `osm-asset-package/v1` manifest声明 WGS84 anchor、ENUX east/Y north/Z up、heading correction 和包内模型 URL
- `package/models/<area-id>.glb`:完整静态场景;`roads.glb``buildings.glb``vegetation.glb``water.glb` 是可选重叠分层,默认不要与完整场景同时加载
其余内容仍是本项目工作目录:
- `<area-id>.blend`Blender 场景,包含道路、建筑、水体、植被等
- `<area-id>.png`Blender 预览渲染
- `<area-id>.glb`Cesium 可加载的完整 3D 模型(基线产物)
- `<area-id>.glb`Cesium 可加载的完整 3D 模型(默认压缩交付产物)
- `<area-id>-roads.glb``-buildings.glb``-vegetation.glb``-water.glb`Cesium 分类检查用的辅助模型
- `<area-id>.json`Cesium 放置元数据和示例代码
- `<area-id>-cesium-preview.html`Cesium 本地预览页
- `<area-id>-compressed-webp768.glb/json/html`:显式 `compress` 阶段生成的可选压缩预览产物
- `osm2streets_web_out/`osm2streets GeoJSON 中间层
- `native-road/`native road compiler 的道路、路口、标线和信号灯运行时产物
- `osm2streets_web_out/`可选的 osm2streets/QGIS 参考中间层
- `<area-id>.gpkg` / `<area-id>.qgz` / `<area-id>-preview.png`QGIS 调试资产
## 环境
需要:
默认 native-only 构建需要:
- QGIS 与 GDAL 工具链
- Blender
- Node.js / npm
QGIS/GDAL 仅在显式运行 legacy `intermediates`/`reimport` 或 Workbench 参考链路时需要。
macOS 默认使用 QGIS `/Applications/QGIS.app` 与 Blender
`/Applications/Blender.app`。Linux 配置中,`qgisApp` 是安装前缀(通常为
`/usr``blenderApp` 是可执行文件路径(通常为 `/usr/bin/blender`)。
@@ -37,12 +45,14 @@ npm install
## 主流程
默认构建南台子湖创新谷样例
交互式选择区域和构建阶段
```bash
npm run build
```
使用方向键移动、空格多选阶段、回车开始构建;`intermediates``reimport` 不能同时选择。
指定区域配置:
```bash
@@ -60,15 +70,14 @@ npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages reimport
```
`intermediates` 会生成 osm2streets GeoJSON、GeoPackage、QGIS 工程和 QGIS 预览图。`blender` 使用 OSM 和 osm2streets GeoJSON 生成 `.blend`/`.png``cesium``.blend` 导出 `.glb`/`.json`,并生成 Cesium 预览 HTML。`preview` 只在已有 `.glb/.json` 时补生成 HTML。`compress` 从已有 `.glb/.json/html` 生成并列压缩产物,不覆盖默认 GLB。`reimport` 把手工编辑过的 GeoPackage 回导为 GeoJSON不含在 `all`,详见 [QGIS 手工修正工作流](#qgis-手工修正工作流)。
默认构建依次由 native road compiler、Blender、Cesium、压缩、package 和 preview 组成,不读取 `osm2streets_web_out/`。native preview 在没有 legacy 路线文件时仍可用,只是不提供可选车辆巡航。`intermediates`/`reimport` 仍可显式运行 legacy QGIS 链路,详见 [QGIS 手工修正工作流](#qgis-手工修正工作流)。
Cesium 预览默认显示完整场景。点击 `Inspect` 后会按道路、建筑、绿化与设施、水体加载辅助 GLB该模式用于单独检查生成结果主 GLB 仍是完整场景和下游使用的基线
压缩预览同样保留这些分类检查资产;重跑 `compress` 后应打开新生成的压缩预览 HTML。
Cesium 预览默认显示完整场景。点击 `Inspect` 后会按道路、建筑、绿化与设施、水体加载辅助 GLB该模式用于单独检查生成结果主 GLB 是压缩后的下游交付资产
`compress` 含在 `all` 里,也不能从配置文件默认开启。需要重导出 Cesium 后立刻生成压缩产物时,显式跑:
`compress` 含在完整构建的默认阶段和 `all` 中。需要对已有标准产物重新压缩时,可以显式跑:
```bash
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium,compress
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages compress
```
## 区域诊断
@@ -91,6 +100,53 @@ npm run preflight:area -- --config config/areas/nantaizi-lake-innovation-valley.
预检有 error 时退出非零且不会更新记录;通过后写 `preflight.manifest.json`,保留本次
验证的 config / OSM 文件摘要和检查结果。
## Native Road Workbench
`road:compile` 是独立于 osm2streets 的实验性道路编译器入口。它从 OSM 生成可追溯的
道路模型、基础道路面、诊断与对比摘要,写入 `outputs/<area-id>/native-road/`,不会覆盖
`osm2streets_web_out/` 或影响现有 Blender/Cesium 构建:
```bash
npm run road:compile -- --config config/areas/nantaizi-lake-innovation-valley.json
npm run road:check -- --config config/areas/nantaizi-lake-innovation-valley.json
```
`road:check` 验证已发布 connector 与语义行驶动作的一致性,并在诊断包含 error、图层缺失或
动作/几何对应关系不一致时以非零退出。warning 保留给工作台审查,不会阻止产物打开。
### Native Preview Traffic Simulation
native preview 会额外生成:
```text
outputs/<area-id>/_preview/<area-id>-traffic-simulation.json
```
该文件使用 `native-preview-traffic-simulation/v1`,记录 native 路线、connector、停止点、
信号 `signal_uid`、源文件 SHA-256、坐标系和仿真参数。它与 `package/manifest.json` 一起可
迁移到其他平台;消费方不需要运行 Node 或 QGIS。预览中的车辆会根据现有信号相位在停止线
前停车,并按最小车距跟车排队。该功能是确定性的验证预览,不是法规级导航或完整交通仿真。
迁移契约、JSON 示例、更新循环和回撤方式见
[native preview traffic simulation](docs/native-preview-traffic-simulation.md)。
启动本地浏览器工作台:
```bash
npm run road:workbench -- --config config/areas/nantaizi-lake-innovation-valley.json
```
工作台默认先编译,并在 `http://127.0.0.1:8787/` 启动基于 OpenLayers 的 GIS 工作台,展示
OSM 道路、原生结果、车道、行驶动作、诊断和每个值的来源。可编辑道路宽度、车道数、两侧
人行道,以及候选路口端点连接。保存写入
`outputs/<area-id>/native-road-overrides.json`;此文件是版本化的可审查输入,下一次编译和
启动工作台时会自动加载。编辑不会直接修改最终 polygon。
道路按行驶方向显示:点击道路面、中心线、车道或转向路径后,蓝色三角形标记当前方向在 OSM
原始中心线上的方向;右侧“路口连接”只列出该方向到达终点路口后可驶入的目标道路,并标记为
左转、直行、右转或掉头。车道按当前方向从左向右编号。内部断头诊断中的近邻候选不会自动连接,
必须由用户显式暂存、保存并重新编译。
诊断会输出 OSM bounds、building way / multipolygon relation、显式高度、植被数量、
现有产物状态,以及 GLB 的 size / nodes / meshes / materials / images / extensions。
缺少已期望的基线产物、异常 building relation、GLB 超过保守预算等会进入 `Warnings`
@@ -112,9 +168,9 @@ outputs/<area-id>/_pipeline/stages/compress.manifest.json
造成假 stale。
manifest 记录阶段输入/输出文件的 bytes、mtime、sha256、耗时和结构摘要前段记录 OSM /
GeoJSON feature countsBlender 记录 `.blend` / renderCesium/压缩记录 GLB digest
preview 记录 GLB、metadata、`lane_polygons.geojson``network.json``intersection_surface.geojson`
车辆路线和 runtime 文件。巡航道路区间按 osm2streets internal road 匹配真实 Driving lane 中轴,
native-road 或 legacy GeoJSON feature countsBlender 记录 `.blend` / renderCesium/压缩记录 GLB digest
preview 记录 GLB、metadata、provider 对应的道路输入、可选车辆路线和 runtime 文件。legacy
巡航道路区间按 osm2streets internal road 匹配真实 Driving lane 中轴,native preview 可省略巡航,
路口 connector 必须通过 intersection surface 越界检查。`diagnose:area` 会读取这些
manifest缺失或当前输入/输出 sha/bytes 不一致会在 `Stage manifests``Warnings`
里标出来。
@@ -165,7 +221,7 @@ cp config/examples/template.json config/areas/my-area.json
"qgisApp": "/Applications/QGIS.app",
"blenderApp": "/Applications/Blender.app",
"stages": {
"intermediates": true,
"intermediates": false,
"blender": true,
"cesium": true
},
@@ -183,7 +239,8 @@ cp config/examples/template.json config/areas/my-area.json
},
"blender": {
"treeStyle": "natural",
"officeOverrides": ""
"officeOverrides": "",
"roadProvider": "native"
},
"compress": {
"textureSize": 768,
@@ -209,8 +266,7 @@ QGIS road-layer knobs:
"areaDir": "/absolute/path/to/custom-area",
"blend": "/absolute/path/to/custom.blend",
"glb": "/absolute/path/to/custom.glb",
"cesiumPreview": "/absolute/path/to/custom-preview.html",
"compressedGlb": "/absolute/path/to/custom-compressed.glb"
"cesiumPreview": "/absolute/path/to/custom-preview.html"
}
}
```
@@ -275,8 +331,8 @@ Cesium GLB 低层导出:
## 压缩 GLB
推荐使用显式 `compress` 阶段。它不会覆盖默认 `<area-id>.glb`,只生成并列的压缩 GLB、
metadata 和预览页
完整构建默认运行 `compress`。它将未压缩 Cesium 导出复制到 `_pipeline` 下的临时目录,
压缩成功后才替换标准 `<area-id>.glb``.json` 和预览页;最终输出目录不保留并列压缩文件
```bash
npm run build:area -- \
@@ -289,7 +345,7 @@ npm run build:area -- \
```bash
npm run compress:glb -- \
--input outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley.glb \
--output outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley-compressed-webp768.glb \
--output /tmp/nantaizi-lake-innovation-valley-compressed.glb \
--texture-size 768 \
--metadata outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley.json \
--preview outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley-cesium-preview.html
@@ -310,7 +366,7 @@ npm run compress:glb -- \
南台子湖创新谷当前可直接使用下面这条命令:
```bash
npm run build -- --config config/areas/nantaizi-lake-innovation-valley.json --stages reimport,blender,cesium
npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages reimport,blender,cesium
```
`reimport` 阶段(`scripts/reimport-gpkg.js`)做两件事:
@@ -323,7 +379,7 @@ npm run build -- --config config/areas/nantaizi-lake-innovation-valley.json --st
- 这套流程假设你的手工修改已经保存在 `outputs/<area-id>/<area-id>.gpkg`
- 所有图层先导出到临时目录并校验通过后才写回 `osm2streets_web_out/`;任一图层缺失或导出结果不是合法 FeatureCollection整批都不落盘`ogr2ogr` 遇到不存在的图层会留下 0 字节文件,直接覆盖会静默损坏数据)
- `intermediates``reimport` 互斥,同时指定会直接报错:前者用 OSM 重建 `gpkg`,正好会抹掉后者要读回的手工修改
- `blender,cesium` 阶段读取的是 `osm2streets_web_out/*.geojson`,不是直接读取 `gpkg`
- 在显式 legacy provider 下,`blender,cesium` 阶段读取的是 `osm2streets_web_out/*.geojson`,不是直接读取 `gpkg`native provider 不读取这些文件
- 如果 Blender 当前环境不稳定,先确认 `geojson` 已完成回导,再单独排查 Blender 本身
- 增删图层或调整 `z_index` 只需改 `scripts/lib/scene-layers.js`构建、场景合并、场景样式、QGIS 工程会一并同步

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Some files were not shown because too many files have changed in this diff Show More