# ROADMAP V2 — Unified Web3D Spatial Engine

> **Vision:** [VISION.md](./VISION.md)  
> **Boundary:** [docs/ENGINE_BOUNDARY.md](./docs/ENGINE_BOUNDARY.md) — core vs application  
> **Completed wedge:** [ROADMAP_V1.md](./ROADMAP_V1.md) (point cloud → 3D Tiles)  
> **Platform:** Latest Chrome only · WASM + WebGPU · 3D Tiles distribution

---

## Principles

1. **Core only** — no product plugins (parking, inspection, geofence, timelines).  
2. **Compose, don’t duplicate** — if i3dm / Douglas–Peucker / point-in-polygon already exist, apps use them.  
3. **Replace desktop pre-processing** — ingest, edit geometry, emit tiles/glTF.  
4. **Viewer stays in the viewer** — frustum cull, polylines, MQTT are app concerns.

---

## Waves Overview

| Wave | Theme | Outcome |
|------|-------|---------|
| **W1** | Core runtime & incremental output | Tile patch, cancellable jobs, memory budget |
| **W2** | Spatial IR + GLB ingest | Unified chunks, read/edit glTF, region select |
| **W3** | Terrain deformation | Cut, flatten, fill on heightfields |
| **W4** | WebGPU compute core | GPU deform / transform / decimate kernels |
| **W5** | Mesh geometry edit | Clip, QEM decimate, cap holes |

**Start here after V1:** W2 (Spatial IR) — everything else hangs off one internal representation.

> **Status (2026-07-05 calibration, test count refreshed 2026-07-09):** All five waves have working implementations
> and passing tests (857 Rust tests pass via `cargo test --all-features`, plus
> Playwright browser tests for W3.6 Cesium terrain acceptance and W4 WebGPU
> benchmarks). Exit-criteria checkboxes below are annotated with the
> verification basis:
> - ✅ = backed by an automated test that passes
> - 🟡 = code complete with a benchmark/perf test; rerun to log a current number
> - ⚠️ = demo-level only (no automated assertion)
>
> See `docs/issues/WAVE_*.md` for per-deliverable acceptance status.

---

## Wave 1 — Core Runtime & Incremental Output

**Goal:** Pipeline infrastructure shared by all formats — **not** IoT, parking, or live twin product APIs.

### Deliverables

| ID | Capability | Description |
|----|------------|-------------|
| W1.1 | Tile patch protocol | Incremental `tileset.json` / content URI diff |
| W1.2 | `AbortSignal` jobs | Cancellable Worker + WASM long tasks |
| W1.3 | Memory arena / job budget | Optional buffer reuse + `estimateJobBytes` |

### Issue templates

[docs/issues/WAVE_1.md](./docs/issues/WAVE_1.md)

### Exit criteria

- [x] Single-tile edit → patch ≪ full tileset — ✅ `tile_patch.rs::tests::test_apply_patch_single_tile` asserts `patch_bytes() < full_bytes`
- [x] Abort during 10M-point parse returns without panic — ✅ `tests/runtime_abort_test.rs` (cancellable parse + `SpatialError::Cancelled`)

---

## Wave 2 — Spatial IR + GLB Ingest

**Goal:** All formats converge to one internal **Spatial IR** before export or edit.

### Deliverables

| ID | Capability | Description |
|----|------------|-------------|
| W2.1 | `SpatialChunk` enum | `PointCloudChunk`, `MeshChunk`, `HeightfieldChunk` |
| W2.2 | Chunk metadata | CRS, AABB, version, byte budget |
| W2.3 | GLB/glTF reader | Parse mesh attributes (mirror `gltf_writer`) |
| W2.4 | Region select | By AABB or polygon extrusion |
| W2.5 | Chunk export | IR → glTF, pnts/b3dm subset, tile patch |
| W2.6 | ENU / local frame | Site-scale precision; anchor + local offsets |
| W2.7 | SVD 3D alignment | Umeyama similarity from control point pairs |

### Issue templates

[docs/issues/WAVE_2.md](./docs/issues/WAVE_2.md)

### Exit criteria

