Files
settled-reach/tooling/archive/wiki-bootstrap/patch-core-sector.py
T
jpmschweitzerandClaude Opus 5.5 4537b71b92 refactor(tooling): T-1290 — the wiki domain, and the renderer that must not run
`reach wiki stats` and `reach wiki gttr-hook` replace tooling/db/wiki_sync.py
and populate_gttr_hook.py. Both are output-identical to the originals:
`stats` byte-for-byte, and all 301 extracted GTTR hooks line-for-line.

wiki_sync.py moved whole, but generate_wiki() and import_from_wiki() are NOT
verbs. Before porting, the old `--generate` was run against a clean tree to get
a parity baseline. It changed all 301 system pages, +940 / -10,761, and was
reverted at once. It deletes the Celestial Bodies / Stations blocks (owned by
the Rust atlas sync, which it does not know about), deletes the
Industries / Exports / Imports rows (nothing writes those any more), and
rewrites star types where systems.db and the pages disagree. D-262, CLAUDE.md
and the wiki skill all described it as the routine, prose-preserving render.
CLAUDE.md and the skill now say not to run it; D-262 needs amending — T-1292.

Provenance moves to tooling/archive/, with a README naming what each script
did and why it is not run:

- pql-migrate/ (the T-1271 ruling)
- wiki-bootstrap/: assign-astro-ids + its catalog, migrate-s-to-gj,
  patch-core-sector (hardcodes a dead path), fill-missing-globes,
  generate-stubs and find-stubs (finds 0 stubs — Phase 1 is done),
  backfill_cultural_corridor (a raw systems.db patch script, outside D-262),
  and process-wiki-system-changes, whose last step is the destructive render

Also:

- stats() printed "run import first" and exited 0 when a table was missing;
  it now fails with a remedy. generate_wiki() counted created pages after
  writing them, so `created` was always 0.
- tooling/godot-cold-parse and godot-parse-sweep were never retired after
  T-1283, and the pr-process skill still told agents to run them. Removed;
  the skill and parse_sweep.gd now name the reach verbs.
- systems.db re-stamped: schema comments changed, and the stamp records the
  schema file's SHA for tamper detection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 16:25:51 +02: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()