- make test-tooling: planet-gen determinism guard + import_economics
--dry-run, wired into pre-push on TOOLING_CHANGED; ruff widened to
E4/E7/E9/F/W (90 safe auto-fixes applied; E402/E702/F841 ignored with
documented counts)
- one-generator reality fixed in DEVOPS.md, asset-pipeline rule, CLAUDE.md
(import_economics sole generator since #951/D-223); dead check-protocol
target deleted; DEVOPS hook/config sections rewritten from the actual
hook sources; team-patterns gate description updated (client+tooling)
- project.yaml: 0.2.0 → 0.4.0 per the 0.{phase}.{n} scheme, description
refreshed from the v0.1 Sova narration to cascade reality
- stale comment sweep: voxel.rs stub claims (all 8 families implemented),
cascade.rs TODO recited to T-1044, main.rs D-192 handshake claim,
relationships.rs/chunk_streaming.rs version targets → phase language
- gitignore: client/settings.db* e2e-run artifacts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
243 lines
6.4 KiB
Python
243 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
generate-stubs.py
|
|
Generates wiki stub markdown files for all star systems from star-map.json.
|
|
|
|
Each system gets: wiki/star-systems/{gj_dir}/index.md
|
|
where {gj_dir} is the astronomical_id with spaces replaced by hyphens.
|
|
|
|
Wiki pages are the SINGLE SOURCE OF TRUTH for all per-system data.
|
|
star-map.json provides initial values for topology/identity fields;
|
|
once written to wiki, the wiki page is authoritative.
|
|
|
|
Safe to re-run: skips files that already exist (use --force to overwrite).
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
STAR_MAP_PATH = Path(__file__).parent.parent.parent / "docs/design/star-map.json"
|
|
WIKI_DIR = Path(__file__).parent.parent.parent / "wiki/star-systems"
|
|
|
|
|
|
def gj_to_dirname(astronomical_id: str) -> str:
|
|
"""Convert 'GJ 71' to 'GJ-71', '2MASS J...' to '2MASS-J...'."""
|
|
return astronomical_id.replace(" ", "-")
|
|
|
|
|
|
def render_stub(node: dict, adjacent_ids: list[str]) -> str:
|
|
"""Render a wiki stub with ALL fields in frontmatter."""
|
|
sid = node["system_id"]
|
|
astro_id = node["astronomical_id"]
|
|
proper_name = node.get("proper_name")
|
|
system_name = proper_name if proper_name else node.get("system_name", f"System {sid}")
|
|
|
|
star_type = node.get("star_type", "~")
|
|
sector = node.get("geographic_sector", "~")
|
|
band = node.get("geographic_band", "~")
|
|
zone = node.get("political_zone", "~")
|
|
wave = node.get("settlement_wave", "~")
|
|
horizon = node.get("aperture_count", 0) > 0
|
|
aperture = node.get("aperture_count", 0)
|
|
connections = node.get("gate_connections", 0)
|
|
topology = node.get("gate_topology", "~")
|
|
earth_prox = node.get("earth_proximity", "~")
|
|
hop = node.get("hop_distance_from_gateway", "~")
|
|
dist_ly = node.get("dist_ly", 0)
|
|
spectral = node.get("spectral_class", "~")
|
|
|
|
# Format proper_name for YAML
|
|
proper_yaml = f'"{proper_name}"' if proper_name else "~"
|
|
|
|
# Adjacent systems list
|
|
adjacent_str = ", ".join(adjacent_ids) if adjacent_ids else "none"
|
|
|
|
return f"""---
|
|
# {astro_id} — {system_name}
|
|
# Star Systems Wiki — The Settled Reach
|
|
# Source of truth for all per-system data.
|
|
# Topology graph (edges) lives in star-map.json.
|
|
|
|
# I. Identity and Location
|
|
system_id: {sid}
|
|
astronomical_id: "{astro_id}"
|
|
proper_name: {proper_yaml}
|
|
system_name: "{system_name}"
|
|
star_type: {star_type}
|
|
spectral_class: "{spectral}"
|
|
dist_ly: {dist_ly}
|
|
geographic_sector: {sector}
|
|
geographic_band: {band}
|
|
political_zone: {zone}
|
|
|
|
# II. Physical Character
|
|
habitable_planet_count: ~
|
|
inhabited_planet_count: ~
|
|
asteroid_belt: ~
|
|
gas_giant: ~
|
|
habitability_profile: ~
|
|
|
|
# III. Gate Infrastructure
|
|
horizon_station: {str(horizon).lower()}
|
|
aperture_count: {aperture}
|
|
gate_connections: {connections}
|
|
gate_topology: {topology}
|
|
span_gate_network: ~
|
|
|
|
# IV. Settlement History
|
|
settlement_wave: {wave}
|
|
founding_motivation: ~
|
|
founding_culture_primary: ~
|
|
founding_culture_secondary: ~
|
|
cultural_persistence: ~
|
|
historical_events: []
|
|
religious_status: ~
|
|
religious_generation_count: ~
|
|
|
|
# V. Economic Life
|
|
economic_tier: ~
|
|
population: ~
|
|
economic_base_primary: ~
|
|
economic_base_secondary: ~
|
|
distribution_index: ~
|
|
imprint_access: ~
|
|
|
|
# VI. Governance
|
|
governance_type: ~
|
|
dominant_faction: ~
|
|
primary_fault_line: ~
|
|
secondary_fault_line: ~
|
|
|
|
# VII. Faction Presence
|
|
assembly_presence: ~
|
|
commission_presence: ~
|
|
syndic_presence: ~
|
|
syndic_type: ~
|
|
separatist_presence: ~
|
|
separatist_type: ~
|
|
institute_presence: ~
|
|
guardians_presence: ~
|
|
|
|
# VIII. Cultural Voice
|
|
cultural_register: ~
|
|
cultural_register_secondary: ~
|
|
ambient_anxiety: ~
|
|
local_pride: ~
|
|
atmospheric_tone: ~
|
|
atmospheric_tone_secondary: ~
|
|
active_situation: ~
|
|
silence_threshold: ~
|
|
|
|
# IX. Political Character
|
|
earth_alignment: ~
|
|
earth_proximity: {earth_prox}
|
|
earth_tension: ~
|
|
|
|
# X. Narrative Profile
|
|
primary_archetype: ~
|
|
secondary_archetype: ~
|
|
narrative_notable: ~
|
|
generation_priority: ~
|
|
|
|
# XI. Content Notes
|
|
stability_index: ~
|
|
system_volatility: ~
|
|
cultural_corridor: ~
|
|
---
|
|
|
|
# {system_name}
|
|
**{astro_id}** | {sid} | {star_type}-type | {sector} {band}
|
|
|
|
## Supply Dependency
|
|
<!-- Required for manufacturing/transit_logistics base and narrative_notable systems -->
|
|
|
|
## Faction Notes
|
|
<!-- Significant local factions not covered by standard columns -->
|
|
|
|
## Silence Topic
|
|
<!-- Required for narrative_notable and Institutional Core / Diplomatic Periphery zones -->
|
|
|
|
## Narrative Hook
|
|
<!-- Required if narrative_notable = true. One sentence. -->
|
|
|
|
## Calibration Note
|
|
<!-- Unusual column combinations requiring justification -->
|
|
|
|
## Topology
|
|
<!-- READ-ONLY — regenerated from star-map.json edges -->
|
|
- **Hop Distance from Gateway:** {hop}
|
|
- **Adjacent Systems:** {adjacent_str}
|
|
"""
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate wiki stub files from star-map.json"
|
|
)
|
|
parser.add_argument(
|
|
"--force",
|
|
action="store_true",
|
|
help="Overwrite existing index.md files (never touches subdirectories)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
with open(STAR_MAP_PATH, "r") as f:
|
|
data = json.load(f)
|
|
|
|
nodes = data["nodes"]
|
|
edges = data["edges"]
|
|
|
|
# Build adjacency map: system_id -> list of connected system_ids
|
|
adj: dict[str, list[str]] = {n["system_id"]: [] for n in nodes}
|
|
for a, b in edges:
|
|
adj[a].append(b)
|
|
adj[b].append(a)
|
|
|
|
# Sort adjacency lists for deterministic output
|
|
for sid in adj:
|
|
adj[sid].sort()
|
|
|
|
created = 0
|
|
overwritten = 0
|
|
skipped = 0
|
|
|
|
for node in sorted(nodes, key=lambda n: n["system_id"]):
|
|
sid = node["system_id"]
|
|
astro_id = node["astronomical_id"]
|
|
dirname = gj_to_dirname(astro_id)
|
|
outdir = WIKI_DIR / dirname
|
|
outfile = outdir / "index.md"
|
|
|
|
if outfile.exists() and not args.force:
|
|
skipped += 1
|
|
continue
|
|
|
|
existed = outfile.exists()
|
|
outdir.mkdir(parents=True, exist_ok=True)
|
|
|
|
adjacent = adj.get(sid, [])
|
|
content = render_stub(node, adjacent)
|
|
|
|
outfile.write_text(content)
|
|
if existed:
|
|
overwritten += 1
|
|
else:
|
|
created += 1
|
|
|
|
print(f"Wiki stubs: {created} created, {overwritten} overwritten, {skipped} skipped")
|
|
print(f"Output directory: {WIKI_DIR}")
|
|
|
|
# Print a few examples
|
|
print("\nExample directories:")
|
|
dirs = sorted(WIKI_DIR.iterdir())[:5]
|
|
for d in dirs:
|
|
if d.is_dir():
|
|
idx = d / "index.md"
|
|
size = idx.stat().st_size if idx.exists() else 0
|
|
print(f" {d.name}/index.md ({size} bytes)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|