From fcb3d18b0ca5c8b3c6b0d47f6c1334ac6fe6c325 Mon Sep 17 00:00:00 2001 From: nathan Date: Thu, 14 May 2026 05:53:53 -0700 Subject: [PATCH] Analyze Ground Zero shoreline requirements --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 4 +- ...n_e544_n4160_water_shoreline_analysis.json | 74 +++++++++++ Docs/Terrain/GroundZeroWaterShoreline.md | 50 ++++++++ Scripts/analyze_ground_zero_water.py | 116 ++++++++++++++++++ 4 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 Data/Terrain/Analysis/gz_us_ca_pacifica_utm10n_e544_n4160/gz_us_ca_pacifica_utm10n_e544_n4160_water_shoreline_analysis.json create mode 100644 Docs/Terrain/GroundZeroWaterShoreline.md create mode 100644 Scripts/analyze_ground_zero_water.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 6b404ff..d8b43fd 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -417,7 +417,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Choose MVP biome based on Ground Zero location. - [~] Create playable test map. - [x] Add terrain base from real elevation data. -- [ ] Add first-pass water depth/shoreline handling if applicable. +- [x] Add first-pass water depth/shoreline handling if applicable. - [ ] Add first-pass hill, mountain, river, stream, lake, and coastline handling if present in Ground Zero. - [x] Add source metadata record for the MVP tile. - [x] Add generated tile metadata record for the MVP tile. @@ -1402,4 +1402,4 @@ Next version .01 priorities: Immediate next item: -- [ ] Add first-pass water depth/shoreline handling if applicable. +- [ ] Add first-pass hill, mountain, river, stream, lake, and coastline handling if present in Ground Zero. diff --git a/Data/Terrain/Analysis/gz_us_ca_pacifica_utm10n_e544_n4160/gz_us_ca_pacifica_utm10n_e544_n4160_water_shoreline_analysis.json b/Data/Terrain/Analysis/gz_us_ca_pacifica_utm10n_e544_n4160/gz_us_ca_pacifica_utm10n_e544_n4160_water_shoreline_analysis.json new file mode 100644 index 0000000..6239748 --- /dev/null +++ b/Data/Terrain/Analysis/gz_us_ca_pacifica_utm10n_e544_n4160/gz_us_ca_pacifica_utm10n_e544_n4160_water_shoreline_analysis.json @@ -0,0 +1,74 @@ +{ + "schema_version": 1, + "tile_id": "gz_us_ca_pacifica_utm10n_e544_n4160", + "generated_at_utc": "2026-05-14T12:52:25Z", + "source_dem": "Data/Terrain/Extracted/gz_us_ca_pacifica_utm10n_e544_n4160/gz_us_ca_pacifica_utm10n_e544_n4160_1m_dem_subset.tif", + "thresholds": { + "sea_level_m": 0.0, + "shoreline_buffer_m": 2.0, + "low_coastal_buffer_m": 5.0 + }, + "elevation_summary": { + "sample_count": 1000000, + "min_elevation_m": 3.1603012084960938, + "max_elevation_m": 96.50570678710938, + "mean_elevation_m": 18.240529416484833, + "sea_level_or_below_samples": 0, + "near_sea_level_samples": 0, + "low_coastal_samples": 57812, + "sea_level_or_below_percent": 0.0, + "near_sea_level_percent": 0.0, + "low_coastal_percent": 5.7812 + }, + "edge_summary": { + "north": { + "sample_count": 10000, + "min_elevation_m": 3.1603012084960938, + "max_elevation_m": 37.224822998046875, + "mean_elevation_m": 19.415938945102692, + "sea_level_or_below_samples": 0, + "near_sea_level_samples": 0, + "low_coastal_samples": 2710 + }, + "south": { + "sample_count": 10000, + "min_elevation_m": 11.210755348205566, + "max_elevation_m": 57.378944396972656, + "mean_elevation_m": 19.908063950920106, + "sea_level_or_below_samples": 0, + "near_sea_level_samples": 0, + "low_coastal_samples": 0 + }, + "west": { + "sample_count": 10000, + "min_elevation_m": 3.5281026363372803, + "max_elevation_m": 59.69044876098633, + "mean_elevation_m": 15.149734993314743, + "sea_level_or_below_samples": 0, + "near_sea_level_samples": 0, + "low_coastal_samples": 2892 + }, + "east": { + "sample_count": 10000, + "min_elevation_m": 24.61846351623535, + "max_elevation_m": 96.50570678710938, + "mean_elevation_m": 59.40558109054565, + "sea_level_or_below_samples": 0, + "near_sea_level_samples": 0, + "low_coastal_samples": 0 + } + }, + "classification": { + "contains_open_water": false, + "contains_shoreline": false, + "water_depth_required_for_mvp_tile": false, + "shoreline_mesh_required_for_mvp_tile": false, + "freshwater_source_required_for_gameplay": true, + "recommended_first_pass": "No ocean/shoreline actor required inside the current Ground Zero tile. Add a small freshwater source for gameplay separately, and defer ocean/bathymetry work to west/southwest neighbor coastal tiles." + }, + "notes": [ + "This analysis uses the extracted 1-meter USGS DEM for the selected 1 km tile.", + "The selected Ground Zero tile is coastal-influenced but the DEM minimum is above sea level.", + "Ocean depth/bathymetry remains required for adjacent coastal/ocean tiles before multi-tile expansion." + ] +} diff --git a/Docs/Terrain/GroundZeroWaterShoreline.md b/Docs/Terrain/GroundZeroWaterShoreline.md new file mode 100644 index 0000000..58dae63 --- /dev/null +++ b/Docs/Terrain/GroundZeroWaterShoreline.md @@ -0,0 +1,50 @@ +# Ground Zero Water And Shoreline Pass + +This pass determines whether the current 1 km Ground Zero MVP tile needs ocean, +bathymetry, or shoreline handling inside the playable demo map. + +## Result + +The current Ground Zero tile does not contain ocean water or a shoreline inside +its 1 km bounds. First-pass ocean depth and shoreline mesh work is therefore not +required for this tile. + +The analysis found no sea-level or near-sea-level samples: + +- Minimum DEM elevation: approximately `3.16 m` +- Sea-level or below samples: `0` +- Near-sea-level samples at `<= 2.0 m`: `0` +- Low coastal samples at `<= 5.0 m`: `57,812` out of `1,000,000` + +The tile is still coastal-influenced, and water remains important for gameplay. +For the MVP map, add a small freshwater source separately from ocean handling. +Ocean, bathymetry, coastline, and surf/shoreline systems should be handled when +the west and southwest neighboring coastal tiles are brought into scope. + +## Source + +- Tile: `gz_us_ca_pacifica_utm10n_e544_n4160` +- DEM: + `Data/Terrain/Extracted/gz_us_ca_pacifica_utm10n_e544_n4160/gz_us_ca_pacifica_utm10n_e544_n4160_1m_dem_subset.tif` +- Analysis output: + `Data/Terrain/Analysis/gz_us_ca_pacifica_utm10n_e544_n4160/gz_us_ca_pacifica_utm10n_e544_n4160_water_shoreline_analysis.json` + +## Thresholds + +- Sea level: `0.0 m` +- Shoreline buffer: `<= 2.0 m` +- Low coastal buffer: `<= 5.0 m` + +## Implementation Decision + +- Do not place an ocean plane in the current Ground Zero tile. +- Do not fake bathymetry in the current tile. +- Keep NOAA/NCEI bathymetry in the source plan for coastal/ocean neighbor tiles. +- Add gameplay water as a freshwater source item or small placed water actor in + a later MVP map pass. + +## Follow-Up + +The next terrain/map pass should classify hills, slopes, streams, coastline +absence, and other visible landform features from the DEM so resource placement, +movement modifiers, and future World Partition stitching can use the same data. diff --git a/Scripts/analyze_ground_zero_water.py b/Scripts/analyze_ground_zero_water.py new file mode 100644 index 0000000..dc58281 --- /dev/null +++ b/Scripts/analyze_ground_zero_water.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Analyze first-pass water and shoreline applicability for the Ground Zero tile.""" + +from __future__ import annotations + +import json +from datetime import datetime, timezone +from pathlib import Path + +import numpy as np +from osgeo import gdal + +gdal.UseExceptions() + + +PROJECT_ROOT = Path(__file__).resolve().parents[1] +TILE_ID = "gz_us_ca_pacifica_utm10n_e544_n4160" +DEM_PATH = PROJECT_ROOT / "Data" / "Terrain" / "Extracted" / TILE_ID / f"{TILE_ID}_1m_dem_subset.tif" +OUTPUT_DIR = PROJECT_ROOT / "Data" / "Terrain" / "Analysis" / TILE_ID +OUTPUT_PATH = OUTPUT_DIR / f"{TILE_ID}_water_shoreline_analysis.json" + +SEA_LEVEL_M = 0.0 +SHORELINE_BUFFER_M = 2.0 +LOW_COASTAL_BUFFER_M = 5.0 + + +def summarize_edge(values: np.ndarray) -> dict[str, float | int]: + finite = values[np.isfinite(values)] + return { + "sample_count": int(finite.size), + "min_elevation_m": float(np.min(finite)) if finite.size else None, + "max_elevation_m": float(np.max(finite)) if finite.size else None, + "mean_elevation_m": float(np.mean(finite)) if finite.size else None, + "sea_level_or_below_samples": int(np.count_nonzero(finite <= SEA_LEVEL_M)), + "near_sea_level_samples": int(np.count_nonzero(finite <= SHORELINE_BUFFER_M)), + "low_coastal_samples": int(np.count_nonzero(finite <= LOW_COASTAL_BUFFER_M)), + } + + +def main() -> None: + dataset = gdal.Open(str(DEM_PATH), gdal.GA_ReadOnly) + if dataset is None: + raise RuntimeError(f"Could not open DEM: {DEM_PATH}") + + band = dataset.GetRasterBand(1) + data = band.ReadAsArray().astype(float) + nodata = band.GetNoDataValue() + if nodata is not None: + data[data == nodata] = np.nan + + finite = data[np.isfinite(data)] + if finite.size == 0: + raise RuntimeError(f"DEM has no finite elevation samples: {DEM_PATH}") + + total_samples = int(finite.size) + sea_or_below = int(np.count_nonzero(finite <= SEA_LEVEL_M)) + near_sea = int(np.count_nonzero(finite <= SHORELINE_BUFFER_M)) + low_coastal = int(np.count_nonzero(finite <= LOW_COASTAL_BUFFER_M)) + + edge_width = 10 + edges = { + "north": summarize_edge(data[:edge_width, :].reshape(-1)), + "south": summarize_edge(data[-edge_width:, :].reshape(-1)), + "west": summarize_edge(data[:, :edge_width].reshape(-1)), + "east": summarize_edge(data[:, -edge_width:].reshape(-1)), + } + + has_open_water = sea_or_below > 0 + has_shoreline = near_sea > 0 + + result = { + "schema_version": 1, + "tile_id": TILE_ID, + "generated_at_utc": datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"), + "source_dem": str(DEM_PATH.relative_to(PROJECT_ROOT)), + "thresholds": { + "sea_level_m": SEA_LEVEL_M, + "shoreline_buffer_m": SHORELINE_BUFFER_M, + "low_coastal_buffer_m": LOW_COASTAL_BUFFER_M, + }, + "elevation_summary": { + "sample_count": total_samples, + "min_elevation_m": float(np.min(finite)), + "max_elevation_m": float(np.max(finite)), + "mean_elevation_m": float(np.mean(finite)), + "sea_level_or_below_samples": sea_or_below, + "near_sea_level_samples": near_sea, + "low_coastal_samples": low_coastal, + "sea_level_or_below_percent": sea_or_below / total_samples * 100.0, + "near_sea_level_percent": near_sea / total_samples * 100.0, + "low_coastal_percent": low_coastal / total_samples * 100.0, + }, + "edge_summary": edges, + "classification": { + "contains_open_water": has_open_water, + "contains_shoreline": has_shoreline, + "water_depth_required_for_mvp_tile": False, + "shoreline_mesh_required_for_mvp_tile": False, + "freshwater_source_required_for_gameplay": True, + "recommended_first_pass": "No ocean/shoreline actor required inside the current Ground Zero tile. Add a small freshwater source for gameplay separately, and defer ocean/bathymetry work to west/southwest neighbor coastal tiles.", + }, + "notes": [ + "This analysis uses the extracted 1-meter USGS DEM for the selected 1 km tile.", + "The selected Ground Zero tile is coastal-influenced but the DEM minimum is above sea level.", + "Ocean depth/bathymetry remains required for adjacent coastal/ocean tiles before multi-tile expansion.", + ], + } + + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + OUTPUT_PATH.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") + print(f"Wrote {OUTPUT_PATH.relative_to(PROJECT_ROOT)}") + print(json.dumps(result["classification"], indent=2)) + + +if __name__ == "__main__": + main()