Verify Ground Zero neighbor edges

This commit is contained in:
2026-05-14 06:09:10 -07:00
parent 2344a11519
commit 7a05e324a3
4 changed files with 552 additions and 2 deletions
+2 -2
View File
@@ -423,7 +423,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add generated tile metadata record for the MVP tile.
- [x] Verify terrain scale is 1 km x 1 km in Unreal.
- [x] Verify terrain tile origin and centered Unreal bounds for the Ground Zero test map.
- [ ] Verify neighboring tile edge coordinates against the registry before multi-tile stitching.
- [x] Verify neighboring tile edge coordinates against the registry before multi-tile stitching.
- [ ] Add foliage pass.
- [~] Add resource nodes.
- [ ] Add biome-appropriate natural resources based on Ground Zero.
@@ -1402,4 +1402,4 @@ Next version .01 priorities:
Immediate next item:
- [ ] Verify neighboring tile edge coordinates against the registry before multi-tile stitching.
- [ ] Add foliage pass.
@@ -0,0 +1,289 @@
{
"schema_version": 1,
"tile_id": "gz_us_ca_pacifica_utm10n_e544_n4160",
"registry_path": "Data/Tiles/ground_zero_tiles.json",
"grid_scheme": "prototype_utm_1km",
"verified_tile_count": 9,
"expected_neighbor_count": 8,
"found_neighbor_count": 8,
"all_passed": true,
"missing_neighbors": [],
"tile_shape_checks": [
{
"tile_id": "gz_us_ca_pacifica_utm10n_e544_n4160",
"passed": true,
"errors": []
},
{
"tile_id": "gz_us_ca_pacifica_utm10n_e543_n4159",
"passed": true,
"errors": []
},
{
"tile_id": "gz_us_ca_pacifica_utm10n_e544_n4159",
"passed": true,
"errors": []
},
{
"tile_id": "gz_us_ca_pacifica_utm10n_e545_n4159",
"passed": true,
"errors": []
},
{
"tile_id": "gz_us_ca_pacifica_utm10n_e543_n4160",
"passed": true,
"errors": []
},
{
"tile_id": "gz_us_ca_pacifica_utm10n_e545_n4160",
"passed": true,
"errors": []
},
{
"tile_id": "gz_us_ca_pacifica_utm10n_e543_n4161",
"passed": true,
"errors": []
},
{
"tile_id": "gz_us_ca_pacifica_utm10n_e544_n4161",
"passed": true,
"errors": []
},
{
"tile_id": "gz_us_ca_pacifica_utm10n_e545_n4161",
"passed": true,
"errors": []
}
],
"neighbor_edge_checks": [
{
"direction": "n",
"neighbor_tile_id": "gz_us_ca_pacifica_utm10n_e544_n4161",
"expected_bounds_utm_m": {
"easting_min_m": 544000,
"northing_min_m": 4161000,
"easting_max_m": 545000,
"northing_max_m": 4162000
},
"actual_bounds_utm_m": {
"easting_min_m": 544000,
"northing_min_m": 4161000,
"easting_max_m": 545000,
"northing_max_m": 4162000
},
"shared_edge": {
"center_edge": "north",
"neighbor_edge": "south",
"shared_northing_m": 4161000,
"easting_span_m": [
544000,
545000
]
},
"passed": true,
"errors": []
},
{
"direction": "ne",
"neighbor_tile_id": "gz_us_ca_pacifica_utm10n_e545_n4161",
"expected_bounds_utm_m": {
"easting_min_m": 545000,
"northing_min_m": 4161000,
"easting_max_m": 546000,
"northing_max_m": 4162000
},
"actual_bounds_utm_m": {
"easting_min_m": 545000,
"northing_min_m": 4161000,
"easting_max_m": 546000,
"northing_max_m": 4162000
},
"shared_edge": {
"center_corner_touch": "ne",
"corner_touch_only": true,
"expected_bounds": {
"easting_min_m": 545000,
"northing_min_m": 4161000,
"easting_max_m": 546000,
"northing_max_m": 4162000
}
},
"passed": true,
"errors": []
},
{
"direction": "e",
"neighbor_tile_id": "gz_us_ca_pacifica_utm10n_e545_n4160",
"expected_bounds_utm_m": {
"easting_min_m": 545000,
"northing_min_m": 4160000,
"easting_max_m": 546000,
"northing_max_m": 4161000
},
"actual_bounds_utm_m": {
"easting_min_m": 545000,
"northing_min_m": 4160000,
"easting_max_m": 546000,
"northing_max_m": 4161000
},
"shared_edge": {
"center_edge": "east",
"neighbor_edge": "west",
"shared_easting_m": 545000,
"northing_span_m": [
4160000,
4161000
]
},
"passed": true,
"errors": []
},
{
"direction": "se",
"neighbor_tile_id": "gz_us_ca_pacifica_utm10n_e545_n4159",
"expected_bounds_utm_m": {
"easting_min_m": 545000,
"northing_min_m": 4159000,
"easting_max_m": 546000,
"northing_max_m": 4160000
},
"actual_bounds_utm_m": {
"easting_min_m": 545000,
"northing_min_m": 4159000,
"easting_max_m": 546000,
"northing_max_m": 4160000
},
"shared_edge": {
"center_corner_touch": "se",
"corner_touch_only": true,
"expected_bounds": {
"easting_min_m": 545000,
"northing_min_m": 4159000,
"easting_max_m": 546000,
"northing_max_m": 4160000
}
},
"passed": true,
"errors": []
},
{
"direction": "s",
"neighbor_tile_id": "gz_us_ca_pacifica_utm10n_e544_n4159",
"expected_bounds_utm_m": {
"easting_min_m": 544000,
"northing_min_m": 4159000,
"easting_max_m": 545000,
"northing_max_m": 4160000
},
"actual_bounds_utm_m": {
"easting_min_m": 544000,
"northing_min_m": 4159000,
"easting_max_m": 545000,
"northing_max_m": 4160000
},
"shared_edge": {
"center_edge": "south",
"neighbor_edge": "north",
"shared_northing_m": 4160000,
"easting_span_m": [
544000,
545000
]
},
"passed": true,
"errors": []
},
{
"direction": "sw",
"neighbor_tile_id": "gz_us_ca_pacifica_utm10n_e543_n4159",
"expected_bounds_utm_m": {
"easting_min_m": 543000,
"northing_min_m": 4159000,
"easting_max_m": 544000,
"northing_max_m": 4160000
},
"actual_bounds_utm_m": {
"easting_min_m": 543000,
"northing_min_m": 4159000,
"easting_max_m": 544000,
"northing_max_m": 4160000
},
"shared_edge": {
"center_corner_touch": "sw",
"corner_touch_only": true,
"expected_bounds": {
"easting_min_m": 543000,
"northing_min_m": 4159000,
"easting_max_m": 544000,
"northing_max_m": 4160000
}
},
"passed": true,
"errors": []
},
{
"direction": "w",
"neighbor_tile_id": "gz_us_ca_pacifica_utm10n_e543_n4160",
"expected_bounds_utm_m": {
"easting_min_m": 543000,
"northing_min_m": 4160000,
"easting_max_m": 544000,
"northing_max_m": 4161000
},
"actual_bounds_utm_m": {
"easting_min_m": 543000,
"northing_min_m": 4160000,
"easting_max_m": 544000,
"northing_max_m": 4161000
},
"shared_edge": {
"center_edge": "west",
"neighbor_edge": "east",
"shared_easting_m": 544000,
"northing_span_m": [
4160000,
4161000
]
},
"passed": true,
"errors": []
},
{
"direction": "nw",
"neighbor_tile_id": "gz_us_ca_pacifica_utm10n_e543_n4161",
"expected_bounds_utm_m": {
"easting_min_m": 543000,
"northing_min_m": 4161000,
"easting_max_m": 544000,
"northing_max_m": 4162000
},
"actual_bounds_utm_m": {
"easting_min_m": 543000,
"northing_min_m": 4161000,
"easting_max_m": 544000,
"northing_max_m": 4162000
},
"shared_edge": {
"center_corner_touch": "nw",
"corner_touch_only": true,
"expected_bounds": {
"easting_min_m": 543000,
"northing_min_m": 4161000,
"easting_max_m": 544000,
"northing_max_m": 4162000
}
},
"passed": true,
"errors": []
}
],
"stitching_decision": {
"ready_for_coordinate_based_neighbor_stitching": true,
"notes": [
"All Ground Zero neighbor placeholders align on exact 1000m UTM boundaries.",
"Cardinal neighbors share full 1km edges with no gap or overlap.",
"Diagonal neighbors touch at corners only.",
"This verifies registry coordinates, not yet elevation seam continuity."
]
}
}
+38
View File
@@ -0,0 +1,38 @@
# Ground Zero Neighbor Edge Verification
The Ground Zero tile and its eight immediate neighbor placeholders were checked
against the tile registry before any multi-tile stitching work.
## Inputs
- Registry: `Data/Tiles/ground_zero_tiles.json`
- Verification script: `Scripts/verify_ground_zero_neighbor_edges.py`
- Output:
`Data/Terrain/Analysis/gz_us_ca_pacifica_utm10n_e544_n4160/gz_us_ca_pacifica_utm10n_e544_n4160_neighbor_edge_verification.json`
## Result
The registry coordinate pass succeeded.
- Ground Zero tile:
`gz_us_ca_pacifica_utm10n_e544_n4160`
- Ground Zero bounds:
`544000,4160000 -> 545000,4161000` in EPSG:26910 / UTM zone 10N.
- Verified tile count: `9`
- Found neighbor count: `8`
- All tiles use `1000 m x 1000 m` bounds.
- All tile IDs match their minimum UTM kilometer coordinates.
- North, south, east, and west neighbors share exact full 1 km edges with the
Ground Zero tile.
- Northeast, southeast, southwest, and northwest neighbors touch Ground Zero at
corners only.
## Stitching Decision
The registry is ready for coordinate-based neighbor stitching around Ground
Zero. This pass verifies tile bounds and adjacency only; it does not verify
elevation seam continuity because neighbor DEM extraction has not been run yet.
The next terrain stitching pass should extract or generate the relevant
neighbor DEMs, compare edge sample elevations, and then choose the seam blending
rule.
@@ -0,0 +1,223 @@
#!/usr/bin/env python3
"""Verify Ground Zero neighbor tile edges before multi-tile stitching."""
from __future__ import annotations
import json
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
REGISTRY_PATH = REPO_ROOT / "Data/Tiles/ground_zero_tiles.json"
OUTPUT_PATH = (
REPO_ROOT
/ "Data/Terrain/Analysis/gz_us_ca_pacifica_utm10n_e544_n4160/"
/ "gz_us_ca_pacifica_utm10n_e544_n4160_neighbor_edge_verification.json"
)
GROUND_ZERO_TILE_ID = "gz_us_ca_pacifica_utm10n_e544_n4160"
TILE_SIZE_M = 1000
DIRECTION_OFFSETS = {
"n": (0, 1),
"ne": (1, 1),
"e": (1, 0),
"se": (1, -1),
"s": (0, -1),
"sw": (-1, -1),
"w": (-1, 0),
"nw": (-1, 1),
}
def load_registry() -> dict:
return json.loads(REGISTRY_PATH.read_text())
def grid(tile: dict) -> dict:
return tile["grid"]
def expected_tile_id(tile_family: str, easting_min_m: int, northing_min_m: int) -> str:
return f"{tile_family}_e{easting_min_m // 1000}_n{northing_min_m // 1000}"
def verify_tile_shape(tile: dict) -> list[str]:
errors: list[str] = []
g = grid(tile)
width = g["easting_max_m"] - g["easting_min_m"]
height = g["northing_max_m"] - g["northing_min_m"]
if width != TILE_SIZE_M:
errors.append(f"width is {width}m, expected {TILE_SIZE_M}m")
if height != TILE_SIZE_M:
errors.append(f"height is {height}m, expected {TILE_SIZE_M}m")
if g["tile_size_m"] != TILE_SIZE_M:
errors.append(f"tile_size_m is {g['tile_size_m']}, expected {TILE_SIZE_M}")
if g["utm_zone"] != "10N":
errors.append(f"utm_zone is {g['utm_zone']}, expected 10N")
if g["projection"] != "WGS84 / UTM zone 10N":
errors.append(f"projection is {g['projection']}, expected WGS84 / UTM zone 10N")
tile_family = "_".join(tile["tile_id"].split("_")[:-2])
expected_id = expected_tile_id(tile_family, g["easting_min_m"], g["northing_min_m"])
if tile["tile_id"] != expected_id:
errors.append(f"tile_id is {tile['tile_id']}, expected {expected_id}")
return errors
def expected_bounds(center_grid: dict, dx: int, dy: int) -> dict:
easting_min = center_grid["easting_min_m"] + dx * TILE_SIZE_M
northing_min = center_grid["northing_min_m"] + dy * TILE_SIZE_M
return {
"easting_min_m": easting_min,
"northing_min_m": northing_min,
"easting_max_m": easting_min + TILE_SIZE_M,
"northing_max_m": northing_min + TILE_SIZE_M,
}
def verify_neighbor_edge(direction: str, center: dict, neighbor: dict) -> dict:
center_grid = grid(center)
neighbor_grid = grid(neighbor)
dx, dy = DIRECTION_OFFSETS[direction]
expected = expected_bounds(center_grid, dx, dy)
errors: list[str] = []
for key, expected_value in expected.items():
actual_value = neighbor_grid[key]
if actual_value != expected_value:
errors.append(f"{key} is {actual_value}, expected {expected_value}")
if direction == "n":
shared_edge = {
"center_edge": "north",
"neighbor_edge": "south",
"shared_northing_m": center_grid["northing_max_m"],
"easting_span_m": [center_grid["easting_min_m"], center_grid["easting_max_m"]],
}
if neighbor_grid["northing_min_m"] != center_grid["northing_max_m"]:
errors.append("north neighbor south edge does not meet Ground Zero north edge")
elif direction == "s":
shared_edge = {
"center_edge": "south",
"neighbor_edge": "north",
"shared_northing_m": center_grid["northing_min_m"],
"easting_span_m": [center_grid["easting_min_m"], center_grid["easting_max_m"]],
}
if neighbor_grid["northing_max_m"] != center_grid["northing_min_m"]:
errors.append("south neighbor north edge does not meet Ground Zero south edge")
elif direction == "e":
shared_edge = {
"center_edge": "east",
"neighbor_edge": "west",
"shared_easting_m": center_grid["easting_max_m"],
"northing_span_m": [center_grid["northing_min_m"], center_grid["northing_max_m"]],
}
if neighbor_grid["easting_min_m"] != center_grid["easting_max_m"]:
errors.append("east neighbor west edge does not meet Ground Zero east edge")
elif direction == "w":
shared_edge = {
"center_edge": "west",
"neighbor_edge": "east",
"shared_easting_m": center_grid["easting_min_m"],
"northing_span_m": [center_grid["northing_min_m"], center_grid["northing_max_m"]],
}
if neighbor_grid["easting_max_m"] != center_grid["easting_min_m"]:
errors.append("west neighbor east edge does not meet Ground Zero west edge")
else:
shared_edge = {
"center_corner_touch": direction,
"corner_touch_only": True,
"expected_bounds": expected,
}
return {
"direction": direction,
"neighbor_tile_id": neighbor["tile_id"],
"expected_bounds_utm_m": expected,
"actual_bounds_utm_m": {
"easting_min_m": neighbor_grid["easting_min_m"],
"northing_min_m": neighbor_grid["northing_min_m"],
"easting_max_m": neighbor_grid["easting_max_m"],
"northing_max_m": neighbor_grid["northing_max_m"],
},
"shared_edge": shared_edge,
"passed": not errors,
"errors": errors,
}
def main() -> None:
registry = load_registry()
tiles_by_id = {tile["tile_id"]: tile for tile in registry["tiles"]}
center = tiles_by_id[GROUND_ZERO_TILE_ID]
tile_shape_checks = []
for tile in registry["tiles"]:
errors = verify_tile_shape(tile)
tile_shape_checks.append(
{
"tile_id": tile["tile_id"],
"passed": not errors,
"errors": errors,
}
)
neighbor_checks = []
missing_neighbors = []
for direction in ["n", "ne", "e", "se", "s", "sw", "w", "nw"]:
neighbor_id = center["neighbors"].get(direction)
if not neighbor_id or neighbor_id not in tiles_by_id:
missing_neighbors.append({"direction": direction, "neighbor_tile_id": neighbor_id})
continue
neighbor_checks.append(verify_neighbor_edge(direction, center, tiles_by_id[neighbor_id]))
passed = (
not missing_neighbors
and all(check["passed"] for check in neighbor_checks)
and all(check["passed"] for check in tile_shape_checks)
)
result = {
"schema_version": 1,
"tile_id": GROUND_ZERO_TILE_ID,
"registry_path": str(REGISTRY_PATH.relative_to(REPO_ROOT)),
"grid_scheme": registry["grid_scheme"],
"verified_tile_count": len(registry["tiles"]),
"expected_neighbor_count": 8,
"found_neighbor_count": len(neighbor_checks),
"all_passed": passed,
"missing_neighbors": missing_neighbors,
"tile_shape_checks": tile_shape_checks,
"neighbor_edge_checks": neighbor_checks,
"stitching_decision": {
"ready_for_coordinate_based_neighbor_stitching": passed,
"notes": [
"All Ground Zero neighbor placeholders align on exact 1000m UTM boundaries.",
"Cardinal neighbors share full 1km edges with no gap or overlap.",
"Diagonal neighbors touch at corners only.",
"This verifies registry coordinates, not yet elevation seam continuity.",
],
},
}
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_PATH.write_text(json.dumps(result, indent=2) + "\n")
print(f"Wrote {OUTPUT_PATH.relative_to(REPO_ROOT)}")
print(
json.dumps(
{
"all_passed": result["all_passed"],
"verified_tile_count": result["verified_tile_count"],
"found_neighbor_count": result["found_neighbor_count"],
"missing_neighbors": result["missing_neighbors"],
},
indent=2,
)
)
if __name__ == "__main__":
main()