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 114 additions and 6 deletions
Showing only changes of commit bbb607445e - Show all commits
+22
View File
@@ -13,6 +13,7 @@ This repository currently contains:
- An implementation roadmap under `docs/plans/`.
- Legal and reference-material hygiene notes under `docs/legal/` and `docs/research/`.
- A clean-room terrain import boundary with project-owned `ovp-text` fixtures and an SRTM/HGT byte importer behind the `hgt` Cargo feature.
- A clean-room procedural terrain generator module (`src/terrain_gen.rs`) that produces deterministic, seeded synthetic landscapes.
## Development
@@ -50,6 +51,27 @@ cargo run --features import-geotiff --bin openvistapro -- info
cargo test --all-features
```
## Terrain generation
OpenVistaPro includes a clean-room procedural terrain generator in
`src/terrain_gen.rs`. Its public surface is intentionally small:
`TerrainGenerationSpec` captures the seed and dimensions of a generation
request, the `TerrainGenerator` trait defines the `generate` boundary, and
`DeterministicTerrainGenerator` implements it with a seeded value-noise fBm
stack that returns a plain `HeightGrid` — generator state never leaks into the
grid or renderer.
Generation is seeded and deterministic: an identical spec always yields an
identical grid, and different seeds diverge. As with the importers, the
generator tests use only tiny synthetic, project-owned fixtures — no committed
binaries and no real geodata.
To verify the terrain-generation surface:
```bash
cargo test terrain_gen -- --nocapture
```
The default `render` mode writes a deterministic top-down elevation preview.
Passing `--camera-demo` switches to the current CPU perspective renderer spike:
a simple pinhole-camera raymarcher with bilinear height sampling, fixed step
+8
View File
@@ -5,6 +5,14 @@
Start simple, then split into crates when module boundaries stabilize.
- `src/terrain.rs`: height grid, bounds, sampling, normals, terrain transforms.
- `src/terrain_gen.rs`: procedural terrain generator boundary. `TerrainGenerationSpec`
captures a request's seed and dimensions, and `DeterministicTerrainGenerator`
(a seeded value-noise fBm generator behind the `TerrainGenerator` trait)
consumes that spec and returns a plain `HeightGrid` — generator state never
leaks into the grid or renderer. Generation is seeded and deterministic: an
identical spec always yields an identical grid. Its tests use only tiny
synthetic, project-owned fixtures, never committed binaries or real geodata,
and run via `cargo test terrain_gen -- --nocapture`.
- `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.
+19 -6
View File
@@ -9,18 +9,19 @@ Define the next terrain-generation workstream so future slices stay small, deter
OpenVistaPro already has:
- `src/terrain.rs` with immutable `HeightGrid` storage, safe indexing, min/max, and deterministic `plane` / `radial_hill` fixtures.
- `src/terrain_gen.rs` with the procedural terrain generator slice: `TerrainGenerationSpec` (seed plus dimensions), the `TerrainGenerator` trait, and `DeterministicTerrainGenerator`, a deterministic seeded value-noise fBm generator that returns a plain `HeightGrid`.
- `src/render.rs` with a deterministic top-down preview and a CPU perspective spike that only depends on `HeightGrid` + `Scene`.
- `src/scene.rs` and `src/app_state.rs` for scene controls and preview wiring.
- `src/import.rs` for the open-format import boundary.
- `docs/plans/initial-roadmap.md` and `docs/plans/phase-4-formats-scripts-ui.md` for the broader project sequence.
The missing piece is a dedicated procedural terrain generator pipeline that can produce richer synthetic landscapes without mixing algorithm state into `HeightGrid`.
The first procedural slice has now landed in `src/terrain_gen.rs`: it produces richer synthetic landscapes without mixing algorithm state into `HeightGrid`. The remaining work is preset profiles and the later enhancements tracked in the roadmap slices below.
## Decision summary
### First generator family: seeded 2D value-noise fBm
### First generator family: seeded 2D value-noise fBm (landed)
Implement a deterministic, seedable fractal terrain family first, built from 2D value noise with fBm-style octaves.
This family has landed as `DeterministicTerrainGenerator` in `src/terrain_gen.rs`: a deterministic, seedable fractal terrain generator built from 2D value noise with fBm-style octaves.
Why this first:
@@ -38,7 +39,7 @@ Keep `HeightGrid` as pure data plus basic helpers.
Recommended split:
- `src/terrain.rs`: immutable grid storage, validation, indexing, min/max, and tiny deterministic fixtures like `plane` and `radial_hill`.
- New generator module, e.g. `src/terrain/generation.rs` or `src/generation.rs`: procedural generation logic, seed handling, interpolation/noise helpers, and generator presets.
- Generator module (landed as `src/terrain_gen.rs`): procedural generation logic, seed handling, interpolation/noise helpers, and generator presets.
- Public API should return `HeightGrid` and nothing renderer-specific.
- Any generator metadata should live in a separate spec/config type, not in the grid itself.
@@ -53,6 +54,8 @@ If the implementation later needs richer provenance, add a lightweight wrapper b
### Slice 1: generator module skeleton
Status: landed in `src/terrain_gen.rs`.
Goal: introduce the procedural terrain namespace without changing renderer behavior.
Deliverables:
@@ -68,6 +71,8 @@ Acceptance:
### Slice 2: deterministic value-noise core
Status: landed in `src/terrain_gen.rs`.
Goal: implement the smallest reusable noise primitive.
Deliverables:
@@ -84,6 +89,8 @@ Acceptance:
### Slice 3: fBm terrain composition
Status: landed in `src/terrain_gen.rs`.
Goal: layer octaves into usable synthetic landscapes.
Deliverables:
@@ -99,6 +106,8 @@ Acceptance:
### Slice 4: preset profiles
Status: not started — the next slice.
Goal: provide a few named generator presets for common shapes.
Suggested first presets:
@@ -149,12 +158,16 @@ Run these for each generator slice:
```bash
cargo fmt --check
cargo test terrain
cargo test terrain_gen -- --nocapture
cargo test
cargo clippy --all-targets -- -D warnings
```
Add one feature-specific smoke command for the slice once the generator has a public entry point, for example a tiny render or sample-generation command that writes to `/tmp` and proves the generated grid is usable end-to-end.
`cargo test terrain_gen -- --nocapture` is the feature-specific smoke command for
the landed generator slice: it exercises `DeterministicTerrainGenerator` against
the `TerrainGenerationSpec` surface in `src/terrain_gen.rs` and proves a seeded
grid is reproducible. Future slices should add their own targeted smoke command
once they expose a new public entry point.
## Definition of done for the workstream
+65
View File
@@ -0,0 +1,65 @@
//! Docs-sync guard for the terrain-generation surface.
//!
//! The terrain generator has landed in `src/terrain_gen.rs` (see
//! `tests/terrain_gen.rs`), but the docs have not been updated to describe it.
//! This test fails by design until README, the terrain-generation plan, and
//! the architecture notes mention the new surface.
use std::fs;
use std::path::Path;
/// Substrings every terrain-aware doc should mention now that the
/// terrain-generation module has landed.
const REQUIRED_TERMS: &[&str] = &[
"src/terrain_gen.rs",
"TerrainGenerationSpec",
"DeterministicTerrainGenerator",
"cargo test terrain_gen",
];
fn read_doc(relative: &str) -> String {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(relative);
fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()))
}
/// Returns the required terrain-generation terms that `doc` fails to mention.
fn missing_terms(doc: &str) -> Vec<&'static str> {
let mut missing: Vec<&'static str> = REQUIRED_TERMS
.iter()
.copied()
.filter(|term| !doc.contains(term))
.collect();
// A determinism/seed note: the docs should say generation is seeded and
// reproducible, not just reuse the word "deterministic" elsewhere.
let lower = doc.to_lowercase();
if !(lower.contains("seed") && lower.contains("determin")) {
missing.push("a determinism/seed note");
}
missing
}
fn assert_doc_mentions_terrain_gen(relative: &str) {
let doc = read_doc(relative);
let missing = missing_terms(&doc);
assert!(
missing.is_empty(),
"{relative} does not mention the landed terrain-generation surface: {missing:?}"
);
}
#[test]
fn readme_mentions_terrain_generation_surface() {
assert_doc_mentions_terrain_gen("README.md");
}
#[test]
fn terrain_generation_plan_mentions_terrain_generation_surface() {
assert_doc_mentions_terrain_gen("docs/plans/terrain-generation.md");
}
#[test]
fn architecture_notes_mention_terrain_generation_surface() {
assert_doc_mentions_terrain_gen("docs/knowledgebase/architecture-notes.md");
}