feat: wire shell placeholders to backend actions #12

Merged
moldybits merged 11 commits from feat/terrain-gen-abstraction into main 2026-05-17 19:30:57 -04:00
4 changed files with 93 additions and 64 deletions
Showing only changes of commit d63184f57b - Show all commits
+6
View File
@@ -34,6 +34,9 @@ Importer status:
- `ovp-text`: project-owned plain-text heightfield fixture format used for tests.
- `hgt`: enabled by the optional `hgt` Cargo feature; parses SRTM HGT payloads as square grids of big-endian signed 16-bit metre samples. The implementation and tests use open specifications and synthetic/tiny fixtures only.
- `geotiff`: enabled by the optional `import-geotiff` Cargo feature; parses single-band GeoTIFF elevation tiles in memory via the pure-Rust `geotiff-reader` crate (no GDAL, no native dependency). It supports a deliberately narrow subset — a single-band raster decoded as `f32` — and is reported by `openvistapro info` only when the feature is built.
All importer tests use tiny synthetic, project-owned fixture data: HGT uses inline synthetic byte arrays, and the GeoTIFF tests generate a tiny single-band tile in memory rather than reading committed binaries or real geodata.
To verify the importer feature surface:
@@ -42,6 +45,9 @@ cargo test hgt
cargo test hgt --features hgt
cargo run --features hgt --bin openvistapro -- info
cargo test --no-default-features
cargo test geotiff --features import-geotiff
cargo run --features import-geotiff --bin openvistapro -- info
cargo test --all-features
```
The default `render` mode writes a deterministic top-down elevation preview.
+2 -2
View File
@@ -5,7 +5,7 @@
Start simple, then split into crates when module boundaries stabilize.
- `src/terrain.rs`: height grid, bounds, sampling, normals, terrain transforms.
- `src/import/`: importers for open/safe formats; historical compatibility later.
- `src/import/`: importers for open/safe formats; historical compatibility later. Implemented so far: the project-owned `ovp-text` heightfield, an SRTM/HGT byte parser behind the `hgt` feature, and an optional single-band GeoTIFF importer (`src/import/geotiff.rs`) behind the `import-geotiff` feature. Each importer yields the same internal `HeightGrid` plus `TerrainSourceMetadata`, keeping source formats out of renderer code.
- `src/scene.rs`: camera, target, light, atmosphere, water, vegetation parameters.
- `src/render/`: CPU reference renderer first, then WGPU renderer.
- `src/script.rs`: parse and execute OpenVistaPro script commands.
@@ -20,7 +20,7 @@ Start simple, then split into crates when module boundaries stabilize.
- `image` for PNG output and texture loading.
- `nalgebra` or `glam` for vector/matrix math.
- `wgpu` plus `winit`/`egui` for the eventual interactive app.
- `gdal` support should be optional because native GDAL dependency setup can be heavy.
- GeoTIFF support uses the pure-Rust `geotiff-reader` crate behind the optional `import-geotiff` feature, so default builds stay free of native dependencies. `gdal` is intentionally not used; any future GDAL-backed path would be a separate, opt-in feature because native GDAL setup can be heavy.
## Development strategy
+39 -22
View File
@@ -344,35 +344,52 @@ Run: `cargo test dem --features import-dem`
Expected: PASS.
### Task C3: Add GeoTIFF spike behind an optional feature
### Task C3: GeoTIFF importer behind an optional feature (implemented)
**Objective:** Decide whether to use a pure-Rust TIFF path or optional GDAL without making default builds fragile.
**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`](../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:**
- 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
- `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.)
**Step 1: Write failing test or spike acceptance check**
**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.
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.
**Validation**
**Step 2: Verify RED**
Run: `cargo test --no-default-features`, `cargo test geotiff --features import-geotiff`,
and `cargo test --all-features`.
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.
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
+46 -40
View File
@@ -1,11 +1,13 @@
# GeoTIFF Import Strategy Research Note
> **Status:** Spike / decision note. Documentation-only — no code or `Cargo.toml`
> changes accompany this note.
> **Status:** Decision implemented. The recommended pure-Rust path has landed
> as the optional `import-geotiff` feature; the crate survey below is kept for
> provenance.
>
> **Context:** Phase 4 Milestone C, Task C3 ("Add GeoTIFF spike behind an
> optional feature"). This note records the crate survey and the recommended
> direction so the implementation slice can start from a settled decision.
> **Context:** Phase 4 Milestone C, Task C3 ("GeoTIFF importer behind an
> optional feature"). This note recorded the crate survey and recommended
> direction; the "Implementation notes" section below describes what was
> actually built.
## Recommendation
@@ -136,44 +138,48 @@ Consistent with Task C1's existing feature scaffold (`import-dem`,
- The committed fixture should be only as large as the test needs (target:
well under a kilobyte).
## Follow-up implementation slice
## Implementation notes
Concrete next slice for Task C3, to be done TDD (RED → minimal GREEN →
validation) on a separate implementation branch:
The recommended pure-Rust path has landed. What was built:
1. **Add the feature + dependency.** In `Cargo.toml`, make `geotiff-reader` an
optional dependency gated by `import-geotiff` (default `cog` feature
disabled). Confirm `cargo test --no-default-features` and the default build
stay GeoTIFF-free.
2. **Create `src/import/geotiff.rs`.** A `cfg(feature = "import-geotiff")`
module exposing something like
`parse_geotiff_bytes(&[u8]) -> Result<ImportedTerrain, ImportError>`,
reusing the `ImportedTerrain` / `ImportError` / `TerrainSourceMetadata`
types from Milestone A.
3. **Generate the synthetic fixture.** Add a tiny generated single-band
elevation GeoTIFF as a project-owned fixture (separate commit from the
parser, per the plan's commit strategy).
4. **RED test.** `cargo test geotiff --features import-geotiff` — assert
dimensions, sample values, and metadata; assert malformed input yields a
typed error.
5. **Minimal GREEN.** Parse only the tiny supported subset (single band,
uncompressed, known sample type). Reject everything else explicitly;
document that broad real-world GeoTIFF coverage is future work.
6. **CLI plumbing (optional, can be a later slice).** Mirror the HGT pattern
(Task B3): an `--import-geotiff <path>` render input, gated so it only
appears when the feature is built.
7. **Validation.** `cargo test --no-default-features`,
`cargo test --features import-geotiff`, `cargo test --all-features`,
`cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`,
`git diff --check`.
8. **Update `openvistapro info`** to report GeoTIFF support honestly — only
once a real parser and fixture exist (consistent with Task A3).
1. **Feature + dependency.** `Cargo.toml` declares
`import-geotiff = ["dep:geotiff-reader"]`; `geotiff-reader` is an optional
dependency built with its `local` feature and `default-features = false`, so
the network-oriented `cog` feature stays off. Default and
`--no-default-features` builds remain GeoTIFF-free.
2. **`src/import/geotiff.rs`.** A `cfg(feature = "import-geotiff")` module
exposing `parse_geotiff_bytes(&[u8]) -> Result<ImportedTerrain, ImportError>`,
reusing the `ImportedTerrain` / `ImportError` / `TerrainSourceMetadata` types
from Milestone A. It reads the payload in memory, rejects rasters that are
not single-band, decodes the raster as `f32` into a row-major `HeightGrid`,
and records `TerrainSourceMetadata` with format `"geotiff"`. Reader and
decode failures map to `ImportError::MalformedSource`.
3. **Synthetic fixtures only.** No GeoTIFF binary is committed. Tests generate a
tiny single-band elevation tile in memory with the `geotiff-writer`
dev-dependency (alongside `ndarray`) and parse it back — mirroring the
inline-bytes approach used for HGT. Real USGS/NASA tiles may be used for
local manual verification only, under the already-ignored `reference/` and
`.work/` directories.
4. **Tests.** `cargo test geotiff --features import-geotiff` asserts dimensions,
sample values, and metadata, and checks that non-GeoTIFF input yields a typed
`ImportError::MalformedSource`.
5. **Supported subset.** Only the narrow single-band `f32` case is parsed;
broader real-world GeoTIFF coverage (multi-band, exotic compression, full
geo-tag interpretation) remains future work.
6. **`openvistapro info`.** `supported_importers()` and `info_text()` list
`geotiff` only when the feature is built, keeping `info` honest.
7. **GDAL is not part of the landed feature.** The importer has zero native
dependencies; a GDAL-backed path stays out of scope until a separate review
approves the native-dependency cost, and would use its own opt-in feature
name rather than `import-geotiff`.
If `geotiff-reader` proves insufficient during the spike (missing geo-tag
coverage, API gaps), fall back in this order: `georaster``wbgeotiff`
hand-rolled geo-tag reading on top of `image`'s TIFF decoder. A GDAL-backed
path stays out of scope until a separate review approves the native-dependency
cost.
**Not yet done:** CLI render plumbing. A `render --import-geotiff <path>` input
mirroring the HGT pattern (Task B3) is still future work; the parser is reachable
through the library API and reported by `info`, but not yet wired into `render`.
If `geotiff-reader` later proves insufficient (missing geo-tag coverage, API
gaps), the documented fallback order remains `georaster``wbgeotiff`
hand-rolled geo-tag reading on top of `image`'s TIFF decoder.
## Sources