- 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>
195 lines
6.6 KiB
Python
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"")
|
|
lines.append("")
|
|
if pclass not in ("gas_giant",):
|
|
lines.append(f"")
|
|
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()
|