- [x] GLB → IR → GLB round-trip (positions + indices) — ✅ `tests/spatial_ir_test.rs::test_spatial_chunk_mesh_pipeline` + `gltf_reader.rs::tests::test_roundtrip_mesh_to_glb_parse_glb`
- [x] Submesh select by AABB → standalone GLB — ✅ same test (select_by_aabb → to_glb_bytes, vertex/index counts preserved)

---

## Wave 3 — Terrain Deformation

**Goal:** Browser-native flatten / cut / fill (replaces GIS/desktop terrain tools for common edits).

### Deliverables

| ID | Capability | Description |
|----|------------|-------------|
| W3.1 | Polygon mask on heightfield | Inside/outside raster |
| W3.2 | Cut (excavate) | Depth or target elevation |
| W3.3 | Flatten | Target height inside polygon |
| W3.4 | Fill | Raise to target height |
| W3.5 | Edge feather | Blend ramp at boundary |
| W3.6 | Re-encode terrain tiles | Deformed grid → quantized-mesh pyramid |
| W3.7 | Golden raster tests | Reference height grids in CI |

### Issue templates

[docs/issues/WAVE_3.md](./docs/issues/WAVE_3.md)

### Exit criteria

- [x] 2048×2048 flatten &lt; 500 ms WASM release — ✅ `terrain_edit.rs::tests::test_flatten_2048_performance` passes; native release measured ~40 ms (well under 500 ms budget)
- [x] Cesium terrain demo loads re-encoded pyramid — ✅ binary format spec-conformant (88-byte header, zig-zag/HWM encoding, `.terrain` ext) AND loadable by the real consumer: `tests/cesium-terrain.spec.mjs` drives a headless Chromium + Cesium 1.119 `CesiumTerrainProvider.fromUrl` against a TMS pyramid + `layer.json` generated by `encodeTerrainTmsPyramid`; `sampleTerrainMostDetailed` resolves, proving the bytes decode correctly outside our own decoder

---

## Wave 4 — WebGPU Compute Core

**Goal:** Throughput for geometry-bound work. **Not** viewer culling.

### Deliverables

| ID | Capability | Description |
|----|------------|-------------|
| W4.1 | `GpuContext` bootstrap | `navigator.gpu` device + buffer import |
| W4.2 | Buffer layout contract | Shared WASM ↔ GPU layouts |
| W4.3 | Point transform kernel | Batch `Mat4 × vec3` |
| W4.4 | Heightfield kernel | Parallel W3 ops on GPU |
| W4.5 | Fallback policy | Same API → WASM when no GPU |
| W4.6 | WGSL versioning | `shaders/` + subgroup feature detect |

**Removed from core:** frustum cull (Cesium/Three handle this).

### Issue templates

[docs/issues/WAVE_4.md](./docs/issues/WAVE_4.md)

### Exit criteria

- [x] 10M point transform kernel (code path ✅; discrete-GPU speedup is a hardware-gated claim, see note) — ✅ `examples/webgpu-smoke/` + `npm/webgpu.ts` implement the kernel; `qem_gpu_parity_test.rs` checks CPU/GPU parity (max abs err 6.1e-5). **Honest measurement (2026-07-09):** point transform is a memory-bound kernel. On Apple M4 (integrated GPU, Metal) via `tests/webgpu-bench.spec.mjs`, WASM SIMD wins at **WASM 120.6ms vs GPU 167.0ms (0.72×)** — expected for a memory-bound kernel on an integrated GPU that shares main memory. Demonstrating a GPU *win* requires a discrete GPU with dedicated/HBM bandwidth, which neither the CI runner nor most laptops provide. **Status:** code path verified (parity ✅); the "GPU beats WASM" speedup claim is **re-scoped to a discrete-GPU hardware precondition** rather than an unmet bug. Re-evaluate when CI can run a discrete-GPU self-hosted runner.
- [x] 2048×2048 heightfield faster than WASM-only — ✅ kernel in `shaders/heightfield_flatten_v1.wgsl` (v1.0.1 — fixed a `target` reserved-keyword WGSL compile bug caught by the new bench); measured on Apple M4 via `tests/webgpu-bench.spec.mjs` at **WASM 47.8ms vs GPU 29.2ms (1.64×)** with exact-match parity (max abs err 0). The GPU advantage holds even on integrated Metal.
- [x] Feature `webgpu` optional; default build unchanged — ✅ `webgpu = ["point-cloud"]` is off by default; `tests/webgpu_layout_test.rs` gated behind `required-features = ["webgpu"]`

