docs(assets): document reusable asset package contract

This commit is contained in:
2026-08-12 09:06:04 +08:00
parent f2b8d79f5d
commit 0102ffbb3c
4 changed files with 182 additions and 56 deletions

View File

@@ -68,11 +68,14 @@ cp config/examples/template.json config/areas/my-area.json
| `intermediates` | `true` | 旧名 `qgis` 仍被接受 | | `intermediates` | `true` | 旧名 `qgis` 仍被接受 |
| `blender` | `true` | | | `blender` | `true` | |
| `cesium` | `true` | | | `cesium` | `true` | |
| `compress` | `true` | 压缩 staged GLB供随后发布使用 |
| `package` | `true` | 将通过校验的静态模型原子发布到 `package/` |
| `preview` | `true` | 基于已发布 package 写验证预览及 `_preview/` 动态资源 |
`reimport``preview` **在这里配也没用**——`normalizeAreaConfig` 把它 `reimport` **在这里配也没用**——`normalizeAreaConfig` 把它硬编码为 `false`,只能靠
硬编码为 `false`,只能靠 `--stages` 显式请求。`compress` 始终默认开启。 `--stages` 显式请求。`compress``package``preview` 始终默认开启。
> 恢复动作reimport和补丁动作preview不该被一份配置文件变成默认行为;压缩是标准交付链的一部分。 > 恢复动作reimport不该被一份配置文件变成默认行为压缩、发布和验证预览是标准交付链的一部分。
`--stages` 会整体覆盖这里的默认值。 `--stages` 会整体覆盖这里的默认值。
@@ -133,7 +136,8 @@ cp config/examples/template.json config/areas/my-area.json
### `compress` ### `compress`
完整构建和显式 `--stages compress` 都使用此配置。默认压缩链是 texture resize + WebP 完整构建和显式 `--stages compress` 都使用此配置。默认压缩链是 texture resize + WebP
transcode成功后覆盖标准 `<area-id>.glb``.json` 和预览 HTML;未压缩源只保留在构建临时目录。 transcode成功后替换**package staging** 中的主 GLB 与 manifest;未压缩源只保留在构建临时目录。
只有随后的 `package` 阶段才会原子发布到 `package/`
| 字段 | 默认 | 说明 | | 字段 | 默认 | 说明 |
|---|---|---| |---|---|---|
@@ -161,14 +165,16 @@ transcode成功后覆盖标准 `<area-id>.glb`、`.json` 和预览 HTML
### `outputs`(逃生舱) ### `outputs`(逃生舱)
默认全部从 `id` 推导`<outputRoot>/<id>/<fileStem>.<ext>`。需要定制时逐项覆盖: 默认全部从 `id` 推导。发布给下游的静态资产固定在
`<outputRoot>/<id>/package/`,临时静态资产在 `_pipeline/package-staging/`,预览专用动态资源在
`<outputRoot>/<id>/_preview/`。需要定制时逐项覆盖:
```json ```json
{ {
"outputs": { "outputs": {
"areaDir": "/absolute/path/to/custom-area", "areaDir": "/absolute/path/to/custom-area",
"blend": "/absolute/path/to/custom.blend", "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" "cesiumPreview": "/absolute/path/to/custom-preview.html"
} }
} }
@@ -178,6 +184,11 @@ transcode成功后覆盖标准 `<area-id>.glb`、`.json` 和预览 HTML
`qgisProject``qgisPreview``blend``render``glb``metadata``cesiumPreview` `qgisProject``qgisPreview``blend``render``glb``metadata``cesiumPreview`
`vehicleRoute``vehicleModel``pipelineDir``stageManifestDir` `vehicleRoute``vehicleModel``pipelineDir``stageManifestDir`
静态发布路径另有 `packageDir``packageStagingDir``packageManifest`
`packageStagingManifest``packageModelDir``packageStagingModelDir`
`packagePrimaryGlb`;预览路径另有 `previewDir``previewDescriptor`。除非在迁移旧调用,
不要覆盖 `glb` / `metadata`:它们是 staging 内部路径,不是下游资产入口。
**优先改 `fileStem` 或 `areaDir`**——它们能一次性影响全部派生路径。逐个覆盖容易漏。 **优先改 `fileStem` 或 `areaDir`**——它们能一次性影响全部派生路径。逐个覆盖容易漏。
--- ---
@@ -219,7 +230,8 @@ transcode成功后覆盖标准 `<area-id>.glb`、`.json` 和预览 HTML
| 布尔字段用 `\|\|` 兜底 | `false` 被翻转 | | 布尔字段用 `\|\|` 兜底 | `false` 被翻转 |
| 给新字段造顶层平铺别名 | 扩大历史包袱 | | 给新字段造顶层平铺别名 | 扩大历史包袱 |
| 逐个覆盖 `outputs` 而不用 `fileStem` | 漏掉某个产物路径 | | 逐个覆盖 `outputs` 而不用 `fileStem` | 漏掉某个产物路径 |
| `stages` 里配 `reimport` / `preview` | 无效,被硬编码为 false | | `glb` / `metadata` 当作下游入口 | 它们位于 staging应只读取 `package/manifest.json` |
| 在 `stages` 里配 `reimport` | 无效,被硬编码为 false |
| 加数值字段不做范围校验 | 错配置在中途才崩,输出已被破坏 | | 加数值字段不做范围校验 | 错配置在中途才崩,输出已被破坏 |
--- ---

