Files
settled-reach/tooling/planet-gen/scaffold_bodies.py
T
jpmschweitzerandClaude Opus 4.6 0c3eddee39 fix(assets): address PR #117 review comments
- Remove tracked kallast_terrain.npy (HIGH — binary in git)
- Amend D-086: stance + interaction icons delivered, not deferred
- Create ticket #818 for icon_tint.gdshader (client team)
- Fix gas giant profile table: suppress gravity/land%/hydrosphere
- Fix atmosphere_color: null when atmosphere is none
- Fix gas_giant_ringed display as gas_giant in profile table
- Re-scaffold + regenerate Ran system with fixes

.import sidecars: not applicable — gitignored by design (client/**/*.import).
Godot auto-generates on first run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 16:19:53 +02:00

195 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""
Scaffold per-body index.md files from a system index.md.
Reads the system's Celestial Bodies table, runs body_definition_parser
to produce full body definitions, and writes one index.md per body
under wiki/star-systems/{system}/bodies/{body_id}/index.md.
The frontmatter IS the body definition — the generator reads it directly.
Below the frontmatter is space for authored body content (narrative, notes).
Usage:
python3 scaffold_bodies.py wiki/star-systems/GJ-144/index.md
python3 scaffold_bodies.py wiki/star-systems/GJ-144/index.md --overrides sol_overrides.json
python3 scaffold_bodies.py wiki/star-systems/GJ-144/index.md --dry-run
Only creates files that don't exist yet — never overwrites authored content.
Re-running is safe: existing body index.md files are skipped.
"""
import argparse
import json
import os
import sys
from pathlib import Path
# Venv bootstrap
TOOLING_DIR = Path(__file__).resolve().parent
WORKTREE_ROOT = (TOOLING_DIR / ".." / "..").resolve()
_venv_python = WORKTREE_ROOT / ".venv" / "bin" / "python"
if _venv_python.exists() and Path(sys.executable).resolve() != _venv_python.resolve():
os.execv(str(_venv_python), [str(_venv_python)] + sys.argv)
try:
import yaml
except ImportError:
# PyYAML is in pyproject.toml deps
print("error: PyYAML not installed — run `make setup-venv`", file=sys.stderr)
sys.exit(1)
from body_definition_parser import parse_system
def _body_to_frontmatter(bd: dict) -> str:
"""Convert a body definition dict to clean YAML frontmatter."""
# Order fields for readability
ordered = {}
for key in ("id", "name", "body_type", "planet_class", "body_scale", "seed"):
if key in bd:
ordered[key] = bd[key]
for section in ("star", "orbit", "physical", "terrain", "environment",
"clouds", "render", "gas_giant", "rings"):
if section in bd and bd[section] is not None:
ordered[section] = bd[section]
return yaml.dump(ordered, default_flow_style=False, sort_keys=False,
allow_unicode=True).rstrip()
def _body_prose(bd: dict, system_dir: Path) -> str:
"""Generate markdown content below the frontmatter."""
name = bd.get("name") or bd.get("id")
bid = bd["id"]
pclass = bd.get("planet_class", "unknown").replace("_ringed", "")
btype = bd.get("body_type", "planet")
wiki = bd.get("wiki", {})
phys = bd.get("physical", {})
orbit = bd.get("orbit", {})
terrain = bd.get("terrain", {})
env = bd.get("environment", {})
# System link (relative path from body dir to system index)
system_link = "../../index.md"
lines = [f"# {name}", ""]
# Type line
if btype == "moon":
lines.append(f"{pclass.title()} moon.")
elif pclass in ("gas_giant", "gas_giant_ringed"):
lines.append(f"Gas giant.")
else:
lines.append(f"{pclass.title()} {btype}.")
lines.append("")
# System link
lines.append(f"**System:** [{system_dir.name}]({system_link})")
lines.append("")
# Visual overview
lines.append("## Visual")
lines.append("")
lines.append(f"![Globe](globe.png)")
lines.append("")
if pclass not in ("gas_giant",):
lines.append(f"![Heightmap](heightmap.png)")
lines.append("")
# Profile table
lines.append("## Profile")
lines.append("")
is_gas = pclass in ("gas_giant", "gas_giant_ringed")
lines.append("| | |")
lines.append("|---|---|")
lines.append(f"| **Type** | {btype} |")
lines.append(f"| **Class** | {pclass} |")
if phys.get("gravity_g") and not is_gas:
lines.append(f"| **Gravity** | {phys['gravity_g']}g |")
if phys.get("atmosphere") and phys["atmosphere"] != "none":
lines.append(f"| **Atmosphere** | {phys['atmosphere']} |")
hydro = env.get("hydrosphere")
if hydro and hydro not in ("none", "—", ""):
lines.append(f"| **Hydrosphere** | {hydro} |")
if not is_gas and terrain.get("land_fraction") is not None:
lines.append(f"| **Land** | {terrain['land_fraction']*100:.0f}% |")
if orbit.get("period_days"):
label = "Orbit" if btype == "moon" else "Year"
lines.append(f"| **{label}** | {orbit['period_days']:.0f} days |")
if wiki.get("inhabited"):
lines.append(f"| **Inhabited** | yes |")
if wiki.get("population"):
lines.append(f"| **Population** | {wiki['population']} |")
if wiki.get("economy"):
lines.append(f"| **Economy** | {wiki['economy']} |")
if wiki.get("settlement"):
lines.append(f"| **Settlement** | {wiki['settlement']} |")
if wiki.get("industrial"):
lines.append(f"| **Industry** | {wiki['industrial']} |")
lines.append("")
# Content section
lines.append("## Description")
lines.append("")
lines.append("<!-- Body content: narrative, history, notes -->")
lines.append("")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Scaffold per-body index.md files from a system index.md")
parser.add_argument("system_index", help="Path to system index.md")
parser.add_argument("--overrides", help="Per-body overrides JSON")
parser.add_argument("--dry-run", action="store_true",
help="Print what would be created without writing")
args = parser.parse_args()
system_path = Path(args.system_index)
system_dir = system_path.parent
bodies_dir = system_dir / "bodies"
overrides = {}
if args.overrides:
with open(args.overrides) as f:
overrides = json.load(f)
body_defs = parse_system(str(system_path), overrides=overrides)
print(f"System: {system_path}{len(body_defs)} renderable bodies")
created = 0
skipped = 0
for bd in body_defs:
body_id = bd["id"]
body_dir = bodies_dir / body_id
index_path = body_dir / "index.md"
if index_path.exists():
print(f" skip {body_id} — index.md exists")
skipped += 1
continue
frontmatter = _body_to_frontmatter(bd)
prose = _body_prose(bd, system_dir)
content = f"---\n{frontmatter}\n---\n\n{prose}"
if args.dry_run:
print(f" would create {index_path}")
print(f" {bd.get('planet_class', '?')} / {bd.get('body_type', '?')}")
else:
body_dir.mkdir(parents=True, exist_ok=True)
with open(index_path, "w") as f:
f.write(content)
print(f" created {index_path}")
created += 1
action = "would create" if args.dry_run else "created"
print(f"\n {action} {created}, skipped {skipped}")
if __name__ == "__main__":
main()