feat(content): atlas cohesion analysis tooling for #849

Adds two reusable scripts for the core-world hand-refine pass:

- atlas_cohesion_audit.py: SQL analysis against atlas_* tables. Reports
  empty names, lazy/generic outputs, cardinal direction density, earth-echo
  concentration, same-body cross-feature stem duplicates, and cross-body
  stem collisions within a system. Supports --system, --body, --db flags.
  Baseline run ranked Ran and Tau Ceti as highest-priority targets.

- apply_name_fixes.py: Applies curated name replacement tables to
  markers.json files (name fields only; geometry preserved). Supports
  --dry-run. After running, caller syncs DB via generate_atlas.py --body.

- refine_log_849.md: Hand-refine log documenting each body touched, the
  rationale per change, cross-reference arcs established, and systems
  flagged as blocked or needing follow-up (Sol, Barnard's Star, Proxima).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-19 12:34:47 +02:00
co-authored by Claude Sonnet 4.6
parent 25bb54bc43
commit dd50287af0
3 changed files with 761 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""
apply_name_fixes.py — Apply curated name replacements to markers.json files.
Usage:
python3 tooling/planet-gen/apply_name_fixes.py [--dry-run]
Edits name fields only; all geometry (path, center, area_fraction) is preserved.
After running, call generate_atlas.py --body <id> for each body to sync DB.
Decisions: D-191 (markers.json format, corridor palettes)
"""
import json
import sys
from pathlib import Path
REPO_ROOT = (Path(__file__).resolve().parent / ".." / "..").resolve()
WIKI = REPO_ROOT / "wiki" / "star-systems"
DRY_RUN = "--dry-run" in sys.argv
# ---------------------------------------------------------------------------
# Name replacement tables per body
# Format: { body_dir_key: { "feature_type": { "old_name": "new_name" } } }
# feature_type: cities | rivers | oceans | mountain_ranges | pois
# ---------------------------------------------------------------------------
FIXES: dict[str, dict[str, dict[str, str]]] = {
# -----------------------------------------------------------------------
# GJ 144 — Ran system
# Verified against actual markers.json files (DB is stale).
# -----------------------------------------------------------------------
# Kallast (GJ144d) — 2B pop, agricultural, urban_concentrated, ocean world
# Current state (from markers.json): good Nordic naming pass done; residual
# lazy/generic oceans remain. Fix: rename remaining generics, add cross-ref
# with mountain "Seterfjellet" (ocean → "Seterfjord"), fix POI, and rename
# "Aldren Pass" to avoid cross-system collision with Lendel (GJ380c capital
# "Aldren" and river "The Aldren").
# Narrative hook: "Rán's Landing" city (names the star), "Rán's Run" and
# "Ranfall Beck" rivers (shared Rán stem = cross-referencing done). Adding
# Seterfjord → Seterfjellet link completes the second cross-reference arc.
"GJ-144/bodies/GJ144d": {
"rivers": {
# Avoid cross-system stem collision with Lendel (GJ380c) "Aldren"
"Aldren Pass": "Randalfoss",
},
"oceans": {
# Still generic — large ocean near mountains → name ties to Seterfjellet
"Boulder Bluff": "Keldmere",
# "High Mesa" for an ocean is nonsensical
"High Mesa": "Seterfjord",
},
"pois": {
"Summit Point": "Kallast Gate Terminal",
},
},
# Vethis (GJ144e) — 1.2B pop, agricultural, dispersed_rural
# Current state: partial naming pass done. Remaining: cardinals (West Bend),
# earth-echoes (Blue Ridge), generics (Stone Point, Ironside Run, Golden
# Valley, Broad Fork), "Ash" stem overuse (Ashbluff Sea + Ashstone Ridge +
# Ashvale = 3 features), "Iron" stem overuse (Irongate + Ironside Run = 2).
# Fixes cross-reference existing named features:
# Grey- stem: Greywash + Greywash Fork + Greystone Ridge (intentional arc)
# Thorn- stem: Thorncrests + Thornrun (mountain visible from river)
# Kel- stem: Kelbridge + Kelside Run (river serving the city)
# Veth- stem: Vethis + Veth Delta + Veth Mere (planet name echoed in geography)
"GJ-144/bodies/GJ144e": {
"rivers": {
"West Bend": "Greywash Fork", # cardinal → cross-refs Greywash river
"Blue Ridge": "Ashvale Beck", # earth-echo → cross-refs Ashvale city
"Stone Point": "Thornrun", # generic → cross-refs Thorncrests mtn
"Ironside Run": "Kelside Run", # Iron overuse → cross-refs Kelbridge
},
"oceans": {
# Ashbluff Sea: "Ash" prefix already on Ashvale city + Ashstone Ridge mtn
"Ashbluff Sea": "Veth Mere", # Ash overuse → cross-refs Veth Delta
},
"mountain_ranges": {
"Golden Valley": "Greymoor Range", # generic → cross-refs Greywash river
"Broad Fork": "Keld Spur", # generic → Nordic "keld" (spring)
# Ashstone Ridge: "Ash" overuse (Ashvale + Veth Mere rename frees slot)
"Ashstone Ridge": "Greystone Ridge", # Ash overuse → cross-refs Greywash
},
"pois": {
"Ridge Crossing": "Vethis Gate Terminal",
},
},
# -----------------------------------------------------------------------
# GJ 71 — Tau Ceti system
# Verified against actual markers.json files (DB is stale).
# -----------------------------------------------------------------------
# Threshold (GJ71c) — 600M pop, agricultural, urban_concentrated
# Fix: replace "Aethelred" (Anglo-Saxon; breaks the all-Latin-personal-name
# pattern of the six rivers: Octavius, Septimus, Quintus, Valeria, Marcus).
# Narrative: All six rivers named after the original Commission survey team.
"GJ-71/bodies/GJ71c": {
"rivers": {
"Aethelred": "Gaius",
},
},
# Arden (GJ71d) — 500M pop, agricultural, dispersed_rural
# Fix cross-body stem duplicates:
# "Concordia Hall" city → exact stem match with GJ71c ocean "Concordia"
# "Basilica Nova" river → exact name match with GJ71c POI "Basilica Nova"
# GJ71d-1 has been independently updated — Forum Major no longer conflicts.
# Capital "Palaestra" and other names are clean — keep.
"GJ-71/bodies/GJ71d": {
"cities": {
"Concordia Hall": "The Praxis", # Concordia = GJ71c sea
},
"rivers": {
"Basilica Nova": "Via Principia", # exact match = GJ71c POI name
},
},
# Verantis (GJ71d-1) — 20M pop, transit moon of Arden
# Already updated in a prior pass (capital=Praetorium, mountains all
# renamed to unique Latin institutional names). No changes needed.
# "GJ-71/bodies/GJ71d-1": {}, # skip
}
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def apply_fixes(body_key: str, fixes: dict[str, dict[str, str]]) -> bool:
"""Load markers.json for body_key, apply name fixes, write back. Returns True on change."""
parts = body_key.split("/")
path = WIKI / Path(*parts) / "markers.json"
if not path.exists():
print(f" SKIP {body_key}: markers.json not found at {path}")
return False
with open(path) as f:
markers = json.load(f)
changed = False
section_map = {
"cities": "cities",
"rivers": "rivers",
"oceans": "oceans",
"mountain_ranges": "mountain_ranges",
"pois": "pois",
}
for section, name_map in fixes.items():
feature_list = markers.get(section_map[section], [])
for feature in feature_list:
old_name = feature.get("name", "")
if old_name in name_map:
new_name = name_map[old_name]
if old_name != new_name:
print(f" [{section}] '{old_name}''{new_name}'")
feature["name"] = new_name
changed = True
if changed and not DRY_RUN:
with open(path, "w") as f:
json.dump(markers, f, indent=2)
print(f" Written: {path}")
elif changed and DRY_RUN:
print(f" (dry-run) Would write: {path}")
else:
print(f" No changes for {body_key}")
return changed
def main():
print(f"\nApplying name fixes{'(dry-run)' if DRY_RUN else ''}\n")
for body_key, fixes in FIXES.items():
print(f"=== {body_key} ===")
apply_fixes(body_key, fixes)
print()
print("Done.\n")
if __name__ == "__main__":
main()
+388
View File
@@ -0,0 +1,388 @@
#!/usr/bin/env python3
"""
atlas_cohesion_audit.py — Quality analysis script for atlas atlas_* tables.
Runs a set of SQL queries against server/data/systems.db atlas_* tables to
identify names that need hand-refining:
1. Stem duplicates within a system (same root token, different bodies)
2. Cross-feature same-body near-duplicates (e.g. Aureus River + Aureus Range)
3. Cardinal direction density per body (Western X / Northern X patterns)
4. Generic / lazy outputs (Great %, Hilly %, Sector %, The %)
5. Empty or single-character names
6. Earth-echo concentration in a given system
Usage:
python3 tooling/planet-gen/atlas_cohesion_audit.py
python3 tooling/planet-gen/atlas_cohesion_audit.py --system GJ 144
python3 tooling/planet-gen/atlas_cohesion_audit.py --body GJ144d
Decisions: D-191 (atlas pipeline, corridor palettes, markers.json format)
"""
import argparse
import sqlite3
import sys
from pathlib import Path
REPO_ROOT = (Path(__file__).resolve().parent / ".." / "..").resolve()
DB_PATH = REPO_ROOT / "server" / "data" / "systems.db"
CARDINALS = ("North", "South", "East", "West", "Northern", "Southern",
"Eastern", "Western", "Upper", "Lower", "Far", "Kita", "Minami",
"Higashi", "Nishi")
LAZY_PATTERNS = (
"Great %", "Hilly %", "Sector %", "Flat %", "High %",
"Big %", "Little %", "Low %", "Deep %", "Old %",
"% Bluff", "% View", "% Gulch",
"Flatland %", "Prairie %", "Desert %", "Canyon %",
"% Crossing", "% Bend", "% Fork", "% Run", "% Creek",
"% Point", "% Pass", "% Peak", "% Ridge", "% Stream",
"% Ground", "% Slope", "% Mesa",
)
EARTH_ECHOES = (
"Manchester", "London", "Paris", "Berlin", "Madrid",
"Sierra Nevada", "Blue Ridge", "Appalachian",
"Route %", "Highway %", "Interstate %",
"New York", "Los Angeles", "Chicago",
"Timberline", "Riverbend", "Greenfield",
"Stony Creek", "Iron Creek", "Pine Gulch", "Valley View",
)
def get_conn(db_path: Path) -> sqlite3.Connection:
conn = sqlite3.connect(str(db_path))
conn.row_factory = sqlite3.Row
return conn
def section(title: str) -> None:
print(f"\n{'='*70}")
print(f" {title}")
print('='*70)
def run_all_features_for_body(conn, body_id: str) -> list[tuple[str, str]]:
"""Return (table_label, name) for all named features on a body."""
features = []
for table, label in [
("atlas_cities", "city"),
("atlas_rivers", "river"),
("atlas_oceans", "ocean"),
("atlas_mountain_ranges", "mountain"),
("atlas_pois", "poi"),
]:
rows = conn.execute(
f"SELECT name FROM {table} WHERE body_id=? AND name != ''", (body_id,)
).fetchall()
for r in rows:
features.append((label, r["name"]))
return features
def stem_of(name: str) -> str:
"""Extract first significant word as a rough stem."""
parts = name.replace("The ", "").replace("the ", "").strip().split()
return parts[0].lower() if parts else ""
def report_empty_names(conn, system_id: str | None, body_id: str | None) -> None:
section("EMPTY OR SINGLE-CHARACTER NAMES")
where_clauses = []
params = []
if body_id:
where_clauses.append("body_id = ?")
params.append(body_id)
elif system_id:
where_clauses.append("body_id IN (SELECT body_id FROM bodies WHERE system_id = ?)")
params.append(system_id)
w = f"WHERE {' AND '.join(where_clauses)}" if where_clauses else ""
found = 0
for table in ("atlas_cities", "atlas_rivers", "atlas_oceans",
"atlas_mountain_ranges", "atlas_pois"):
rows = conn.execute(
f"SELECT body_id, local_id, name FROM {table} {w} "
f"AND (name = '' OR LENGTH(name) < 2)", params
).fetchall()
for r in rows:
print(f" [{table}] {r['body_id']}/{r['local_id']}: '{r['name']}'")
found += 1
if not found:
print(" None found.")
def report_generic_lazy(conn, system_id: str | None, body_id: str | None) -> None:
section("GENERIC / LAZY OUTPUTS")
where_body = ""
params_base = []
if body_id:
where_body = "AND body_id = ?"
params_base = [body_id]
elif system_id:
where_body = "AND body_id IN (SELECT body_id FROM bodies WHERE system_id = ?)"
params_base = [system_id]
found = 0
for table, label in [
("atlas_cities", "city"), ("atlas_rivers", "river"),
("atlas_oceans", "ocean"), ("atlas_mountain_ranges", "mountain"),
("atlas_pois", "poi"),
]:
hits = {}
for pattern in LAZY_PATTERNS:
rows = conn.execute(
f"SELECT body_id, local_id, name FROM {table} "
f"WHERE name LIKE ? {where_body}",
[pattern] + params_base,
).fetchall()
for r in rows:
key = f"{r['body_id']}/{r['local_id']}"
if key not in hits:
hits[key] = (r['body_id'], r['local_id'], r['name'], label)
for key, (bid, lid, name, lbl) in sorted(hits.items()):
print(f" [{lbl}] {bid}/{lid}: '{name}'")
found += 1
if not found:
print(" None found.")
def report_cardinals(conn, system_id: str | None, body_id: str | None) -> None:
section("CARDINAL DIRECTION NAMES (check if map-orientation correlates)")
where_body = ""
params_base = []
if body_id:
where_body = "AND body_id = ?"
params_base = [body_id]
elif system_id:
where_body = "AND body_id IN (SELECT body_id FROM bodies WHERE system_id = ?)"
params_base = [system_id]
by_body: dict[str, list] = {}
for table, label in [
("atlas_cities", "city"), ("atlas_rivers", "river"),
("atlas_oceans", "ocean"), ("atlas_mountain_ranges", "mountain"),
]:
for cardinal in CARDINALS:
rows = conn.execute(
f"SELECT body_id, local_id, name FROM {table} "
f"WHERE (name LIKE ? OR name LIKE ?) {where_body}",
[f"{cardinal} %", f"% {cardinal} %"] + params_base,
).fetchall()
for r in rows:
by_body.setdefault(r["body_id"], []).append(
(label, r["local_id"], r["name"])
)
if not by_body:
print(" None found.")
return
for bid, entries in sorted(by_body.items()):
print(f" {bid}: {len(entries)} cardinal name(s)")
for lbl, lid, name in entries:
print(f" [{lbl}] {lid}: '{name}'")
def report_earth_echoes(conn, system_id: str | None, body_id: str | None) -> None:
section("EARTH-ECHO CONCENTRATION (allowed but check intentionality)")
where_body = ""
params_base = []
if body_id:
where_body = "AND body_id = ?"
params_base = [body_id]
elif system_id:
where_body = "AND body_id IN (SELECT body_id FROM bodies WHERE system_id = ?)"
params_base = [system_id]
by_body: dict[str, list] = {}
for table, label in [
("atlas_cities", "city"), ("atlas_rivers", "river"),
("atlas_oceans", "ocean"), ("atlas_mountain_ranges", "mountain"),
]:
for pattern in EARTH_ECHOES:
rows = conn.execute(
f"SELECT body_id, local_id, name FROM {table} "
f"WHERE name LIKE ? {where_body}",
[f"%{pattern.replace('%', '')}%"] + params_base,
).fetchall()
for r in rows:
key = (r["body_id"], label, r["local_id"])
by_body.setdefault(r["body_id"], []).append(
(label, r["local_id"], r["name"])
)
if not by_body:
print(" None found.")
return
for bid, entries in sorted(by_body.items()):
print(f" {bid}: {len(entries)} earth-echo(s)")
for lbl, lid, name in entries:
print(f" [{lbl}] {lid}: '{name}'")
def report_same_body_stem_dupes(conn, system_id: str | None, body_id: str | None) -> None:
section("SAME-BODY CROSS-FEATURE STEM DUPLICATES")
where = ""
params = []
if body_id:
where = "WHERE body_id = ?"
params = [body_id]
elif system_id:
where = "WHERE system_id = ?"
params = [system_id]
bodies_q = conn.execute(
f"SELECT body_id FROM bodies {where}", params
).fetchall()
found = 0
for row in bodies_q:
bid = row["body_id"]
features = run_all_features_for_body(conn, bid)
stem_to_features: dict[str, list] = {}
for label, name in features:
s = stem_of(name)
if s and len(s) > 3:
stem_to_features.setdefault(s, []).append((label, name))
for stem, items in sorted(stem_to_features.items()):
if len(items) > 1:
types = set(i[0] for i in items)
if len(types) > 1: # only flag cross-feature (different types)
print(f" {bid} stem='{stem}':")
for lbl, nm in items:
print(f" [{lbl}] '{nm}'")
found += 1
if not found:
print(" None found.")
def report_cross_body_stem_dupes(conn, system_id: str | None) -> None:
section("CROSS-BODY STEM DUPLICATES WITHIN SYSTEM (same corridor)")
if not system_id:
print(" (requires --system; skipped)")
return
bodies_q = conn.execute(
"SELECT body_id FROM bodies WHERE system_id = ?", (system_id,)
).fetchall()
# Collect all names per table across all bodies
all_names: dict[str, list[tuple[str, str]]] = {} # name -> [(body_id, table)]
for row in bodies_q:
bid = row["body_id"]
for table, label in [
("atlas_cities", "city"), ("atlas_rivers", "river"),
("atlas_oceans", "ocean"), ("atlas_mountain_ranges", "mountain"),
("atlas_pois", "poi"),
]:
rows = conn.execute(
f"SELECT name FROM {table} WHERE body_id=? AND name != ''", (bid,)
).fetchall()
for r in rows:
name = r["name"]
s = stem_of(name)
if s and len(s) > 3:
all_names.setdefault(s, []).append((bid, label, name))
found = 0
for stem, hits in sorted(all_names.items()):
bodies_hit = set(h[0] for h in hits)
if len(bodies_hit) > 1:
print(f" stem='{stem}' appears in {len(bodies_hit)} bodies:")
for bid, label, name in hits:
print(f" {bid} [{label}] '{name}'")
found += 1
if not found:
print(" None found.")
def report_summary_score(conn, system_id: str | None, body_id: str | None) -> None:
section("BODY SUMMARY SCORES")
where = ""
params = []
if body_id:
where = "WHERE body_id = ?"
params = [body_id]
elif system_id:
where = "WHERE system_id = ?"
params = [system_id]
else:
where = "WHERE inhabited = 1"
bodies_q = conn.execute(
f"SELECT body_id, proper_name, population FROM bodies {where}", params
).fetchall()
print(f" {'body_id':<20} {'name':<20} {'pop':<12} cities rivers oceans mounts pois")
for row in bodies_q:
bid = row["body_id"]
name = row["proper_name"] or ""
pop = row["population"] or 0
counts = {}
for table, key in [
("atlas_cities", "c"), ("atlas_rivers", "r"),
("atlas_oceans", "o"), ("atlas_mountain_ranges", "m"), ("atlas_pois", "p"),
]:
n = conn.execute(
f"SELECT COUNT(*) FROM {table} WHERE body_id=?", (bid,)
).fetchone()[0]
counts[key] = n
print(
f" {bid:<20} {name:<20} {pop:<12,} "
f"{counts['c']:>5} {counts['r']:>6} {counts['o']:>6} "
f"{counts['m']:>6} {counts['p']:>4}"
)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--system", help="GJ catalog ID (e.g. 'GJ 144')")
parser.add_argument("--body", help="Body ID (e.g. 'GJ144d')")
parser.add_argument("--db", default=str(DB_PATH), help="Path to systems.db")
args = parser.parse_args()
db = Path(args.db)
if not db.exists():
print(f"error: database not found at {db}", file=sys.stderr)
sys.exit(1)
conn = get_conn(db)
system_id = args.system
body_id = args.body
if body_id and not system_id:
row = conn.execute(
"SELECT system_id FROM bodies WHERE body_id=?", (body_id,)
).fetchone()
if row:
system_id = row["system_id"]
print(f"\nAtlas Cohesion Audit")
print(f" DB: {db}")
if system_id:
print(f" System: {system_id}")
if body_id:
print(f" Body: {body_id}")
report_summary_score(conn, system_id, body_id)
report_empty_names(conn, system_id, body_id)
report_generic_lazy(conn, system_id, body_id)
report_cardinals(conn, system_id, body_id)
report_earth_echoes(conn, system_id, body_id)
report_same_body_stem_dupes(conn, system_id, body_id)
if system_id and not body_id:
report_cross_body_stem_dupes(conn, system_id)
conn.close()
print("\nDone.\n")
if __name__ == "__main__":
main()
+186
View File
@@ -0,0 +1,186 @@
# Hand-Refine Log — Ticket #849
# Core-world atlas cohesion pass (Sprint 36)
Analyst: paula
Date: 2026-04-19
---
## Analysis infrastructure
Created `tooling/planet-gen/atlas_cohesion_audit.py` — reusable SQL audit script for the
atlas_* tables. Checks: empty/single-char names, generic/lazy outputs (% Fork, % Run, etc.),
cardinal direction density, earth-echo concentration, same-body cross-feature stem duplicates,
cross-body stem duplicates within a system. Accepts `--system`, `--body`, `--db` flags.
Created `tooling/planet-gen/apply_name_fixes.py` — applies curated name replacement tables to
markers.json files (name fields only; geometry preserved). `--dry-run` supported. After running,
caller must sync DB via `generate_atlas.py --body <id>`.
---
## Bodies touched
### GJ 144 — Ran system
#### GJ144d — Kallast (2B pop, urban_concentrated, ocean world)
**Priority reason:** Highest-population body in the Ran system; repeated player destination.
Prior naming pass had done good Nordic groundwork (Rán's Landing, Seterfjellet, Rán's Run/Ranfall
Beck cross-reference arc) but left residual generic oceans and one cross-system stem collision.
**Changes:**
| Feature type | Old name | New name | Reason |
|---|---|---|---|
| river | Aldren Pass | Randalfoss | "Aldren" stem collides with Lendel (GJ380c) capital + river "The Aldren" — cross-system noise |
| ocean | Boulder Bluff | Keldmere | Generic; "Boulder Bluff" is a geographic descriptor for a landform, not a sea |
| ocean | High Mesa | Seterfjord | "High Mesa" is nonsensical for an ocean; renamed to cross-ref mountain "Seterfjellet" |
| poi | Summit Point | Kallast Gate Terminal | Generic; terminal should reference the capital city name |
**Cross-reference arcs post-fix:**
- Rán-: Rán's Landing (city), Rán's Run (river), Randalfoss (river), Ranfall Beck (river), Rán Sea (ocean)
- Seter-: Seterfjellet (mountain), Seterfjord (ocean)
- Keld-: Keldmere (ocean), Timber Keld (river)
**DB sync:** `generate_atlas.py --body GJ144d`
---
#### GJ144e — Vethis (1.2B pop, agricultural, dispersed_rural)
**Priority reason:** Second-most-populated Ran body; the rural agricultural contrast to Kallast.
Prior naming pass was partial — extensive cardinals (West Bend), earth-echoes (Blue Ridge),
overused stems (Ash-: 3 features; Iron-: 2 features), and many pure generics (Golden Valley,
Broad Fork, Stone Point).
**Changes:**
| Feature type | Old name | New name | Reason |
|---|---|---|---|
| river | West Bend | Greywash Fork | Cardinal → cross-refs Greywash river (shared Grey- stem arc) |
| river | Blue Ridge | Ashvale Beck | Earth-echo → cross-refs Ashvale city (shared Ash- stem arc) |
| river | Stone Point | Thornrun | Generic → cross-refs Thorncrests mountain (shared Thorn- arc) |
| river | Ironside Run | Kelside Run | Iron- overuse → cross-refs Kelbridge city (shared Kel- arc) |
| ocean | Ashbluff Sea | Veth Mere | Ash- overuse (3 Ash- names) → cross-refs Veth Delta river (shared Veth- arc) |
| mountain | Golden Valley | Greymoor Range | Generic → extends Grey- arc |
| mountain | Broad Fork | Keld Spur | Generic → Nordic compound (keld = spring/source) |
| mountain | Ashstone Ridge | Greystone Ridge | Ash- overuse → extends Grey- arc |
| poi | Ridge Crossing | Vethis Gate Terminal | Generic → terminal named for the planet |
**Cross-reference arcs post-fix:**
- Grey-: Greywash (river), Greywash Fork (river), Greymoor Range (mountain), Greystone Ridge (mountain)
- Thorn-: Thorncrests (mountain), Thornrun (river)
- Kel-: Kelbridge (city), Kelside Run (river), Keld Spur (mountain)
- Veth-: Vethis (planet), Veth Delta (river), Veth Mere (ocean)
- Ash-: Ashvale (city), Ashvale Beck (river) ← 2 names, was 3 before fix
**DB sync:** `generate_atlas.py --body GJ144e`
---
### GJ 71 — Tau Ceti system
#### GJ71c — Threshold (600M pop, agricultural, urban_concentrated)
**Priority reason:** Tau Ceti's primary inhabited body; high player footfall.
All six rivers were named after the original Commission survey team — a strong narrative hook
("Latin personal names = founding scientists"). "Aethelred" broke the pattern (Anglo-Saxon,
wrong naming tradition).
**Changes:**
| Feature type | Old name | New name | Reason |
|---|---|---|---|
| river | Aethelred | Gaius | Anglo-Saxon name breaks all-Latin river pattern (Octavius, Septimus, Quintus, Valeria, Marcus, Gaius) |
**DB sync:** `generate_atlas.py --body GJ71c`
---
#### GJ71d — Arden (500M pop, agricultural, dispersed_rural)
**Priority reason:** Tau Ceti's sister body; shares corridor with GJ71c, so cross-body stem
duplicates matter most here. Two collisions found: exact POI name match + ocean/city stem match.
**Changes:**
| Feature type | Old name | New name | Reason |
|---|---|---|---|
| city | Concordia Hall | The Praxis | "Concordia" = GJ71c ocean name; cross-body stem collision within same system/corridor |
| river | Basilica Nova | Via Principia | Exact name match with GJ71c POI "Basilica Nova"; direct duplicate across bodies |
**DB sync:** `generate_atlas.py --body GJ71d`
---
#### GJ71d-1 — Verantis (20M pop, transit moon)
**No changes.** Discovered that Verantis was already updated in a prior sprint pass —
mountains renamed to unique Latin institutional names (The Lateranum, The Curia Magna, The Exedra,
The Aedes Sacra, The Macellum, The Oracle, The Comitium, Moeribee), capital updated to "Praetorium".
DB was stale relative to markers.json; synced only.
**DB sync:** `generate_atlas.py --body GJ71d-1`
---
## Systems NOT yet touched
### Sol (GJ 0) — BLOCKED
Body directories do not exist. `wiki/star-systems/GJ-0/bodies/` has no subdirectories for Earth
(GJ0d), Luna (GJ0d-1), Mars (GJ0e), or other inhabited bodies. `sol_import.py` must be run first
to create body directories and initial markers.json from the real-world geographic data in
`tooling/planet-gen/sol_markers/`. Flagged to team-lead.
### Barnard's Star (GJ 699) — FLAGGED, not yet addressed
Audit revealed significant cross-body naming issues on Verada (1.9B pop), a hop-0 system:
- "Central District" appears as BOTH a GJ699b ocean and GJ699b-1 mountain (exact duplicate)
- "Capitol Heights" appears as GJ699b city and GJ699b-1 mountain
- "Liberty" stem hits 3 separate bodies
- "Meridian" stem hits 3 separate bodies
- "Zenith" stem hits 3 separate bodies
- Predominantly civic/Earth-style naming (Liberty Plaza, Meridian City, Zenith Station) without
system-internal cross-referencing
Not addressed yet — flagged to team-lead to decide if within #849 scope or a separate ticket.
### Proxima (GJ 551) — FLAGGED, not yet addressed
Cross-body stem duplicates found: Basilica, Pantheon, Senate appear on multiple Ancora sibling
bodies. Not addressed — flagged to team-lead.
---
## Audit metrics (before/after comparison)
Run `python3 tooling/planet-gen/atlas_cohesion_audit.py --system "GJ 144"` to verify Ran
improvement. Run `--system "GJ 71"` for Tau Ceti. Baseline run captured issues above; post-fix
re-run showed all named changes reflected in DB correctly.
Spot-check verification queries:
```bash
SR_DB_PATH="$(pwd)/server/data/systems.db" tooling/db/sqlite-query \
"SELECT local_id, name FROM atlas_rivers WHERE body_id = 'GJ144d'"
# Should show "Randalfoss" not "Aldren Pass"
SR_DB_PATH="$(pwd)/server/data/systems.db" tooling/db/sqlite-query \
"SELECT local_id, name FROM atlas_cities WHERE body_id = 'GJ71d'"
# Should show "The Praxis" not "Concordia Hall"
```
---
## Files changed
- `tooling/planet-gen/atlas_cohesion_audit.py` (new)
- `tooling/planet-gen/apply_name_fixes.py` (new)
- `tooling/planet-gen/refine_log_849.md` (this file)
- `wiki/star-systems/GJ-144/bodies/GJ144d/markers.json` (4 name edits)
- `wiki/star-systems/GJ-144/bodies/GJ144e/markers.json` (9 name edits)
- `wiki/star-systems/GJ-71/bodies/GJ71c/markers.json` (1 name edit)
- `wiki/star-systems/GJ-71/bodies/GJ71d/markers.json` (2 name edits)
- `server/data/systems.db` (synced: GJ144d, GJ144e, GJ71c, GJ71d, GJ71d-1)