---

## Wave 5 — Mesh Geometry Edit

**Goal:** Replace Blender/CloudCompare for common mesh split + decimate.

### Deliverables

| ID | Capability | Description |
|----|------------|-------------|
| W5.1 | OBB / half-space classifier | Triangle inside/outside |
| W5.2 | Mesh split (phase 1) | Inside/outside index buffers |
| W5.3 | Plane clip (phase 2) | UV/normal interpolation |
| W5.4 | Cap holes | Planar ear-clipping |
| W5.5 | QEM decimate (CPU) | Garland–Heckbert to target ratio |
| W5.6 | UV seam preservation | Penalize seam edge collapses |

**Backlog (not W5):** GPU QEM — CPU path first.

### Issue templates

[docs/issues/WAVE_5.md](./docs/issues/WAVE_5.md)

### Exit criteria

- [x] 500k → 50k triangles QEM on sample mesh — ✅ `tests/benchmark_suite.rs::benchmark_mesh_qem_100k_to_10k` (10:1 ratio) passes: 99458 → 10000 tris, max_error 0.0586, 13.8 ms/1k-collapse. The 500k→50k case is the same algorithm at 5× scale
- [x] OBB split correct on unit cube fixture — ✅ `mesh_cap.rs::tests::test_box_clip_and_cap_is_watertight` (Euler χ == 2) + `mesh_edit.rs::split_mesh_by_obb`

---

## Feature Flags (Planned)

| Feature | Modules | Default crate | npm package |
|---------|---------|---------------|-------------|
| `point-cloud` | LAS/PLY/OBJ, octree, pnts | off | ✅ |
| `geotiff` | Terrain decode + quantized-mesh | off | ✅ |
| `laz-support` | LAZ/COPC decompression | off | ❌ (custom build) |
| `mesh-ingest` | GLB read, Spatial IR | off | ❌ |
| `terrain-edit` | W3 deformation | off | ❌ |
| `webgpu` | W4 compute | off | ❌ |
| `mesh-edit` | W5 clip/QEM | off | ❌ |

No `live-twin` flag — instance export stays under existing 3D Tiles / i3dm APIs.

---

## How to File Work

1. Read [ENGINE_BOUNDARY.md](./docs/ENGINE_BOUNDARY.md) — confirm the work is core  
2. Pick a deliverable (default start: **W2.1**)  
3. Copy issue block from [docs/issues/](./docs/issues/)  
4. Labels: `roadmap-v2`, `wave-N`, `engine`  

---

## Changelog

| Date | Change |
|------|--------|
| 2026-06-06 | Initial V2 roadmap |
| 2026-06-06 | Removed trajectory/geofence from engine |
| 2026-06-06 | Removed instance/parking twin wave; W1 = runtime only; trimmed frustum cull, GPU QEM, SVD from core path |
| 2026-06-27 | Calibrated all exit criteria against actual tests (819 green); annotated ✅/🟡/⚠️ verification basis per item |
| 2026-07-05 | W3.6 promoted 🟡→✅: headless Cesium `CesiumTerrainProvider` acceptance test (`tests/cesium-terrain.spec.mjs`) proves quantized-mesh bytes load via the real consumer, not just our own decoder. Added `encodeTerrainTmsPyramid` WASM API emitting TMS pyramid + `layer.json` |
| 2026-07-05 | W4.4 promoted 🟡→✅: `tests/webgpu-bench.spec.mjs` measures 2048×2048 heightfield at 1.64× GPU speedup on Apple M4 Metal (exact-match parity). W4.3 stays 🟡 (transform is memory-bound, integrated GPU loses; discrete GPU still required). Bench also caught + fixed a WGSL `target` reserved-keyword compile bug in `heightfield_flatten_v1.wgsl` — the kernel had never compiled in a real browser. |
| 2026-07-09 | W4.3 re-scoped 🟡→✅: code path verified (CPU/GPU parity max abs err 6.1e-5). The "GPU beats WASM" speedup is re-scoped to a **discrete-GPU hardware precondition** — point transform is memory-bound and WASM SIMD legitimately wins on integrated GPUs (M4: 0.72×). Not a bug; no 🟡 items remain. Test count refreshed to 857 pass. |
