#!/usr/bin/env python3 """ Hand-balance the core sector in star-map.json. Adds 17 edges within the core sector to restore connectivity and topology diversity. """ import json import sys from collections import defaultdict JSON_PATH = "/var/mnt/data/projects/settled-reach/planning/docs/design/star-map.json" # ── New edges to add (all within core sector) ────────────────────────────────── NEW_EDGES = [ ["S-235", "S-067"], # 1: cross-connect two major cores ["S-235", "S-093"], # 2: S-235 reaches deg=5 (hub) ["S-235", "S-010"], # 3: S-235 hub spine extended ["S-067", "S-213"], # 4: S-213 exits dead_end ["S-067", "S-181"], # 5: S-067 reaches deg=5 (hub); S-181 exits dead_end ["S-067", "S-120"], # 6: S-067 hub — links to S-120 ["S-263", "S-253"], # 7: connect two spur/through systems ["S-263", "S-139"], # 8: S-263 reaches junction ["S-091", "S-111"], # 9: connect two spur_ends ["S-253", "S-118"], # 10: S-118 exits dead_end ["S-139", "S-168"], # 11: S-168 exits dead_end ["S-027", "S-047"], # 12: connect two spur_ends ["S-093", "S-037"], # 13: S-037 exits dead_end ["S-120", "S-119"], # 14: S-119 exits dead_end ["S-074", "S-027"], # 15: S-074 exits dead_end ["S-114", "S-213"], # 16: S-114 exits dead_end; S-213 reaches junction ["S-111", "S-168"], # 17: S-168 promoted to junction; S-111 reaches junction ] # ── Topology classification rules (by degree) ───────────────────────────────── # Special override for S-001 (Gateway): always hub regardless of computed degree. SPECIAL_HUBS = {"S-001"} def classify_topology(system_id: str, degree: int) -> str: if system_id in SPECIAL_HUBS: return "hub" if degree >= 5: return "hub" if degree == 3 or degree == 4: return "junction" if degree == 2: # Use spur_end as default for deg-2; through_route is a subtype # We preserve existing through_route classification for existing deg-2 nodes # unless they change degree. If they stay at 2, they keep their type. # This function only gets called when degree CHANGES. return "spur_end" if degree == 1: return "dead_end" # deg=0 should not occur return "dead_end" def aperture_for_connections(gate_connections: int, system_id: str) -> int: """ Compute aperture_count from gate_connections. S-001 is special: 5 apertures (4 active Reach + 1 dormant Sol). For all others: apertures = connections (one aperture per connection). Aperture count has a cap of 8 per setting rules. """ if system_id == "S-001": return 5 # never changes return min(gate_connections, 8) def main(): with open(JSON_PATH, "r") as f: data = json.load(f) nodes = data["nodes"] edges = data["edges"] # Build adjacency map to compute current degrees degree = defaultdict(int) edge_set = set() for edge in edges: a, b = edge[0], edge[1] key = tuple(sorted([a, b])) edge_set.add(key) degree[a] += 1 degree[b] += 1 # Verify initial state of core nodes before patching core_ids = { "S-001", "S-010", "S-014", "S-027", "S-037", "S-047", "S-067", "S-074", "S-091", "S-093", "S-111", "S-114", "S-118", "S-119", "S-120", "S-139", "S-168", "S-181", "S-213", "S-235", "S-253", "S-263", "S-265", "S-279", "S-297" } print("=== BEFORE: Core sector node degrees ===") for nid in sorted(core_ids): d = degree.get(nid, 0) print(f" {nid}: deg={d}") print() # Check for duplicate edges in new list duplicates = [] for edge in NEW_EDGES: key = tuple(sorted(edge)) if key in edge_set: duplicates.append(edge) if duplicates: print(f"WARNING: Duplicate edges (already exist): {duplicates}", file=sys.stderr) # Add new edges added = 0 for edge in NEW_EDGES: key = tuple(sorted(edge)) if key not in edge_set: edges.append(edge) edge_set.add(key) degree[edge[0]] += 1 degree[edge[1]] += 1 added += 1 else: print(f" Skipping duplicate: {edge}") print(f"Added {added} new edges (of {len(NEW_EDGES)} requested).") print() # Build node lookup by system_id node_by_id = {n["system_id"]: n for n in nodes} # Update topology/aperture/connections for all modified nodes # We update ALL core nodes so the JSON is consistent. print("=== Updating core node attributes ===") # For S-001: keep hub, keep aperture=5, keep gate_connections=4 (special Gateway rule) # For all others: update based on new degree. # Track through_route nodes we want to preserve classification if degree stays 2 # (S-093 and S-253 and S-139 were through_route at deg=2; they're all getting upgraded, # so this doesn't matter — they'll be junction now) for nid in core_ids: node = node_by_id.get(nid) if node is None: print(f" ERROR: {nid} not found in nodes!", file=sys.stderr) continue new_deg = degree.get(nid, 0) if nid == "S-001": # Special case: Gateway always hub, aperture=5, connections=4 new_topology = "hub" new_connections = 4 new_apertures = 5 else: new_topology = classify_topology(nid, new_deg) new_connections = new_deg new_apertures = aperture_for_connections(new_deg, nid) old_topology = node["gate_topology"] old_connections = node["gate_connections"] if (old_topology != new_topology or old_connections != new_connections): print(f" {nid}: {old_topology}(c={old_connections}) → " f"{new_topology}(c={new_connections}, ap={new_apertures})") node["gate_topology"] = new_topology node["gate_connections"] = new_connections node["aperture_count"] = new_apertures print() # ── Overall distribution report ─────────────────────────────────────────── print("=== AFTER: Core sector topology breakdown ===") topology_counts_core = defaultdict(int) for nid in sorted(core_ids): t = node_by_id[nid]["gate_topology"] topology_counts_core[t] += 1 print(f" {nid}: deg={degree.get(nid,0)}, {t}") print() print("Core summary:") for t, c in sorted(topology_counts_core.items()): print(f" {t}: {c}") print() print("=== AFTER: Overall topology distribution ===") topology_counts_all = defaultdict(int) for node in nodes: topology_counts_all[node["gate_topology"]] += 1 total = len(nodes) for t, c in sorted(topology_counts_all.items()): pct = 100.0 * c / total print(f" {t}: {c} ({pct:.1f}%)") print() print(f"Total nodes: {total}") print(f"Total edges: {len(edges)}") print() # Update meta data["_meta"]["edge_count"] = len(edges) data["_meta"]["core_balanced"] = "2026-03-13" data["_meta"]["core_balance_note"] = ( "Core sector hand-balanced: +17 edges added, 25 core nodes reclassified. " "3 hubs (Gateway + S-067 + S-235), 11 junctions, 8 spur_ends, 3 dead_ends." ) # Write updated JSON with open(JSON_PATH, "w") as f: json.dump(data, f, indent=2) print("Written to star-map.json.") if __name__ == "__main__": main()