Files
settled-reach/tooling/planet-gen/sol_name_fixes.py
T
jpmschweitzerandClaude Opus 4.7 2c3e3ff5eb fix(content): PR #133 review — 9 remaining items resolved
Closes 9 of 10 review items (blocker 1 handled in add2507e + d78d3b59):

- decisions/economics.md: reformat D-189 §5 amendment to standard
  Amendment (YYYY-MM-DD) block pattern (review #4)
- tooling/planet-gen/sol_name_fixes.py: dedup guard + argparse
  --dry-run (reviews #5, #10a)
- tooling/planet-gen/sol_markers/earth_features.json: trim to 11
  cities with selection rationale (review #6A); user-approved
  rebalance Sydney → Lagos and London → Brussels (review #6B)
- wiki/star-systems/GJ-380/bodies/GJ380c/markers.json: 2 secondary
  features renamed to Akan/Asante register — Kesset → Nkwanta Beck,
  Holt Spur → Bosomtwe Spur (review #7)
- docs/atlas/hand-refine-log.md: Aethelred lore-migration
  documentation + see-also cross-link to refine_log_849.md
  (reviews #8, #13)
- tooling/planet-gen/refine_log_849.md: rebalance addendum
- wiki/star-systems/GJ-0/bodies/GJ0d/markers.json, server/data/
  systems.db: re-synced after rebalance

Stub depth (review blocker #2) handled via split — tracked as
follow-up ticket #861 (three-layer narrative authoring).

Final Earth cities (11): Beijing, Brussels, Cairo, Delhi, Istanbul,
Lagos, Moscow, New York, São Paulo, Singapore, Tokyo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:51:17 +02:00

148 lines
6.3 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:
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)