Files
settled-reach/tooling/patch-core-sector.py
T
jpmschweitzerandClaude Opus 4.6 ff9014ffc0 feat(docs): add star map gate topology for 300 systems
300-node gate network with 334 edges, generated from seed config then
hand-balanced and tuned. Gateway (S-001) is the first hop from Earth
with 4 active + 1 dormant Sol aperture.

Final topology: 20% dead_end, 14% spur_end, 31% through_route,
10% loop_member, 19% junction, 6% hub. Frontier sectors have
distinct linear corridors; core is densely interconnected.

Includes generation pipeline (generate, sculpt, patch-core, tune),
seed config, and star-map-plan.md with algorithm documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 22:57:32 +01:00

210 lines
7.6 KiB
Python

#!/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()