The 30-file tree moves under atlas as its third rung (D-243), ten verbs fronting it. Each verb restates its module's options so `--help` describes something; tooling/test_planet_router.py hands every declared option to the module's own argparse and fails on drift, and now runs in make test-tooling. The 2026-09-02 half of this move had converted the top-level imports and the repo roots. Finishing it found what the half-move left: - Lazy in-function imports, and all of sol_data/, still named siblings bare. They resolved only through sys.path.insert hacks, so under reach the first globe render in generate, batch or sol-import would have raised ModuleNotFoundError. Qualified; the hacks are gone. - 247 print() calls and a stdout progress writer that fired once per 8 KB block. Report verbs (audit, quality) write through console.out, progress through console.event, and download progress is throttled to 10% steps so a job log is not tens of thousands of lines. - Every error exit raises ReachError with a fix. Two checks that could not fail: - batch --verify-determinism printed a warning and exited 0 on a mismatch. - import-provinces exited 0 with errors > 0. Both now raise. The 271-body bake is only safe to re-run because the first one holds. sol-import --body is action="append" in the module but the router took one value, so --body GJ0d --body GJ0e kept one. Now repeatable, and _flags repeats list options. test_conformance walked one level, so a nested group was reported as a verb missing @command and its ten verbs were never checked. It recurses now; proven by stripping @command from `planet quality` and watching it fail. Stray PNGs from the 2026-09-03 runaway router-test run are parked in .cache/t1288-stray-pngs/, not committed. Their reliefmaps differ from HEAD while the heightmap regenerated byte-identical — filed as T-1291. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
4.9 KiB
Python
117 lines
4.9 KiB
Python
"""
|
|
Mercury (GJ0b) terrain builder.
|
|
|
|
Data source:
|
|
- Elevation: MESSENGER DEM from USGS Astrogeology
|
|
665m/px global DEM, GeoTIFF.
|
|
|
|
Mercury properties:
|
|
- Min elevation: ~-5380 m
|
|
- Max elevation: ~4480 m
|
|
- No atmosphere, no water
|
|
- Extreme temperature range: ~100K (night) to ~700K (day)
|
|
- body_type: "planet", planet_class: "barren", atmosphere: "none"
|
|
"""
|
|
|
|
import numpy as np
|
|
|
|
from tooling.domains.atlas.planet.sol_data.download import ensure_cached
|
|
from tooling.domains.atlas.planet.sol_data.shared import (
|
|
GRID_W, GRID_H,
|
|
load_tiff_as_array, load_raw_binary,
|
|
resample_to_grid, normalize_01,
|
|
compute_hillshade, assemble_terrain,
|
|
temperature_grid_analytical,
|
|
)
|
|
from tooling.core import console
|
|
|
|
# MESSENGER DEM — try PDS binary first (compact), fall back to USGS GeoTIFF
|
|
MESSENGER_PDS_URL = "https://pds-geosciences.wustl.edu/messenger/mess-h-mdis_mla-6-dem-elevation-v1/messdmdem_1001/data/global_dem_16ppd.img"
|
|
MESSENGER_PDS_FILE = "messenger_dem_16ppd.img"
|
|
MESSENGER_PDS_W = 5760
|
|
MESSENGER_PDS_H = 2880
|
|
|
|
# USGS GeoTIFF fallback (~506 MB, but PIL-loadable)
|
|
MESSENGER_TIFF_URL = "https://planetarymaps.usgs.gov/mosaic/Mercury_Messenger_USGS_DEM_Global_665m_v2.tif"
|
|
MESSENGER_TIFF_FILE = "Mercury_Messenger_USGS_DEM_Global_665m_v2.tif"
|
|
|
|
MERCURY_MIN_ELEV_M = -5380.0
|
|
MERCURY_MAX_ELEV_M = 4480.0
|
|
MERCURY_EQUATORIAL_TEMP_K = 440.0 # mean dayside
|
|
MERCURY_POLAR_TEMP_K = 200.0
|
|
|
|
|
|
def _load_messenger() -> np.ndarray:
|
|
"""Load MESSENGER DEM, return elevation in metres."""
|
|
# Try PDS binary first (compact ~33 MB)
|
|
try:
|
|
path = ensure_cached(MESSENGER_PDS_URL, MESSENGER_PDS_FILE)
|
|
console.event(f" loading MESSENGER PDS: {path}")
|
|
arr = load_raw_binary(str(path), MESSENGER_PDS_W, MESSENGER_PDS_H,
|
|
dtype=">i2", offset=0)
|
|
arr[arr > 20000] = 0.0
|
|
arr[arr < -20000] = 0.0
|
|
console.event(f" MESSENGER range: [{arr.min():.0f}, {arr.max():.0f}] m")
|
|
return arr
|
|
except Exception as e:
|
|
console.event(f" PDS load failed ({e}), trying USGS GeoTIFF...")
|
|
|
|
# Fallback: USGS GeoTIFF (~506 MB)
|
|
try:
|
|
path = ensure_cached(MESSENGER_TIFF_URL, MESSENGER_TIFF_FILE)
|
|
console.event(f" loading MESSENGER GeoTIFF: {path}")
|
|
arr = load_tiff_as_array(str(path))
|
|
arr[arr < -20000] = 0.0
|
|
console.event(f" MESSENGER shape: {arr.shape}, range: [{arr.min():.0f}, {arr.max():.0f}] m")
|
|
return arr
|
|
except Exception as e2:
|
|
console.event(f" GeoTIFF also failed ({e2}), using procedural")
|
|
return None
|
|
|
|
|
|
def build_terrain(body_def: dict) -> dict:
|
|
"""Build Mercury terrain dict from MESSENGER data."""
|
|
from tooling.domains.atlas.planet.planet_simulation import compute_biome
|
|
|
|
console.event(" Mercury: loading MESSENGER data...")
|
|
|
|
# ── 1. Elevation ────────────────────────────────────────────────────
|
|
raw = _load_messenger()
|
|
if raw is None:
|
|
from tooling.domains.atlas.planet.planet_simulation import simulate
|
|
return simulate(body_def)
|
|
|
|
from tooling.domains.atlas.planet.sol_data.shared import greenwich_to_dateline
|
|
shifted = greenwich_to_dateline(raw)
|
|
elevation_m = resample_to_grid(shifted, GRID_H, GRID_W, order=1)
|
|
elevation = normalize_01(elevation_m, MERCURY_MIN_ELEV_M, MERCURY_MAX_ELEV_M)
|
|
|
|
sea_level = 0.0
|
|
surface_water = np.zeros((GRID_H, GRID_W), dtype=bool)
|
|
|
|
# ── 2. Temperature ──────────────────────────────────────────────────
|
|
temperature_K = temperature_grid_analytical(
|
|
base_T_K=MERCURY_EQUATORIAL_TEMP_K,
|
|
elevation=elevation,
|
|
lapse_rate_K_per_unit=20.0,
|
|
lat_gradient_K=240.0,
|
|
)
|
|
temperature_K = np.maximum(temperature_K, 100.0)
|
|
|
|
# ── 3. Moisture ─────────────────────────────────────────────────────
|
|
moisture = np.zeros((GRID_H, GRID_W), dtype=np.float32)
|
|
|
|
# ── 4. Biome ────────────────────────────────────────────────────────
|
|
biome = compute_biome(body_def, elevation, sea_level, surface_water,
|
|
temperature_K, moisture)
|
|
|
|
# ── 5. Hillshade ────────────────────────────────────────────────────
|
|
hillshade = compute_hillshade(elevation)
|
|
|
|
return assemble_terrain(
|
|
elevation=elevation, temperature_K=temperature_K,
|
|
moisture=moisture, biome=biome,
|
|
surface_water=surface_water, hillshade=hillshade,
|
|
rivers=[], sea_level=sea_level,
|
|
)
|