Files
openvistapro/docs/plans/phase-4-formats-scripts-ui.md
2026-05-20 16:04:04 -04:00

30 KiB
Raw Permalink Blame History

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/import.rs: open-format import boundary with ovp-text, feature-gated SRTM/HGT bytes, feature-gated ESRI ASCII Grid parsing, and feature-gated GeoTIFF parsing.
  • src/script.rs and src/script_exec.rs: project-owned script parsing and execution for presets, grayscale PNG heightmaps, threshold changes, and PNG render outputs.
  • src/path.rs: deterministic MakePath-inspired camera keyframe interpolation.
  • src/app_state.rs, src/app.rs, and src/bin/openvistapro_app.rs: feature-gated app shell using eframe/egui with scene controls and CPU preview rendering.
  • src/cli.rs: info, scene export, render, and script run 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 thin slices have landed as implementation checkpoints, while this file remains a historical implementation plan for follow-on work and validation.

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:

#[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.

#[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:

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:

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:

[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: GeoTIFF importer behind an optional feature (implemented)

Status: Done. Landed as the optional import-geotiff feature; this section records the implemented design. The crate survey behind it is docs/research/geotiff-import-strategy.md.

Objective: Parse single-band GeoTIFF elevation tiles into the internal HeightGrid without making default builds fragile or pulling a native dependency.

Outcome: A pure-Rust path was chosen over GDAL. The importer uses the geotiff-reader crate (local feature, cog/network feature off), declared as an optional dependency gated by the import-geotiff Cargo feature. There is no GDAL dependency and no native toolchain requirement, so import-geotiff builds and tests run anywhere cargo runs — the "documented skip if native GDAL is not installed" escape hatch is not needed and is reserved for any future, separately reviewed GDAL-backed feature.

Files:

  • src/import/geotiff.rs: cfg(feature = "import-geotiff") module exposing parse_geotiff_bytes(&[u8]) -> Result<ImportedTerrain, ImportError>. It reads the payload in memory, rejects non-single-band rasters, decodes the raster as f32, and builds a HeightGrid plus TerrainSourceMetadata (format "geotiff"). Reader/decode failures map to ImportError::MalformedSource.
  • src/import.rs: declares the geotiff submodule under the feature cfg.
  • Cargo.toml: import-geotiff = ["dep:geotiff-reader"]; geotiff-writer and ndarray are dev-dependencies used only to generate the test fixture.
  • src/cli.rs: supported_importers() and info_text() list geotiff only when the feature is built. (A render --import-geotiff flag mirroring the HGT path remains future work.)

Fixture hygiene: Tests do not commit any GeoTIFF binary. They generate a tiny single-band elevation tile in memory with geotiff-writer (dev-dependency) and parse it back, mirroring the synthetic-fixture approach used for HGT. No proprietary data and no large real-world DEMs enter the repository; real tiles may be used only for local manual verification under the already-ignored reference/ and .work/ directories.

Validation

Run: cargo test --no-default-features, cargo test geotiff --features import-geotiff, and cargo test --all-features.

Expected: default and --no-default-features builds stay GeoTIFF-free; the feature build and --all-features exercise the parser and pass without any native dependency.

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:

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:

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

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

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 ScriptCommands.

Step 4: Verify GREEN

Run: cargo test path script cli

Expected: PASS.

Feature-specific sample command

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

Status: Tasks G1G4 have landed. src/app_state.rs holds testable app state and AppAction reducers; src/app.rs and src/bin/openvistapro_app.rs provide the app-feature eframe/egui shell. The shell now docks terrain, scene/camera, render, import, script, path, and project controls around the CPU top-down/perspective preview, with a bottom status bar and backend-backed import/script/path actions. The shell map is tracked in docs/knowledgebase/ui-panel-map.md.

Remaining UI roadmap: Task G5 (WGPU renderer backend) plus the still-open gaps — legacy menus/dialogs, richer file/project chrome, animation-frame export, deeper script/path editors, and palette import/export / texture loading. All of it stays clean-room: no proprietary VistaPro assets, menus, or screenshots enter the repository.

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:

cargo fmt --check
cargo test
git diff --check

Run these before pushing any code-changing branch:

cargo fmt --check
cargo test
cargo clippy --all-targets -- -D warnings
git diff --check

Run feature-specific checks when a milestone enables optional code:

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:

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.