#!/usr/bin/env python3 """Generate client/data/star_map_data.json from star-map.json + systems.db. Run from any directory — paths are resolved relative to this script's location: python3 tooling/generate-star-map-data.py python3 tooling/generate-star-map-data.py --check # exit 1 if committed JSON is stale Sources: docs/design/star-map.json — graph topology (nodes + edges) server/server/data/systems.db — proper names, geographic sectors Output: client/data/star_map_data.json — self-contained client data for the star map UI """ import json import os import sqlite3 import sys import tempfile # Resolve project root from this script's location: tooling/ is one level below root. # Works regardless of cwd — no fragile relative path guessing. _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) _PROJECT_ROOT = os.path.dirname(_SCRIPT_DIR) # Worktree layout: settled-reach/{client,server,main}/ # This script lives in client/tooling/, so _PROJECT_ROOT = client/. # The parent of _PROJECT_ROOT is the worktree parent where sibling dirs live. _WORKTREE_PARENT = os.path.dirname(_PROJECT_ROOT) STAR_MAP_PATH = os.path.join(_PROJECT_ROOT, "docs", "design", "star-map.json") SYSTEMS_DB_PATH = os.path.join(_WORKTREE_PARENT, "server", "server", "data", "systems.db") OUTPUT_PATH = os.path.join(_PROJECT_ROOT, "client", "data", "star_map_data.json") def generate() -> dict: """Generate the enriched star map data dict.""" if not os.path.exists(STAR_MAP_PATH): print(f"ERROR: star-map.json not found at {STAR_MAP_PATH}", file=sys.stderr) sys.exit(1) if not os.path.exists(SYSTEMS_DB_PATH): print(f"ERROR: systems.db not found at {SYSTEMS_DB_PATH}", file=sys.stderr) sys.exit(1) with open(STAR_MAP_PATH) as f: star_map = json.load(f) conn = sqlite3.connect(SYSTEMS_DB_PATH) conn.row_factory = sqlite3.Row cur = conn.cursor() cur.execute( "SELECT system_id, proper_name, geographic_sector, geographic_band " "FROM star_systems" ) db_lookup = {row["system_id"]: dict(row) for row in cur.fetchall()} conn.close() nodes = [] for n in star_map["nodes"]: sid = n["system_id"] db = db_lookup.get(sid, {}) entry = { "system_id": sid, "proper_name": db.get("proper_name", ""), "geographic_sector": db.get("geographic_sector", "unknown"), "geographic_band": db.get("geographic_band", ""), "gate_topology": n["gate_topology"], "aperture_count": n["aperture_count"], "gate_connections": n["gate_connections"], "hop_distance": n["hop_distance_from_gateway"], } if n.get("is_gateway"): entry["is_gateway"] = True nodes.append(entry) nodes.sort(key=lambda x: (x["hop_distance"], x["system_id"])) return { "_meta": { "generated_from": "star-map.json + systems.db", "system_count": len(nodes), "edge_count": len(star_map["edges"]), "note": "Client-side star map data. Regenerate with: tooling/generate-star-map-data.py", }, "nodes": nodes, "edges": star_map["edges"], } def write_output(data: dict, path: str) -> None: os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w") as f: json.dump(data, f, indent=2, ensure_ascii=False) f.write("\n") def main() -> None: check_mode = "--check" in sys.argv data = generate() if check_mode: # Generate to temp file and compare against committed JSON if not os.path.exists(OUTPUT_PATH): print(f"STALE: {OUTPUT_PATH} does not exist — run without --check to generate") sys.exit(1) with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp: json.dump(data, tmp, indent=2, ensure_ascii=False) tmp.write("\n") tmp_path = tmp.name try: with open(tmp_path) as a, open(OUTPUT_PATH) as b: if a.read() != b.read(): print(f"STALE: {OUTPUT_PATH} differs from generated output") print("Run: python3 tooling/generate-star-map-data.py") sys.exit(1) print(f"OK: {OUTPUT_PATH} is up to date") finally: os.unlink(tmp_path) else: write_output(data, OUTPUT_PATH) print(f"Generated {OUTPUT_PATH}") print(f" Nodes: {data['_meta']['system_count']}, Edges: {data['_meta']['edge_count']}") if __name__ == "__main__": main()