chore(task): archive 08-11-asset-package-contract

This commit is contained in:
2026-08-12 09:07:57 +08:00
parent 0102ffbb3c
commit eff7a01b2b
6 changed files with 338 additions and 0 deletions

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