diff --git a/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/check.jsonl b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/check.jsonl new file mode 100644 index 0000000..d21a491 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/check.jsonl @@ -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."} diff --git a/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/design.md b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/design.md new file mode 100644 index 0000000..c4ae978 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/design.md @@ -0,0 +1,135 @@ +# Asset Package Contract Design + +## Product Boundary + +`outputs//` 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// + package/ # publishable boundary + manifest.json # asset-package/v1 + models/ + .glb # compressed complete static scene + roads.glb + buildings.glb + vegetation.glb + water.glb + -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/.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//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. diff --git a/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/implement.jsonl b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/implement.jsonl new file mode 100644 index 0000000..91e61ec --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/implement.jsonl @@ -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."} diff --git a/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/implement.md b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/implement.md new file mode 100644 index 0000000..c21de6e --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/implement.md @@ -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//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 +``` diff --git a/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/prd.md b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/prd.md new file mode 100644 index 0000000..8e35b0a --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/prd.md @@ -0,0 +1,61 @@ +# Reusable asset package contract + +## Goal + +将当前面向 Cesium 预览的区域输出升级为可由多个下游项目稳定消费的静态资产包。资产包 +必须有版本化 manifest、仅使用包内相对引用,并明确发布资产与本项目调试/预览产物的边界。 + +## Confirmed Facts + +- `blender/export_cesium.py` 已生成 `.json`,包含主 GLB、roads/buildings/ + vegetation/water 分层 GLB、WGS84 anchor、ENU 坐标约定、heading correction 和 OSM bounds。 +- 该 JSON 同时包含桌面绝对 `source_osm` / `source_geojson` 路径、Cesium 代码片段,以及 + 动态信号灯和倒计时预览资产;因此不是可发布的下游契约。 +- `outputs//` 还包含 QGIS、GeoJSON、Blender、Cesium HTML/runtime、车辆巡航和自检文件; + 它是构建工作目录,不是干净的发布目录。 +- 当前默认 Cesium GLB 已在构建末端压缩,且 metadata 中的资产 URL 使用同目录相对文件名。 +- 项目目标是生成可复用资产;交通仿真、跟车和信号调度属于下游运行时能力,不属于资产包首版。 + +## Requirements + +- R1:定义一个版本化 `manifest.json` v1,描述区域 ID、WGS84 anchor、ENU 轴向与 heading + correction、WGS84 bounds、发布资产、语义类别和包内相对路径。 +- R2:资产包只含可复用的静态交付资产;主场景和道路、建筑、植被、水体等分层模型必须有 + 明确的角色、加载语义和默认行为。 +- R3:manifest 和包内文件不得包含桌面绝对路径、构建临时目录、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//` 预览工作流继续可用,现有非交互 build 命令保持兼容。 +- [ ] 发布包缺文件、manifest 路径越界、坐标字段无效或资产类别不合法时,构建/校验非零退出。 + +## Proposed Delivery Phases + +1. 契约与目录边界:冻结 manifest v1 schema、发布目录结构、资产角色和坐标定义。 +2. 资产包发布阶段:从现有 Cesium 导出与压缩结果收集、校验并写入独立包目录。 +3. 下游消费证明:Cesium / Three.js 示例仅依赖 manifest 与包内容,并覆盖主场景和按类别加载。 +4. 质量门与迁移:增加结构、路径隔离、坐标和加载验证;保留旧预览输出并记录迁移规则。 + +## Key Decision + +- 发布包根目录固定为 `outputs//package/`。该目录是可整体复制给下游项目的唯一 + 发布边界;GLB 和 manifest 直接生成或移动到这里,避免与工作目录再保留一套大模型副本。 + 本项目的预览页可通过包内相对路径读取发布资产,但 HTML/runtime 本身不属于发布包。 + +## Out Of Scope + +- 交通流、车辆行为、信号相位控制、路口调度和其他运行时仿真。 +- 在本任务中扩展 OSM 到建筑、路灯、标志、植被等资产覆盖率或生成质量。 +- 删除既有 `outputs//` 中由用户保留的历史调试产物。 diff --git a/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/task.json b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/task.json new file mode 100644 index 0000000..a132e76 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-asset-package-contract/task.json @@ -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": {} +} \ No newline at end of file