#!/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: python3 sol_name_fixes.py # apply all fixes python3 sol_name_fixes.py --dry-run # print planned changes without writing """ import argparse import json from pathlib import Path REPO_ROOT = (Path(__file__).resolve().parent / ".." / "..").resolve() 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(): print(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: print(f" [{body_id}/{section}] {fid}: {old!r} → {new!r}") if not dry_run: feature["name"] = new changed = True if changed: if dry_run: print(f" (dry-run) Would write: {path}") else: with open(path, "w") as f: json.dump(markers, f, indent=2) print(f" Written: {path}") else: print(f" No changes for {body_id}") print() if __name__ == "__main__": 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() apply_sol_fixes(dry_run=args.dry_run)