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:
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user