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>
153 lines
6.4 KiB
Python
153 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
sol_name_fixes.py — Name previously-unnamed Sol body auto-detected features.
|
|
|
|
Targets features with null names: Earth oceans/rivers, Luna/Mars/Europa mountain ranges.
|
|
All names are real-world geographic names for Sol bodies.
|
|
|
|
Usage:
|
|
reach atlas planet sol-name-fixes # apply all fixes
|
|
reach atlas planet sol-name-fixes --dry-run # print planned changes without writing
|
|
"""
|
|
import argparse
|
|
import json
|
|
|
|
from tooling.core import config, console
|
|
|
|
|
|
REPO_ROOT = config.repo_root()
|
|
WIKI = REPO_ROOT / "wiki" / "star-systems" / "GJ-0" / "bodies"
|
|
|
|
# Keys are feature IDs; values are the names to assign.
|
|
FIXES = {
|
|
# Earth (GJ0d): 1 unnamed ocean + 3 unnamed rivers (auto-detected terrain artifacts)
|
|
"GJ0d": {
|
|
"oceans": {
|
|
"water_1": "The World Ocean", # 70% surface = all Earth's oceans unified
|
|
},
|
|
"rivers": {
|
|
"river_1": "Dnieper", # Eastern Europe ~50°N, 17°E
|
|
"river_4": "Tone River", # Japan, Kanto plain ~36°N, 140°E
|
|
"river_10": "Cagayan", # Northern Philippines ~18°N, 121°E
|
|
},
|
|
},
|
|
# Luna (GJ0d-1): 24 unnamed mountain ranges — all auto-detected; named after
|
|
# real lunar mountain systems, massifs, and scarps sorted by descending area.
|
|
"GJ0d-1": {
|
|
"mountain_ranges": {
|
|
"range_5": "Montes Apenninus", # 11221 cells — largest lunar mountain range
|
|
"range_34": "Montes Caucasus", # 3670 cells
|
|
"range_209": "Montes Jura", # 367 cells — on Mare Imbrium NW
|
|
"range_551": "Montes Alpes", # 289 cells — lunar Alps
|
|
"range_576": "Montes Rook", # 258 cells — south polar mountains
|
|
"range_599": "Montes Cordillera", # 164 cells — Orientale basin rim
|
|
"range_326": "Montes Carpatus", # 106 cells — south of Mare Imbrium
|
|
"range_555": "Montes Pyrenaeus", # 94 cells — east of Mare Nectaris
|
|
"range_586": "Montes Taurus", # 90 cells — NE of Mare Serenitatis
|
|
"range_589": "Montes Teneriffe", # 52 cells — isolated massif
|
|
"range_124": "Montes Harbinger", # 51 cells — west of Aristarchus
|
|
"range_587": "Montes Recti", # 48 cells — north Mare Imbrium
|
|
"range_522": "Pico Mons", # 35 cells — isolated peak
|
|
"range_579": "Haemus Montes", # 33 cells — south of Mare Serenitatis
|
|
"range_231": "Gruithuisen Domes", # 25 cells — volcanic domes
|
|
"range_580": "Mons La Hire", # 25 cells — volcanic dome, Mare Imbrium
|
|
"range_570": "Montes Riphaeus", # 24 cells — Oceanus Procellarum ridge
|
|
"range_596": "Montes Secchi", # 24 cells — east of Mare Fecunditatis
|
|
"range_604": "Montes Spitzbergen", # 24 cells — isolated massif
|
|
"range_493": "Malapert Mountain", # 23 cells — south pole region
|
|
"range_406": "Doerfel Mountains", # 26 cells — south pole far side
|
|
"range_432": "Altai Scarp", # 20 cells — Rupes Altai
|
|
"range_79": "Mons Rümker", # 21 cells — shield volcano
|
|
"range_348": "Leibnitz Mountains", # 21 cells — south polar range
|
|
},
|
|
},
|
|
# Mars (GJ0e): 4 unnamed mountain ranges in the anti-Tharsis hemisphere
|
|
"GJ0e": {
|
|
"mountain_ranges": {
|
|
"range_2": "Tyrrhena Mons", # ancient volcanic highland (area=67)
|
|
"range_3": "Hadriaca Mons", # old shield volcano (area=31)
|
|
"range_4": "Amphitrites Montes", # near south polar region (area=28)
|
|
"range_5": "Hellespontus Montes", # cross-refs Hellas Station (area=87)
|
|
},
|
|
},
|
|
# Europa (GJ0f-2): 1 unnamed mountain — Europa's ridged ice terrain (area=123557 cells)
|
|
"GJ0f-2": {
|
|
"mountain_ranges": {
|
|
"range_1": "Conamara Ridges", # cross-refs Conamara Station + Conamara Chaos
|
|
},
|
|
},
|
|
}
|
|
|
|
SECTION_MAP = {
|
|
"oceans": "oceans",
|
|
"rivers": "rivers",
|
|
"mountain_ranges": "mountain_ranges",
|
|
}
|
|
|
|
|
|
def _check_dedup(fixes: dict) -> None:
|
|
"""Raise if any name is assigned to more than one feature ID within a body."""
|
|
for body_id, sections in fixes.items():
|
|
for section, id_map in sections.items():
|
|
seen: dict[str, str] = {}
|
|
for fid, name in id_map.items():
|
|
if name in seen:
|
|
raise ValueError(
|
|
f"Duplicate name {name!r} in {body_id}/{section}: "
|
|
f"{seen[name]} and {fid} both assigned"
|
|
)
|
|
seen[name] = fid
|
|
|
|
|
|
def apply_sol_fixes(dry_run: bool = False) -> None:
|
|
_check_dedup(FIXES)
|
|
for body_id, body_fixes in FIXES.items():
|
|
path = WIKI / body_id / "markers.json"
|
|
if not path.exists():
|
|
console.event(f" SKIP {body_id}: markers.json not found")
|
|
continue
|
|
|
|
with open(path) as f:
|
|
markers = json.load(f)
|
|
|
|
changed = False
|
|
for section, id_map in body_fixes.items():
|
|
feature_list = markers.get(SECTION_MAP[section], [])
|
|
for feature in feature_list:
|
|
fid = feature.get("id", "")
|
|
if fid in id_map:
|
|
old = feature.get("name")
|
|
new = id_map[fid]
|
|
if old != new:
|
|
console.event(f" [{body_id}/{section}] {fid}: {old!r} → {new!r}")
|
|
if not dry_run:
|
|
feature["name"] = new
|
|
changed = True
|
|
|
|
if changed:
|
|
if dry_run:
|
|
console.event(f" (dry-run) Would write: {path}")
|
|
else:
|
|
with open(path, "w") as f:
|
|
json.dump(markers, f, indent=2)
|
|
console.event(f" Written: {path}")
|
|
else:
|
|
console.event(f" No changes for {body_id}")
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> None:
|
|
parser = argparse.ArgumentParser(
|
|
description="Name previously-unnamed Sol body auto-detected features."
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="print planned changes without writing any files",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
apply_sol_fixes(dry_run=args.dry_run)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|