904 lines
27 KiB
Markdown
904 lines
27 KiB
Markdown
# Phase 4+ Formats, Scripts, Paths, and UI Implementation Plan
|
|
|
|
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
|
|
|
|
**Goal:** Expand OpenVistaPro from its current deterministic Rust CLI renderer into a clean-room terrain visualization pipeline with open-format importers, scriptable rendering, MakePath-style camera paths, and an eventual WGPU/egui application.
|
|
|
|
**Architecture:** Keep the existing single Rust crate until APIs stabilize. Add small modules around the current `HeightGrid`, `Scene`, `.ovp.toml` scene files, CLI, CPU top-down renderer, and CPU perspective demo renderer. Keep all import/compatibility code behind explicit module boundaries and optional features so the clean internal model stays independent from source formats.
|
|
|
|
**Tech Stack:** Rust 2024, cargo, clap, serde, toml, image, optional importer crates/features, future glam or nalgebra for path math, future wgpu/winit/egui for the app.
|
|
|
|
---
|
|
|
|
## Current baseline
|
|
|
|
The repository already has:
|
|
|
|
- `src/terrain.rs`: `HeightGrid` with validated dimensions, row-major samples, deterministic `plane` and `radial_hill` fixtures.
|
|
- `src/scene.rs`: serializable camera, light, water, tree-line, snow-line, and haze settings.
|
|
- `src/scene_file.rs`: project-owned `.ovp.toml` files with `schema = "openvistapro.scene"`, `version = 1`, and a serialized `Scene` payload.
|
|
- `src/colormap.rs`: deterministic elevation-band colors.
|
|
- `src/render.rs`: deterministic top-down PNG renderer plus spike-quality CPU perspective raymarcher.
|
|
- `src/cli.rs`: `info`, `scene export`, and `render` commands.
|
|
- `README.md`, `docs/legal/asset-policy.md`, `docs/research/reference-inventory.md`, and `docs/knowledgebase/*.md`: clean-room context and project constraints.
|
|
|
|
Phase 4 work should preserve this baseline and extend it in thin, tested slices.
|
|
|
|
## Non-negotiable clean-room and repository hygiene rules
|
|
|
|
- Do not read, copy, decompile, translate, track, or commit proprietary VistaPro binaries, installers, archives, ISO/7z files, screenshots, extracted program files, sample landscapes, manuals, or scratch outputs.
|
|
- Keep `reference/`, `.work/`, and `target/` local-only and ignored.
|
|
- Use open/public formats and synthetic fixtures for tests.
|
|
- Commit only original source, original documentation, tiny synthetic fixtures, and openly licensed data with attribution.
|
|
- Treat VistaPro and MakePath as workflow inspiration. Do not attempt historical file compatibility until a later, separately reviewed clean-room plan approves the scope.
|
|
- Prefer TDD for every code change: write a focused failing test, run it and confirm the expected failure, implement the smallest code, then run the targeted test and the full validation suite.
|
|
|
|
## Dependency map and ordering
|
|
|
|
1. Importer foundation must come first: module shape, metadata, and tiny synthetic fixtures.
|
|
2. Add one importer at a time, starting with the smallest pure-Rust parser. HGT/SRTM is the best first real open-format target because the file layout is simple.
|
|
3. Add importer CLI plumbing after at least one importer can return `HeightGrid` plus source metadata.
|
|
4. Evolve scene/project metadata only after imported terrain needs provenance, source units, or vertical scale.
|
|
5. Add the script MVP after CLI render/import commands are stable, because scripts should drive existing commands instead of inventing parallel behavior.
|
|
6. Add path generation after script commands can carry camera keyframes.
|
|
7. Add WGPU/egui only after the CPU renderer, scene files, importers, script execution, and path generation have stable tests and sample commands.
|
|
|
|
## Milestone A: Importer foundation
|
|
|
|
### Task A1: Create an importer module skeleton
|
|
|
|
**Objective:** Define a stable place for open terrain importers without changing renderer behavior.
|
|
|
|
**Files:**
|
|
- Create: `src/import.rs` or `src/import/mod.rs`
|
|
- Modify: `src/lib.rs`
|
|
- Test: unit tests inside `src/import.rs` or `src/import/mod.rs`
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Add a test asserting that a `TerrainSourceMetadata` value records source format, dimensions, and elevation units.
|
|
|
|
Expected shape:
|
|
|
|
```rust
|
|
#[test]
|
|
fn metadata_records_format_dimensions_and_units() {
|
|
let meta = TerrainSourceMetadata::new("hgt", 1201, 1201, ElevationUnit::Meters);
|
|
assert_eq!(meta.format(), "hgt");
|
|
assert_eq!(meta.width(), 1201);
|
|
assert_eq!(meta.height(), 1201);
|
|
assert_eq!(meta.elevation_unit(), ElevationUnit::Meters);
|
|
}
|
|
```
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test import::tests::metadata_records_format_dimensions_and_units`
|
|
|
|
Expected: FAIL because `import` and its metadata types do not exist.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Create the module, expose it from `src/lib.rs`, and add only the metadata types and accessors required by the test.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test import`
|
|
|
|
Expected: PASS.
|
|
|
|
**Step 5: Full validation**
|
|
|
|
Run: `cargo fmt --check && cargo test && cargo clippy --all-targets -- -D warnings`
|
|
|
|
### Task A2: Define importer result and error boundaries
|
|
|
|
**Objective:** Return both a `HeightGrid` and source metadata while keeping parser errors typed and user-displayable.
|
|
|
|
**Files:**
|
|
- Modify: `src/import.rs` or `src/import/mod.rs`
|
|
- Test: unit tests in the same module
|
|
|
|
**Step 1: Write failing tests**
|
|
|
|
Add tests for:
|
|
|
|
- `ImportedTerrain::new(grid, metadata)` exposes the grid and metadata.
|
|
- `ImportError::InvalidDimensions` renders a helpful display message.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test import::tests::imported_terrain_exposes_grid_and_metadata import::tests::invalid_dimensions_error_is_displayable`
|
|
|
|
Expected: FAIL because the types do not exist yet.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add `ImportedTerrain`, `ImportError`, `Display`, and `Error` impls. Do not add a parser yet.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test import`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task A3: Advertise planned importers without claiming support
|
|
|
|
**Objective:** Keep `openvistapro info` honest while pointing users to planned importer work.
|
|
|
|
**Files:**
|
|
- Modify: `src/cli.rs`
|
|
- Test: unit tests inside `src/cli.rs`
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Add a test that `info_text()` contains a separate planned/importer-roadmap line or docs pointer while `supported_importers()` remains empty until the first parser lands.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test cli::tests::info_text_mentions_importer_roadmap_without_enabling_importers`
|
|
|
|
Expected: FAIL because no importer roadmap text exists.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Update `info_text()` only. Do not list DEM/HGT/GeoTIFF as supported until real parsers exist.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test cli::tests::info_text_mentions_importer_roadmap_without_enabling_importers`
|
|
|
|
Expected: PASS.
|
|
|
|
## Milestone B: First open terrain importer: HGT/SRTM
|
|
|
|
### Task B1: Add tiny HGT parser from bytes
|
|
|
|
**Objective:** Parse a square HGT/SRTM-style big-endian signed 16-bit elevation grid into `HeightGrid` using synthetic bytes.
|
|
|
|
**Files:**
|
|
- Create or modify: `src/import/hgt.rs` if using a directory module, otherwise add `hgt` submodule in `src/import.rs`
|
|
- Modify: `src/import/mod.rs` or `src/import.rs`
|
|
- Test: unit tests for the HGT parser
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Use a tiny synthetic 3x3 byte buffer. Do not use real downloaded HGT files yet.
|
|
|
|
```rust
|
|
#[test]
|
|
fn parses_tiny_hgt_big_endian_i16_grid() {
|
|
let bytes = [
|
|
0x00, 0x01, 0x00, 0x02, 0x00, 0x03,
|
|
0x00, 0x04, 0x00, 0x05, 0x00, 0x06,
|
|
0x00, 0x07, 0x00, 0x08, 0x00, 0x09,
|
|
];
|
|
let imported = parse_hgt_bytes(&bytes, 3).expect("3x3 HGT bytes should parse");
|
|
assert_eq!(imported.grid().width(), 3);
|
|
assert_eq!(imported.grid().height(), 3);
|
|
assert_eq!(imported.grid().sample(0, 0), Some(1.0));
|
|
assert_eq!(imported.grid().sample(2, 2), Some(9.0));
|
|
}
|
|
```
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test hgt::tests::parses_tiny_hgt_big_endian_i16_grid`
|
|
|
|
Expected: FAIL because the parser is missing.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Read pairs with `i16::from_be_bytes`, convert to `f32`, and reject buffers whose length is not `side * side * 2`.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test hgt`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task B2: Reject malformed HGT buffers
|
|
|
|
**Objective:** Prevent silent truncation or wrong dimensions.
|
|
|
|
**Files:**
|
|
- Modify: HGT parser module
|
|
- Test: HGT unit tests
|
|
|
|
**Step 1: Write failing tests**
|
|
|
|
Add tests for:
|
|
|
|
- Odd byte count.
|
|
- Declared side length whose sample count does not match the byte count.
|
|
- Zero side length.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test hgt::tests::rejects_malformed_hgt_buffers`
|
|
|
|
Expected: FAIL for missing validation.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Map validation failures to `ImportError` variants.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test hgt`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task B3: Add CLI import path for HGT to render
|
|
|
|
**Objective:** Let the user render a synthetic or local HGT file through the existing render pipeline.
|
|
|
|
**Files:**
|
|
- Modify: `src/cli.rs`
|
|
- Modify: importer module
|
|
- Test: `src/cli.rs` tests plus importer tests
|
|
|
|
**Step 1: Write failing parser test**
|
|
|
|
Add a CLI parsing test for a future command shape, for example:
|
|
|
|
```text
|
|
openvistapro render --import-hgt /tmp/tiny.hgt --hgt-side 3 --output /tmp/out.png
|
|
```
|
|
|
|
Keep `--preset` mutually exclusive with importer input once implemented.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test cli::tests::parses_render_with_hgt_import`
|
|
|
|
Expected: FAIL because the flags do not exist.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add optional render args for HGT input and side length. Build the `HeightGrid` from HGT when present; otherwise use the existing preset path.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test cli::tests::parses_render_with_hgt_import cargo test hgt`
|
|
|
|
Expected: PASS.
|
|
|
|
**Feature-specific sample command**
|
|
|
|
Run after creating a tiny synthetic fixture in `/tmp`:
|
|
|
|
```bash
|
|
cargo run -- render --import-hgt /tmp/openvistapro-tiny.hgt --hgt-side 3 --output /tmp/openvistapro-hgt.png
|
|
```
|
|
|
|
Expected: PNG is created and has 3x3 dimensions unless explicit render scaling is added later.
|
|
|
|
## Milestone C: DEM and GeoTIFF planning and feature gates
|
|
|
|
### Task C1: Add importer feature flags without pulling heavy dependencies by default
|
|
|
|
**Objective:** Make optional importer capabilities visible and keep the default build lightweight.
|
|
|
|
**Files:**
|
|
- Modify: `Cargo.toml`
|
|
- Modify: `src/import.rs` or `src/import/mod.rs`
|
|
- Test: compile-time cfg tests where practical
|
|
|
|
**Step 1: Write failing check**
|
|
|
|
Add a cfg-gated unit test or compile guard showing `dem` and `geotiff` modules are not required in the default build.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test --no-default-features`
|
|
|
|
Expected: FAIL only until features are declared and module cfgs compile.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add feature entries such as:
|
|
|
|
```toml
|
|
[features]
|
|
default = []
|
|
import-dem = []
|
|
import-hgt = []
|
|
import-geotiff = []
|
|
```
|
|
|
|
Only add external dependencies when a parser needs them.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test --no-default-features && cargo test --all-features`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task C2: Add ASCII DEM parser MVP
|
|
|
|
**Objective:** Support a simple open DEM text fixture before attempting broad real-world DEM variants.
|
|
|
|
**Files:**
|
|
- Create: `src/import/dem.rs`
|
|
- Modify: `src/import/mod.rs`
|
|
- Test: DEM unit tests with tiny inline text fixtures
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Use a tiny project-owned text fixture that declares width, height, units, and samples. Avoid proprietary files.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test dem::tests::parses_tiny_ascii_dem_fixture --features import-dem`
|
|
|
|
Expected: FAIL because parser is missing.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Parse only the documented tiny subset. Reject unknown or incomplete input. Document that full DEM dialect support is future work.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test dem --features import-dem`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task C3: Add GeoTIFF spike behind an optional feature
|
|
|
|
**Objective:** Decide whether to use a pure-Rust TIFF path or optional GDAL without making default builds fragile.
|
|
|
|
**Files:**
|
|
- Create: `src/import/geotiff.rs`
|
|
- Modify: `Cargo.toml`
|
|
- Modify: `docs/plans/phase-4-formats-scripts-ui.md` or follow-up notes if the spike changes direction
|
|
- Test: tiny openly licensed or generated GeoTIFF fixture only if licensing is clear
|
|
|
|
**Step 1: Write failing test or spike acceptance check**
|
|
|
|
Prefer a generated fixture committed under an explicit license. If that is not practical, write parser-interface tests and keep the full fixture local until license is resolved.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test geotiff --features import-geotiff`
|
|
|
|
Expected: FAIL until the selected crate and parser stub exist.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add only enough to detect/parse a tiny supported subset. If GDAL is required, keep the feature opt-in and document native setup.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test --features import-geotiff`
|
|
|
|
Expected: PASS on systems with the optional dependency available, or a documented skip if native GDAL is not installed.
|
|
|
|
## Milestone D: Scene/project metadata evolution
|
|
|
|
### Task D1: Add terrain source metadata to project-owned scene files
|
|
|
|
**Objective:** Preserve source provenance and vertical units without putting importer-specific details into the renderer.
|
|
|
|
**Files:**
|
|
- Modify: `src/scene.rs`
|
|
- Modify: `src/scene_file.rs`
|
|
- Test: scene and scene_file unit tests
|
|
|
|
**Step 1: Write failing tests**
|
|
|
|
Add tests that:
|
|
|
|
- Default scenes have no terrain source metadata.
|
|
- Scene file round-trips a scene with source format, original dimensions, elevation unit, and optional path display string.
|
|
- Version handling remains explicit.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test scene_file::tests::round_trips_terrain_source_metadata`
|
|
|
|
Expected: FAIL because scene metadata does not exist.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add serializable structs and keep fields optional. Decide whether this is backward-compatible under `SCENE_VERSION = 1` or whether to bump to version 2 with an upgrade path.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test scene scene_file`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task D2: Add project file plan before implementing a new container
|
|
|
|
**Objective:** Decide whether `.ovp.toml` remains a scene-only file or grows into a project document that references terrain/import metadata.
|
|
|
|
**Files:**
|
|
- Create: `docs/plans/phase-4-project-file.md` if needed
|
|
- No production code in this task unless the plan chooses the format
|
|
|
|
**Verification:**
|
|
|
|
Run: `git diff --check`
|
|
|
|
Expected: no whitespace errors.
|
|
|
|
## Milestone E: Script language MVP
|
|
|
|
### Task E1: Define a tiny project-owned script command model
|
|
|
|
**Objective:** Represent a script as commands over existing scene/render operations without emulating proprietary syntax.
|
|
|
|
**Files:**
|
|
- Create: `src/script.rs`
|
|
- Modify: `src/lib.rs`
|
|
- Test: unit tests inside `src/script.rs`
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Start with a deliberately small, original syntax such as:
|
|
|
|
```text
|
|
set camera.position 0 30 -20
|
|
set camera.target 16 3 16
|
|
render frame 0
|
|
```
|
|
|
|
Add a test that parses those lines into typed commands.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test script::tests::parses_set_camera_and_render_frame_commands`
|
|
|
|
Expected: FAIL because `script` does not exist.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add `Script`, `ScriptCommand`, parser errors, and a line-oriented parser. Do not execute commands yet.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test script`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task E2: Execute script scene mutations without rendering
|
|
|
|
**Objective:** Apply `set` commands to `Scene` so script behavior can be tested without image output.
|
|
|
|
**Files:**
|
|
- Modify: `src/script.rs`
|
|
- Test: script unit tests
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Assert that applying parsed camera and haze commands changes a `Scene` exactly.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test script::tests::applies_scene_mutation_commands`
|
|
|
|
Expected: FAIL because execution is missing.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add an executor that handles only scene mutations. Unsupported commands return typed errors.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test script`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task E3: Add CLI script dry-run
|
|
|
|
**Objective:** Let users validate script parsing and scene mutations before rendering frames.
|
|
|
|
**Files:**
|
|
- Modify: `src/cli.rs`
|
|
- Modify: `src/script.rs`
|
|
- Test: CLI parser tests and script tests
|
|
|
|
**Step 1: Write failing CLI test**
|
|
|
|
Target command:
|
|
|
|
```text
|
|
openvistapro script check --input /tmp/example.ovpscript
|
|
```
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test cli::tests::parses_script_check_command`
|
|
|
|
Expected: FAIL because command is missing.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add `script check` to parse and report command count. No rendering yet.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test cli::tests::parses_script_check_command cargo test script`
|
|
|
|
Expected: PASS.
|
|
|
|
**Feature-specific sample command**
|
|
|
|
```bash
|
|
cargo run -- script check --input /tmp/openvistapro-example.ovpscript
|
|
```
|
|
|
|
Expected: prints a parse summary and exits successfully for valid script text.
|
|
|
|
### Task E4: Render a scripted single frame
|
|
|
|
**Objective:** Let a script produce one PNG through the existing CPU renderer.
|
|
|
|
**Files:**
|
|
- Modify: `src/cli.rs`
|
|
- Modify: `src/script.rs`
|
|
- Test: CLI/script tests
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Add a test that a script with one `render frame 0` command produces a PNG at a requested output directory using a small synthetic `HeightGrid`.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test script::tests::renders_single_scripted_frame`
|
|
|
|
Expected: FAIL because render execution is missing.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Use existing `render_top_down_to_path` or `render_perspective_to_path`. Keep camera-demo/perspective mode explicit.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test script cli`
|
|
|
|
Expected: PASS.
|
|
|
|
**Feature-specific sample command**
|
|
|
|
```bash
|
|
cargo run -- script render --input /tmp/openvistapro-example.ovpscript --preset hill --output-dir /tmp/openvistapro-frames
|
|
```
|
|
|
|
Expected: creates `frame-0000.png` for the MVP.
|
|
|
|
## Milestone F: MakePath-style camera path generator
|
|
|
|
### Task F1: Add camera keyframe data structures
|
|
|
|
**Objective:** Represent camera/target path control points in project-owned types.
|
|
|
|
**Files:**
|
|
- Create: `src/path.rs`
|
|
- Modify: `src/lib.rs`
|
|
- Test: unit tests inside `src/path.rs`
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Assert that a path with two keyframes exposes start/end camera positions and frame numbers.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test path::tests::camera_path_records_keyframes_in_order`
|
|
|
|
Expected: FAIL because path types do not exist.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add `CameraKeyframe` and `CameraPath`. Validate monotonically increasing frame numbers.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test path`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task F2: Add linear interpolation before splines
|
|
|
|
**Objective:** Generate deterministic per-frame camera poses from two keyframes.
|
|
|
|
**Files:**
|
|
- Modify: `src/path.rs`
|
|
- Test: path unit tests
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Assert frame 5 between frame 0 and frame 10 is the midpoint for position, target, and FOV.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test path::tests::linear_interpolation_returns_midpoint_pose`
|
|
|
|
Expected: FAIL because interpolation is missing.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add linear interpolation only. Avoid splines until linear output is tested.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test path`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task F3: Add spline interpolation as a separate, tested behavior
|
|
|
|
**Objective:** Add MakePath-inspired smooth camera paths without copying its implementation or file formats.
|
|
|
|
**Files:**
|
|
- Modify: `src/path.rs`
|
|
- Test: path unit tests
|
|
|
|
**Step 1: Write failing tests**
|
|
|
|
Use project-owned numeric fixtures for Catmull-Rom or cubic interpolation:
|
|
|
|
- Interpolation passes through keyframes.
|
|
- Endpoints do not overshoot for a simple straight path.
|
|
- Output is deterministic.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test path::tests::spline_path_passes_through_keyframes`
|
|
|
|
Expected: FAIL because spline mode is missing.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Implement a standard documented spline algorithm from first principles. Link to the algorithm reference in comments if useful; do not use MakePath code or data.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test path`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task F4: Export generated paths to the OpenVistaPro script MVP
|
|
|
|
**Objective:** Let the path generator create a script that the script renderer already understands.
|
|
|
|
**Files:**
|
|
- Modify: `src/path.rs`
|
|
- Modify: `src/script.rs`
|
|
- Modify: `src/cli.rs`
|
|
- Test: path/script/CLI tests
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Assert that a two-keyframe path exported for 3 frames produces 3 camera set commands and 3 render commands.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test path::tests::exports_path_as_script_commands`
|
|
|
|
Expected: FAIL because export is missing.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add a converter from path samples to `ScriptCommand`s.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test path script cli`
|
|
|
|
Expected: PASS.
|
|
|
|
**Feature-specific sample command**
|
|
|
|
```bash
|
|
cargo run -- path generate --input /tmp/openvistapro-path.ovppath --frames 60 --output /tmp/openvistapro-path.ovpscript
|
|
cargo run -- script check --input /tmp/openvistapro-path.ovpscript
|
|
```
|
|
|
|
Expected: generated script parses successfully.
|
|
|
|
## Milestone G: WGPU/egui application after CLI stability
|
|
|
|
### Task G1: Create app-state crate/module without a window
|
|
|
|
**Objective:** Separate UI state from rendering and file formats before adding WGPU.
|
|
|
|
**Files:**
|
|
- Create: `src/app_state.rs`
|
|
- Modify: `src/lib.rs`
|
|
- Test: unit tests inside `src/app_state.rs`
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Assert default app state contains a `Scene`, a synthetic terrain preset selection, and no loaded source file.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test app_state::tests::default_app_state_has_scene_and_no_loaded_file`
|
|
|
|
Expected: FAIL because app state does not exist.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add pure data structures only. Do not add WGPU or egui yet.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test app_state`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task G2: Add egui controls as pure state mutations first
|
|
|
|
**Objective:** Make UI semantics testable before creating a native window.
|
|
|
|
**Files:**
|
|
- Modify: `src/app_state.rs`
|
|
- Test: unit tests in `src/app_state.rs`
|
|
|
|
**Step 1: Write failing tests**
|
|
|
|
Add tests for operations like setting water level, moving camera target, toggling renderer mode, and selecting an importer source.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test app_state::tests::updates_scene_controls_from_ui_actions`
|
|
|
|
Expected: FAIL because action handling is missing.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add an `AppAction` enum and reducer-style `apply` method.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test app_state`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task G3: Add native app feature gate
|
|
|
|
**Objective:** Keep CLI builds fast while making room for WGPU/winit/egui.
|
|
|
|
**Files:**
|
|
- Modify: `Cargo.toml`
|
|
- Create: `src/bin/openvistapro_app.rs` or a gated module
|
|
- Test: compile checks
|
|
|
|
**Step 1: Write failing check**
|
|
|
|
Add a feature-gated binary or module that should compile only with `--features app`.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo check --features app`
|
|
|
|
Expected: FAIL until dependencies/module stubs exist.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add a window stub with no GPU terrain rendering. It may display app title and basic scene values.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo check --features app && cargo test --all-features`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task G4: Bridge CPU preview into the app
|
|
|
|
**Objective:** Display the existing deterministic CPU top-down preview in egui before adding GPU terrain rendering.
|
|
|
|
**Files:**
|
|
- Modify: app module/binary
|
|
- Modify: `src/render.rs` only if a reusable image buffer helper is needed
|
|
- Test: app-state/render tests where possible
|
|
|
|
**Step 1: Write failing test**
|
|
|
|
Add a pure test that app state can request a preview image and receives dimensions matching the current terrain.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test app_state::tests::preview_request_returns_expected_dimensions --features app`
|
|
|
|
Expected: FAIL until preview bridge exists.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Reuse `render_top_down` and upload/display the resulting image in the UI. Avoid duplicating renderer logic.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test --features app`
|
|
|
|
Expected: PASS.
|
|
|
|
### Task G5: Add WGPU renderer only after CPU preview is usable
|
|
|
|
**Objective:** Introduce GPU rendering as a second renderer backend, not as a replacement for the tested CPU reference.
|
|
|
|
**Files:**
|
|
- Create: `src/gpu_renderer.rs` or `src/render/gpu.rs` after deciding whether to split `src/render.rs`
|
|
- Modify: app module/binary
|
|
- Test: compile checks and pure math tests
|
|
|
|
**Step 1: Write failing checks**
|
|
|
|
Start with tests for camera matrices, terrain buffer dimensions, and shader-uniform construction. Avoid screenshot tests at first.
|
|
|
|
**Step 2: Verify RED**
|
|
|
|
Run: `cargo test gpu_renderer --features app`
|
|
|
|
Expected: FAIL because GPU renderer types do not exist.
|
|
|
|
**Step 3: Implement minimal code**
|
|
|
|
Add resource creation and a flat/grid terrain draw path. Keep renderer API driven by `HeightGrid` and `Scene`.
|
|
|
|
**Step 4: Verify GREEN**
|
|
|
|
Run: `cargo test --features app && cargo check --features app`
|
|
|
|
Expected: PASS.
|
|
|
|
## Cross-milestone validation commands
|
|
|
|
Run these before every focused commit:
|
|
|
|
```bash
|
|
cargo fmt --check
|
|
cargo test
|
|
git diff --check
|
|
```
|
|
|
|
Run these before pushing any code-changing branch:
|
|
|
|
```bash
|
|
cargo fmt --check
|
|
cargo test
|
|
cargo clippy --all-targets -- -D warnings
|
|
git diff --check
|
|
```
|
|
|
|
Run feature-specific checks when a milestone enables optional code:
|
|
|
|
```bash
|
|
cargo test --no-default-features
|
|
cargo test --all-features
|
|
cargo check --features app
|
|
cargo test --features import-dem
|
|
cargo test --features import-geotiff
|
|
```
|
|
|
|
Use sample commands as acceptance checks when the related CLI surface exists:
|
|
|
|
```bash
|
|
cargo run -- info
|
|
cargo run -- scene export --output /tmp/openvistapro-default.ovp.toml
|
|
cargo run -- render --preset hill --width 256 --height 256 --output /tmp/openvistapro-hill.png
|
|
cargo run -- render --preset hill --scene /tmp/openvistapro-default.ovp.toml --width 256 --height 256 --output /tmp/openvistapro-hill-from-scene.png
|
|
cargo run -- render --preset hill --camera-demo --width 256 --height 192 --output /tmp/openvistapro-perspective.png
|
|
```
|
|
|
|
## Commit strategy
|
|
|
|
- Commit the plan itself as `docs: expand phase 4 implementation plan`.
|
|
- Future implementation branches should commit one RED/GREEN slice at a time.
|
|
- Keep importer fixture commits separate from parser commits when licenses or provenance need review.
|
|
- Do not push generated PNGs, local HGT/DEM/GeoTIFF samples, archives, or scratch directories.
|
|
|
|
## Definition of done for Phase 4+
|
|
|
|
Phase 4 is not one large task. It is complete when:
|
|
|
|
1. At least one open terrain importer can create a `HeightGrid` from a tested, legal fixture.
|
|
2. `openvistapro info` reports real importer support accurately.
|
|
3. `.ovp.toml` scene/project metadata can preserve terrain provenance where needed.
|
|
4. A project-owned script MVP can check scripts and render at least one deterministic frame.
|
|
5. A path generator can produce scriptable camera frames from project-owned keyframes.
|
|
6. The interactive app has tested app state and can show a CPU preview before WGPU terrain rendering is attempted.
|
|
7. Default builds remain clean: `cargo fmt --check`, `cargo test`, `cargo clippy --all-targets -- -D warnings`, and `git diff --check` pass.
|