From a7292bbae1b44a08c3cfa7c4e070d78d9672246a Mon Sep 17 00:00:00 2001 From: nathan Date: Sat, 16 May 2026 02:09:11 -0700 Subject: [PATCH] Link Ground Zero weather lookup coordinates --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 2 +- Data/Tiles/ground_zero_tiles.json | 9 ++ Data/Tiles/tile_registry.schema.json | 43 ++++++++ Data/Tiles/tile_registry.sql | 13 +++ Data/Tiles/tile_weather_manifest.json | 7 +- Docs/TechnicalDesignDocument.md | 8 ++ Docs/Terrain/TileRegistrySchema.md | 24 +++++ Scripts/generate_tile_weather_manifest.py | 15 ++- ..._ground_zero_weather_lookup_coordinates.py | 101 ++++++++++++++++++ Scripts/verify_weather_provider_adapter.py | 5 +- 10 files changed, 217 insertions(+), 10 deletions(-) create mode 100644 Scripts/verify_ground_zero_weather_lookup_coordinates.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index eaf742f..8694aef 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -442,7 +442,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Choose Ground Zero real-world 1 km x 1 km MVP tile. - [x] Choose MVP biome based on Ground Zero location. -- [ ] Link Ground Zero tile coordinates to real-world weather lookup coordinates. +- [x] Link Ground Zero tile coordinates to real-world weather lookup coordinates. Added explicit Ground Zero `weather_lookup_metadata` with tile-center latitude `37.5925`, longitude `-122.4995`, Open-Meteo primary routing, NOAA/NWS fallback eligibility, manifest generation support, registry/schema documentation, and a verifier so future source-backed tiles use declared lookup metadata instead of one-off hard-coded coordinates. - [~] Create playable test map. - [x] Add terrain base from real elevation data. - [x] Add first-pass water depth/shoreline handling if applicable. diff --git a/Data/Tiles/ground_zero_tiles.json b/Data/Tiles/ground_zero_tiles.json index 3a0b384..640a79e 100644 --- a/Data/Tiles/ground_zero_tiles.json +++ b/Data/Tiles/ground_zero_tiles.json @@ -16,6 +16,15 @@ "center_latitude": 37.5925, "center_longitude": -122.4995 }, + "weather_lookup_metadata": { + "lookup_latitude": 37.5925, + "lookup_longitude": -122.4995, + "coordinate_source": "tile_center", + "primary_provider": "open-meteo", + "fallback_provider": "noaa-nws", + "fallback_provider_eligible": true, + "lookup_rule": "Use the Ground Zero tile center latitude/longitude for live MVP weather and temperature snapshots." + }, "status": "source_data_found", "biome_primary": "coastal_california_scrub_woodland", "biome_secondary": [ diff --git a/Data/Tiles/tile_registry.schema.json b/Data/Tiles/tile_registry.schema.json index 8125228..f1ec7f5 100644 --- a/Data/Tiles/tile_registry.schema.json +++ b/Data/Tiles/tile_registry.schema.json @@ -113,6 +113,9 @@ "growing_season_metadata": { "$ref": "#/$defs/growing_season_metadata" }, + "weather_lookup_metadata": { + "$ref": "#/$defs/weather_lookup_metadata" + }, "notes": { "type": "string" } @@ -241,6 +244,46 @@ } } }, + "weather_lookup_metadata": { + "type": "object", + "required": [ + "lookup_latitude", + "lookup_longitude", + "coordinate_source", + "primary_provider", + "fallback_provider_eligible", + "lookup_rule" + ], + "additionalProperties": false, + "properties": { + "lookup_latitude": { + "type": "number", + "minimum": -90, + "maximum": 90 + }, + "lookup_longitude": { + "type": "number", + "minimum": -180, + "maximum": 180 + }, + "coordinate_source": { + "type": "string", + "enum": ["tile_center", "manual_override", "provider_grid_point"] + }, + "primary_provider": { + "type": "string" + }, + "fallback_provider": { + "type": "string" + }, + "fallback_provider_eligible": { + "type": "boolean" + }, + "lookup_rule": { + "type": "string" + } + } + }, "source": { "type": "object", "required": ["source_kind", "source_name", "coverage_status"], diff --git a/Data/Tiles/tile_registry.sql b/Data/Tiles/tile_registry.sql index 9a6f4ec..f78a1b1 100644 --- a/Data/Tiles/tile_registry.sql +++ b/Data/Tiles/tile_registry.sql @@ -64,6 +64,19 @@ CREATE TABLE IF NOT EXISTS terrain_tile_growing_season_metadata ( FOREIGN KEY (tile_id) REFERENCES terrain_tiles(tile_id) ); +CREATE TABLE IF NOT EXISTS terrain_tile_weather_lookup_metadata ( + tile_id TEXT PRIMARY KEY, + lookup_latitude REAL NOT NULL CHECK (lookup_latitude >= -90 AND lookup_latitude <= 90), + lookup_longitude REAL NOT NULL CHECK (lookup_longitude >= -180 AND lookup_longitude <= 180), + coordinate_source TEXT NOT NULL CHECK (coordinate_source IN ('tile_center', 'manual_override', 'provider_grid_point')), + primary_provider TEXT NOT NULL DEFAULT 'open-meteo', + fallback_provider TEXT NOT NULL DEFAULT '', + fallback_provider_eligible INTEGER NOT NULL DEFAULT 0 CHECK (fallback_provider_eligible IN (0, 1)), + lookup_rule TEXT NOT NULL DEFAULT '', + generated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (tile_id) REFERENCES terrain_tiles(tile_id) +); + CREATE TABLE IF NOT EXISTS terrain_tile_neighbors ( tile_id TEXT NOT NULL, direction TEXT NOT NULL CHECK (direction IN ('n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw')), diff --git a/Data/Tiles/tile_weather_manifest.json b/Data/Tiles/tile_weather_manifest.json index 921e1cb..9b639c2 100644 --- a/Data/Tiles/tile_weather_manifest.json +++ b/Data/Tiles/tile_weather_manifest.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "generated_at_utc": "2026-05-16T05:58:06Z", + "generated_at_utc": "2026-05-16T09:06:51Z", "source_registry": "Data/Tiles/ground_zero_tiles.json", "generation_rule": "Every source-backed/generated tile with center coordinates is emitted; unknown placeholders are skipped.", "tiles": [ @@ -8,8 +8,11 @@ "tile_id": "gz_us_ca_pacifica_utm10n_e544_n4160", "center_latitude": 37.5925, "center_longitude": -122.4995, + "coordinate_source": "tile_center", "provider": "open-meteo", - "lookup_rule": "Use tile center latitude/longitude for live MVP weather and temperature snapshots." + "fallback_provider": "noaa-nws", + "fallback_provider_eligible": true, + "lookup_rule": "Use the Ground Zero tile center latitude/longitude for live MVP weather and temperature snapshots." } ] } diff --git a/Docs/TechnicalDesignDocument.md b/Docs/TechnicalDesignDocument.md index a4124ea..1e6c2d9 100644 --- a/Docs/TechnicalDesignDocument.md +++ b/Docs/TechnicalDesignDocument.md @@ -184,6 +184,14 @@ source-backed, generated, validated, packaged, or published tile with center coordinates, while placeholder/unknown tiles are skipped. Future source-backed tiles therefore become weather-eligible when their registry entries are added. +Ground Zero weather lookup coordinates are explicit in the tile registry. The +current MVP tile `gz_us_ca_pacifica_utm10n_e544_n4160` uses its tile center, +latitude `37.5925` and longitude `-122.4995`, as the canonical real-world +weather lookup point. `Scripts/generate_tile_weather_manifest.py` reads +`weather_lookup_metadata` when present, falls back to the tile center for +source-backed tiles, and emits the provider routing used by the weather +subsystem. + Open-Meteo is the first global MVP weather source. The provider contract is stored in `Data/Weather/open_meteo_mvp_source.json`, including the forecast endpoint, requested current/daily variables, tile lookup rule, and Agrarian diff --git a/Docs/Terrain/TileRegistrySchema.md b/Docs/Terrain/TileRegistrySchema.md index cc5e165..df9a037 100644 --- a/Docs/Terrain/TileRegistrySchema.md +++ b/Docs/Terrain/TileRegistrySchema.md @@ -78,6 +78,10 @@ Optional growing-zone fields follow the same rule. Crop viability, frost-free windows, and growing-season length are generated only for tiles with real source work or published packages and explicit growing-zone data. +Optional weather lookup fields are stored only for active/source-backed tiles. +For MVP, Ground Zero uses its tile-center latitude/longitude as the canonical +real-world lookup coordinate for live weather and temperature. + ### `terrain_tile_neighbors` Tracks adjacency for stitching and prefetching. @@ -136,6 +140,26 @@ Required fields: - `data_basis` - `generated_at` +### `terrain_tile_weather_lookup_metadata` + +Tracks the real-world coordinate and provider routing used to fetch live +weather and temperature snapshots for a source-backed tile. + +Required fields: + +- `tile_id` +- `lookup_latitude` +- `lookup_longitude` +- `coordinate_source` +- `primary_provider` +- `fallback_provider_eligible` +- `lookup_rule` + +Optional fields: + +- `fallback_provider` +- `generated_at` + ### `terrain_tile_packages` Tracks generated downloadable packages. diff --git a/Scripts/generate_tile_weather_manifest.py b/Scripts/generate_tile_weather_manifest.py index f0e09be..f4089f1 100644 --- a/Scripts/generate_tile_weather_manifest.py +++ b/Scripts/generate_tile_weather_manifest.py @@ -28,8 +28,9 @@ def main() -> None: status = tile.get("status") tile_id = tile.get("tile_id") grid = tile.get("grid", {}) - latitude = grid.get("center_latitude") - longitude = grid.get("center_longitude") + weather_lookup = tile.get("weather_lookup_metadata", {}) + latitude = weather_lookup.get("lookup_latitude", grid.get("center_latitude")) + longitude = weather_lookup.get("lookup_longitude", grid.get("center_longitude")) if status not in WEATHER_READY_STATUSES or not tile_id or latitude is None or longitude is None: continue @@ -38,8 +39,14 @@ def main() -> None: "tile_id": tile_id, "center_latitude": latitude, "center_longitude": longitude, - "provider": "open-meteo", - "lookup_rule": "Use tile center latitude/longitude for live MVP weather and temperature snapshots.", + "coordinate_source": weather_lookup.get("coordinate_source", "tile_center"), + "provider": weather_lookup.get("primary_provider", "open-meteo"), + "fallback_provider": weather_lookup.get("fallback_provider", ""), + "fallback_provider_eligible": bool(weather_lookup.get("fallback_provider_eligible", False)), + "lookup_rule": weather_lookup.get( + "lookup_rule", + "Use tile center latitude/longitude for live MVP weather and temperature snapshots.", + ), } ) diff --git a/Scripts/verify_ground_zero_weather_lookup_coordinates.py b/Scripts/verify_ground_zero_weather_lookup_coordinates.py new file mode 100644 index 0000000..583dc64 --- /dev/null +++ b/Scripts/verify_ground_zero_weather_lookup_coordinates.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""Verify Ground Zero weather lookup coordinates are registry-driven.""" + +from __future__ import annotations + +import json +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +REGISTRY = ROOT / "Data" / "Tiles" / "ground_zero_tiles.json" +SCHEMA = ROOT / "Data" / "Tiles" / "tile_registry.schema.json" +MANIFEST = ROOT / "Data" / "Tiles" / "tile_weather_manifest.json" +GENERATOR = ROOT / "Scripts" / "generate_tile_weather_manifest.py" +GAME_STATE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianGameState.h" +TDD = ROOT / "Docs" / "TechnicalDesignDocument.md" +REGISTRY_DOC = ROOT / "Docs" / "Terrain" / "TileRegistrySchema.md" +ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md" + +GROUND_ZERO_TILE_ID = "gz_us_ca_pacifica_utm10n_e544_n4160" +GROUND_ZERO_LATITUDE = 37.5925 +GROUND_ZERO_LONGITUDE = -122.4995 + + +def assert_close(name: str, actual: float, expected: float) -> None: + if abs(actual - expected) > 0.00001: + raise RuntimeError(f"{name} expected {expected}, got {actual}") + + +def main() -> None: + registry = json.loads(REGISTRY.read_text(encoding="utf-8")) + schema_text = SCHEMA.read_text(encoding="utf-8") + manifest = json.loads(MANIFEST.read_text(encoding="utf-8")) + generator_text = GENERATOR.read_text(encoding="utf-8") + game_state_text = GAME_STATE_H.read_text(encoding="utf-8") + + ground_zero = next((tile for tile in registry["tiles"] if tile.get("tile_id") == GROUND_ZERO_TILE_ID), None) + if ground_zero is None: + raise RuntimeError("Ground Zero tile is missing from registry") + + grid = ground_zero["grid"] + lookup = ground_zero.get("weather_lookup_metadata") + if not lookup: + raise RuntimeError("Ground Zero tile is missing weather_lookup_metadata") + + assert_close("grid center latitude", float(grid["center_latitude"]), GROUND_ZERO_LATITUDE) + assert_close("grid center longitude", float(grid["center_longitude"]), GROUND_ZERO_LONGITUDE) + assert_close("weather lookup latitude", float(lookup["lookup_latitude"]), GROUND_ZERO_LATITUDE) + assert_close("weather lookup longitude", float(lookup["lookup_longitude"]), GROUND_ZERO_LONGITUDE) + + if lookup.get("coordinate_source") != "tile_center": + raise RuntimeError("Ground Zero weather coordinate_source should be tile_center") + if lookup.get("primary_provider") != "open-meteo": + raise RuntimeError("Ground Zero primary weather provider should be open-meteo") + if lookup.get("fallback_provider") != "noaa-nws": + raise RuntimeError("Ground Zero fallback weather provider should be noaa-nws") + if lookup.get("fallback_provider_eligible") is not True: + raise RuntimeError("Ground Zero should be NOAA/NWS fallback eligible") + + weather_records = [tile for tile in manifest.get("tiles", []) if tile.get("tile_id") == GROUND_ZERO_TILE_ID] + if len(weather_records) != 1: + raise RuntimeError(f"Expected exactly one Ground Zero weather manifest record, got {len(weather_records)}") + manifest_record = weather_records[0] + assert_close("manifest latitude", float(manifest_record["center_latitude"]), GROUND_ZERO_LATITUDE) + assert_close("manifest longitude", float(manifest_record["center_longitude"]), GROUND_ZERO_LONGITUDE) + if manifest_record.get("coordinate_source") != "tile_center": + raise RuntimeError("Weather manifest should preserve the coordinate source") + if manifest_record.get("provider") != "open-meteo": + raise RuntimeError("Weather manifest should preserve the primary provider") + if manifest_record.get("fallback_provider") != "noaa-nws": + raise RuntimeError("Weather manifest should preserve the fallback provider") + + required_snippets = { + "schema": ["weather_lookup_metadata", "lookup_latitude", "fallback_provider_eligible"], + "generator": ["weather_lookup_metadata", "coordinate_source", "fallback_provider_eligible"], + "game_state": ["ActiveTileLatitude = 37.5925f", "ActiveTileLongitude = -122.4995f"], + "tdd": ["Ground Zero weather lookup coordinates", "37.5925", "-122.4995"], + "registry_doc": ["terrain_tile_weather_lookup_metadata", "lookup_latitude", "primary_provider"], + "roadmap": ["[x] Link Ground Zero tile coordinates to real-world weather lookup coordinates."], + } + checks = { + "schema": schema_text, + "generator": generator_text, + "game_state": game_state_text, + "tdd": TDD.read_text(encoding="utf-8"), + "registry_doc": REGISTRY_DOC.read_text(encoding="utf-8"), + "roadmap": ROADMAP.read_text(encoding="utf-8"), + } + missing = [] + for label, snippets in required_snippets.items(): + for snippet in snippets: + if snippet not in checks[label]: + missing.append(f"{label}: {snippet}") + if missing: + raise RuntimeError("Ground Zero weather lookup verification failed: " + "; ".join(missing)) + + print("Ground Zero weather lookup coordinate verification complete.") + + +if __name__ == "__main__": + main() diff --git a/Scripts/verify_weather_provider_adapter.py b/Scripts/verify_weather_provider_adapter.py index 430451b..3dedcc4 100644 --- a/Scripts/verify_weather_provider_adapter.py +++ b/Scripts/verify_weather_provider_adapter.py @@ -26,14 +26,13 @@ EXPECTED = { CPP: [ "current=temperature_2m,relative_humidity_2m,precipitation", "daily=temperature_2m_max,temperature_2m_min", - "GameState->SetRegionalTemperatureProfile", - "GameState->SetRegionalObservedTemperature", - "GameState->SetWeather", + "GameState->ApplyMappedWeatherInputs", "MapOpenMeteoWeatherCode", ], GENERATOR: [ "WEATHER_READY_STATUSES", "Every source-backed/generated tile", + "weather_lookup_metadata", "tile_weather_manifest.json", ], TDD: [