View File

@@ -25,9 +25,10 @@
### 1. Scope / Trigger ### 1. Scope / Trigger
`compress` 是完整构建`all` 的默认末端阶段。它在已有 Cesium GLB 上执行 texture `compress` 是完整构建的默认阶段,位于 `cesium``package` 之间。它在
resize + WebP transcode并以标准 `<area-id>.glb``.json` 和预览 HTML 替换未压缩导出。 `_pipeline/package-staging/` 的主 GLB 上执行 texture resize + WebP transcode并替换 staged
未压缩版本只在 `<areaDir>/_pipeline/compress-*` 临时目录中存在,成功或失败后都会清理。 主 GLB 与 staged manifest不触碰已发布的 `package/`,也不重写 preview HTML。未压缩版本只在
`<areaDir>/_pipeline/compress-*` 临时目录中存在,成功或失败后都会清理。
`scripts/compress-glb.js` 是该阶段调用的低层脚本,也可单独运行做实验。 `scripts/compress-glb.js` 是该阶段调用的低层脚本,也可单独运行做实验。
@@ -63,18 +64,16 @@ npm run compress:glb -- --input in.glb --output out.glb [options]
- 输入必须是现有 `.glb` 文件;`--output` 必须不同于 `--input` - 输入必须是现有 `.glb` 文件;`--output` 必须不同于 `--input`
- 低层脚本仍输出到与输入不同的路径;`build-area.js` 使用临时输入/输出路径,避免原地压缩。 - 低层脚本仍输出到与输入不同的路径;`build-area.js` 使用临时输入/输出路径,避免原地压缩。
- `compress` 阶段依赖标准 `glb``metadata``cesiumPreview` 已存在,并在全部临时交付文件 - `compress` 阶段依赖 staged `glb``metadata` 已存在,并在全部临时交付文件生成和校验后
生成和校验后`renameSync` 替换它们。 `renameSync` 替换它们。
- 默认链固定为 `gltf-transform resize -> gltf-transform webp` - 默认链固定为 `gltf-transform resize -> gltf-transform webp`
- `compress` 阶段默认 `textureSize=768`;低层脚本单独运行时默认 `--texture-size 1024` - `compress` 阶段默认 `textureSize=768`;低层脚本单独运行时默认 `--texture-size 1024`
- `build-area.js` 最终只保留 `<fileStem>.glb``<fileStem>.json` - `build-area.js` 不会写并列 `-compressed-*` 交付物;`package` 是唯一能发布静态文件到
`<fileStem>-cesium-preview.html`;不会写并列 `-compressed-*` 交付物 `package/` 的阶段
- 伴生 metadata 的 `asset``id="main"` 资产 URL 保持为标准 GLB 文件名;其余资产(包括 - staged manifest 的 `assets[*].uri` 必须保持 package-relative`models/<area>.glb`;分类
`category="semantic"` 的 Cesium 分类检查 GLB必须原样保留。 layer GLB 必须原样保留。
- preview HTML 只替换 `window.OSM_ASSET_PREVIEW_CONFIG``glbName` /
`metadataName` 和 loading 文案,不改 preview runtime。
- 成功时 stdout 打印 `GLB_COMPRESS_DONE <json>`包含压缩前后大小、image / - 成功时 stdout 打印 `GLB_COMPRESS_DONE <json>`包含压缩前后大小、image /
non-image bytes、结构计数、扩展metadata / preview 输出路径。 non-image bytes、结构计数、扩展metadata 输出路径。
### 4. Validation & Error Matrix ### 4. Validation & Error Matrix
@@ -85,23 +84,21 @@ npm run compress:glb -- --input in.glb --output out.glb [options]
| `--output` 等于 `--input` | 抛错,避免覆盖源 GLB | | `--output` 等于 `--input` | 抛错,避免覆盖源 GLB |
| 数值参数超范围 | 抛错并指出合法范围 | | 数值参数超范围 | 抛错并指出合法范围 |
| `gltf-transform` 退出非零 | 抛错并带上 status / signal | | `gltf-transform` 退出非零 | 抛错并带上 status / signal |
| `--preview` 没有 `--metadata` | 抛错,因为 preview 必须指向存在的 metadata | | `--stages compress` 但 staged manifest 不存在 | `Cesium metadata not found: <path>` |
| preview HTML 找不到配置块 | 抛错,不做猜测替换 |
| `--stages compress` 但标准 preview 不存在 | `Cesium preview not found: <path>` |
### 5. Good/Base/Bad Cases ### 5. Good/Base/Bad Cases
- Good: `all`默认构建先导出 Cesium再压缩成标准交付物 - Good: 默认构建先导出 Cesium再压缩 staged 资产,最后由 `package` 发布
- Good: `--stages compress` 对已有标准交付物重新压缩,临时源 GLB 不会留在输出目录。 - Good: `--stages compress` 对已有 staged 交付物重新压缩,临时源 GLB 不会留在输出目录。
- Base: 只传 `--input --output` 生成压缩 GLB不生成伴生文件。 - Base: 只传 `--input --output` 生成压缩 GLB不生成伴生文件。
- Bad: 使用 `--meshopt` 后没有做 Cesium 兼容性验证就当默认产物发布。 - Bad: 使用 `--meshopt` 后没有做 Cesium 兼容性验证就当默认产物发布。
### 6. Tests Required ### 6. Tests Required
- `node --check scripts/compress-glb.js` - `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` - `node --check scripts/build-area.js`
- 对目标区域跑一次 `npm run compress:glb -- ... --metadata --preview` - 对目标区域跑一次 `npm run compress:glb -- ... --metadata`
- 对目标区域跑一次默认 `npm run build:area` - 对目标区域跑一次默认 `npm run build:area`
- `node scripts/glb-digest.js <area>.glb` 确认可解析结构和扩展 - `node scripts/glb-digest.js <area>.glb` 确认可解析结构和扩展
- 浏览器/Cesium 预览标准 HTML确认 `EXT_texture_webp` 在目标环境可加载 - 浏览器/Cesium 预览标准 HTML确认 `EXT_texture_webp` 在目标环境可加载
@@ -122,6 +119,103 @@ 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` 只能是包内正向相对路径,禁止绝对路径、反斜杠和 `..`
- `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/` 的车辆模型或动态交通灯加进 `assets`,使资产包绑定本项目的验证运行时。
### 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;
```
---
## OSM 预检命令 ## OSM 预检命令
### 1. Scope / Trigger ### 1. Scope / Trigger
@@ -605,7 +699,7 @@ const gate = classifyAreaQuality(result);
### 1. Scope / Trigger ### 1. Scope / Trigger
Stage manifest 是区域构建阶段或独立验证通过后的机器可读产物契约。它覆盖预检记录与完整区域链: Stage manifest 是区域构建阶段或独立验证通过后的机器可读产物契约。它覆盖预检记录与完整区域链:
`preflight``intermediates``reimport``blender``cesium``preview``compress` `preflight``intermediates``reimport``blender``cesium``compress``package``preview`
它用于诊断产物是否存在、是否 stale、体量是否超预算以及后续 `check:area` / 它用于诊断产物是否存在、是否 stale、体量是否超预算以及后续 `check:area` /
增量构建判断。 增量构建判断。
@@ -627,6 +721,7 @@ Manifest 路径固定:
<areaDir>/_pipeline/stages/cesium.manifest.json <areaDir>/_pipeline/stages/cesium.manifest.json
<areaDir>/_pipeline/stages/preview.manifest.json <areaDir>/_pipeline/stages/preview.manifest.json
<areaDir>/_pipeline/stages/compress.manifest.json <areaDir>/_pipeline/stages/compress.manifest.json
<areaDir>/_pipeline/stages/package.manifest.json
``` ```
代码入口: 代码入口:
@@ -719,9 +814,18 @@ File records use this shape:
- `summary.glb.extensionsUsed` - `summary.glb.extensionsUsed`
- `summary.budget`effective limits、usage 和 violationswarnings 来自同一个预算评估 - `summary.budget`effective limits、usage 和 violationswarnings 来自同一个预算评估
`cesium` 会调用 preview 生成函数,但 preview HTML / route / vehicle model 的 freshness `cesium` 只拥有 staging 内的静态模型与 manifest完成后必须依次由 `compress``package`
所有权属于独立 `preview` manifest。否则单跑 `--stages preview` 会把 Cesium manifest 交付。preview HTML / route / vehicle model 的 freshness 所有权属于独立 `preview` manifest
错误判 stale。
`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 ### Preview Assembly Boundary
@@ -786,8 +890,8 @@ Manifest files are written atomically via `*.tmp` then `renameSync`.
- Good: `--stages intermediates` 成功后写 `intermediates.manifest.json`,诊断显示 - Good: `--stages intermediates` 成功后写 `intermediates.manifest.json`,诊断显示
`ok intermediates` `ok intermediates`
- Good: `--stages blender` 成功后写 `blender.manifest.json`,诊断显示 `ok blender` - Good: `--stages blender` 成功后写 `blender.manifest.json`,诊断显示 `ok blender`
- Good: `--stages cesium` 成功后写 `cesium.manifest.json``preview.manifest.json` - Good: `--stages cesium,compress,package,preview` 成功后依序写四份各自拥有的 manifest。
- Good: `--stages preview` 只更新 preview manifest不让 cesium manifest stale。 - Good: `--stages preview` 只更新 preview manifest不让 package manifest stale。
- Good: `--stages compress` 成功后写 `compress.manifest.json`summary 记录压缩比和节省字节。 - Good: `--stages compress` 成功后写 `compress.manifest.json`summary 记录压缩比和节省字节。
- Base: 旧产物没有当前 ownership 路径的 manifest诊断显示 expected manifest missing - Base: 旧产物没有当前 ownership 路径的 manifest诊断显示 expected manifest missing
提示重跑对应阶段。 提示重跑对应阶段。
@@ -806,6 +910,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 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 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 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 preview`
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages compress` - `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` - `npm run diagnose:area -- --config config/areas/nantaizi-lake-innovation-valley.json`
@@ -924,15 +1029,16 @@ 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` | OSM → osm2streets GeoJSON → GeoPackage → QGIS 工程 + 预览图 | `.osm` | `osm2streets_web_out/``.gpkg``.qgz``-preview.png` |
| `reimport` | GeoPackage → GeoJSON**反向** | `.gpkg` | `osm2streets_web_out/` | | `reimport` | GeoPackage → GeoJSON**反向** | `.gpkg` | `osm2streets_web_out/` |
| `blender` | OSM + GeoJSON → 场景 | `.osm``osm2streets_web_out/` | `.blend``.png` | | `blender` | OSM + GeoJSON → 场景 | `.osm``osm2streets_web_out/` | `.blend``.png` |
| `cesium` | 场景 → GLB + 元数据 + 预览页 | `.blend` | `.glb``.json`、预览 HTML 及其静态资源 | | `cesium` | 场景 → static staging GLB + manifest | `.blend` | `_pipeline/package-staging/models/*.glb`、staged manifest动态 GLB 到 `_preview/` |
| `preview` | 只补生成预览页 | `.glb``.json` | 预览 HTML 及其静态资源 | | `compress` | 压缩 Cesium staging 主模型 | staged `.glb`、manifest | 压缩 staged `.glb`、manifest |
| `compress` | 将 Cesium 临时导出替换为压缩交付产物 | 标准 `.glb``.json`、预览 HTML | 标准 `.glb/json/html` | | `package` | 校验并原子发布静态资产包 | staging manifest 与 models | `package/manifest.json``package/models/*.glb` |
| `preview` | 用已发布 manifest 生成验证预览 | `package/manifest.json`、动态输入 | 预览 HTML、静态 runtime、`_preview/` 动态资源 |
调度是顶层的阶段 `if``build-area.js` 开头),顺序固定,**阶段之间不传内存状态, 调度是顶层的阶段 `if``build-area.js` 开头),顺序固定,**阶段之间不传内存状态,
只通过磁盘产物耦合**。这就是单跑某个阶段能work 的原因。 只通过磁盘产物耦合**。这就是单跑某个阶段能work 的原因。
`cesium` 阶段结束时会直接调 `writeCesiumPreview(area)``build-area.js:285`),所以 完整构建中 `preview``package` 后运行。单跑 `preview` 用于已发布 package 但想重生成
`preview` 只在"已有 GLB、只想重生成 HTML"时才需要单独跑 验证 UI 或 `_preview/` 动态资源的情况
### 别名 ### 别名
@@ -947,11 +1053,11 @@ gpkg: path.resolve(outputOverrides.gpkg || path.join(areaDir, `${fileStem}.gpkg`
```js ```js
// 'reimport' is deliberately absent from 'all': it is a recovery step for // '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 // hand-edited GeoPackages, never part of a full build. build-area.js:159-160
all: ["intermediates", "blender", "cesium", "compress"], all: ["intermediates", "blender", "cesium", "compress", "package", "preview"],
``` ```
`preview` 不在 `all` 里——`cesium` 已经包含它。`compress``all` ,确保完整构建 `compress``package``preview` `all` ,确保完整构建以压缩后的可复用静态 package
压缩后的标准路径交付。 及可运行的验证预览交付。
### `intermediates` 与 `reimport` 互斥 ### `intermediates` 与 `reimport` 互斥
@@ -961,9 +1067,8 @@ all: ["intermediates", "blender", "cesium", "compress"],
这是**显式拒绝而不是警告**——两者同时开,无论谁先跑,另一个的工作都白做。 这是**显式拒绝而不是警告**——两者同时开,无论谁先跑,另一个的工作都白做。
`normalizeAreaConfig``stages.reimport` `stages.preview` 硬编码为 `false` `normalizeAreaConfig`只有 `stages.reimport` 硬编码为 `false`只能靠 `--stages` 显式请求;
只能靠 `--stages` 显式请求;`stages.compress` 为默认 `true` `stages.compress``stages.package``stages.preview` 为默认 `true`
恢复动作和补丁动作不该被一份配置文件变成默认行为,压缩则属于标准交付链。
--- ---
@@ -1000,7 +1105,7 @@ parity 校验依赖 stage 的 stdout 标记来判断阶段是否跑到(如 `SC
| 在阶段函数里现拼输出路径 | 路径规则出现第二份定义 | | 在阶段函数里现拼输出路径 | 路径规则出现第二份定义 |
| 低层脚本直接读 `config/areas/*.json` | 打破两层配置边界 | | 低层脚本直接读 `config/areas/*.json` | 打破两层配置边界 |
| 布尔配置用 `\|\|` 兜底 | `false` 被翻转成默认值 | | 布尔配置用 `\|\|` 兜底 | `false` 被翻转成默认值 |
| 让 `reimport` / `preview` 能从配置文件默认开启 | 恢复动作变成常规行为 | | 让 `reimport` 能从配置文件默认开启 | 恢复动作变成常规行为 |
| 新阶段忘了 `ensureFile` 前置校验 | 单跑时报底层堆栈而非人话 | | 新阶段忘了 `ensureFile` 前置校验 | 单跑时报底层堆栈而非人话 |
| 改 stage 的 stdout 标记 | 静默破坏 parity 契约 | | 改 stage 的 stdout 标记 | 静默破坏 parity 契约 |
| 顺手把多份 `parseArgs` 合并 | 扩大 diff且独立入口的独立性是刻意的 | | 顺手把多份 `parseArgs` 合并 | 扩大 diff且独立入口的独立性是刻意的 |

View File

@@ -51,17 +51,21 @@ config/areas/<id>.json
│ → _pipeline/stages/blender.manifest.json │ → _pipeline/stages/blender.manifest.json
├─[cesium]────────▶ Blender + blender/export_cesium.py ├─[cesium]────────▶ Blender + blender/export_cesium.py
│ 读 .blend → <id>.glb + <id>.json │ 读 .blend → _pipeline/package-staging/models/<id>.glb
→ 并自动执行 preview + staged manifest动态预览 GLB 写入 _preview/
│ → _pipeline/stages/cesium.manifest.json │ → _pipeline/stages/cesium.manifest.json
├─[preview]───────▶ 生成 <id>-cesium-preview.html ├─[compress]──────▶ 压缩 staging 内主 GLB 并更新 staged manifest
+ 拷贝 lib/cesium-preview.{js,css} → _pipeline/stages/compress.manifest.json
│ + 车辆巡航路线与模型
│ → _pipeline/stages/preview.manifest.json
─[compress]──────▶ 临时保存未压缩导出,再以标准 GLB / metadata / preview 路径交付压缩版本 ─[package]───────▶ 校验 manifest 与全部静态模型,原子发布 package/
_pipeline/stages/compress.manifest.json package/manifest.json + package/models/*.glb
│ → _pipeline/stages/package.manifest.json
└─[preview]───────▶ 生成 <id>-cesium-preview.html
│ + 拷贝 lib/cesium-preview.{js,css}
+ _preview/ 车辆巡航路线、模型、动态信号
│ → _pipeline/stages/preview.manifest.json
``` ```
**阶段之间只通过磁盘产物耦合**不传内存状态。这是单跑任意阶段能work 的前提。 **阶段之间只通过磁盘产物耦合**不传内存状态。这是单跑任意阶段能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 并打印完整报告 | | `diagnose-area.js` | 36 | 快速诊断入口:调用共享 area diagnostics 并打印完整报告 |
| `check-area.js` | 74 | 区域质量门入口:调用共享 area diagnostics输出 PASS/FAIL 并设置退出码 | | `check-area.js` | 74 | 区域质量门入口:调用共享 area diagnostics输出 PASS/FAIL 并设置退出码 |
| `lib/area-diagnostics.js` | 776 | 共享区域诊断事实源OSM、产物、metadata、stage manifest、GLB digest 和质量门分类 | | `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-route.js` | 约 180 | 从 OSM 提取确定性预览巡航路线 |
| `lib/vehicle-model.js` | 约 150 | 生成内嵌 buffer 的预览车辆 glTF | | `lib/vehicle-model.js` | 约 150 | 生成内嵌 buffer 的预览车辆 glTF |
| `lib/area-preview.js` | 约 110 | 复制 preview runtime、生成 HTML 与转义配置注入 | | `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) | | `lib/cesium-preview.js` / `.css` | 672 / 230 | 预览页运行时,见 [../preview/](../preview/index.md) |
| `normalize-lane-arrows.py` | 182 | 合并 osm2streets 的三角网箭头(跑在 QGIS Python 里) | | `normalize-lane-arrows.py` | 182 | 合并 osm2streets 的三角网箭头(跑在 QGIS Python 里) |
| `parity.js` | 270 | 产物一致性校验驱动 | | `parity.js` | 270 | 产物一致性校验驱动 |

View File

@@ -8,8 +8,8 @@
## 定位 ## 定位
预览层是**验证性的,不是产物本身**。它加载 `cesium` 阶段导出的 `.glb` + `.json` 预览层是**验证性的,不是产物本身**。它加载 `package/manifest.json`,用来确认已发布资产
用来确认资产在真实 Cesium 里的样子。改这一层**不会**改变 Blender/GLB 资产。 在真实 Cesium 里的样子。改这一层**不会**改变 package 内的 Blender/GLB 静态资产。
车辆巡航同理——README 里写明它是"用于验证高精度巡航可用性的预览层功能"。 车辆巡航同理——README 里写明它是"用于验证高精度巡航可用性的预览层功能"。
@@ -53,7 +53,11 @@ const config = window.OSM_ASSET_PREVIEW_CONFIG || {}; // :4
**加一个新的可配置项**`cesiumPreviewHtml()` 里加进注入的 JSONJS 侧从 `config` 读, **加一个新的可配置项**`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 分别由 不要把 HTML 模板、runtime copy 或转义实现移回阶段调度器。路线 JSON 与车辆 glTF 分别由
`vehicle-route.js``vehicle-model.js` 生成,二者都是不启动外部工具的 Node 模块。 `vehicle-route.js``vehicle-model.js` 生成,二者都是不启动外部工具的 Node 模块。