ruff F401 — unused 'import os'. Pre-existing in the globe-coverage script; ruff runs at pre-push (not pre-commit), so it surfaced only now and blocked the push. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
176 lines
5.2 KiB
Python
176 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
One-shot script: fill missing globe.png files in the wiki by copying a donor
|
|
globe from the same planet_class + body_type pool.
|
|
|
|
Donor selection:
|
|
- Hash body_id to pick deterministically from the pool
|
|
- Track used donors per system to avoid visual repetition
|
|
- Skip oort_cloud and asteroid_belt (handled by generic images)
|
|
|
|
Run once, then the files are baked into the wiki like authored content.
|
|
|
|
Usage:
|
|
python3 tooling/fill-missing-globes.py [--dry-run]
|
|
"""
|
|
|
|
import hashlib
|
|
import shutil
|
|
import sqlite3
|
|
import sys
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
WIKI_ROOT = REPO_ROOT / "wiki" / "star-systems"
|
|
DB_PATH = REPO_ROOT / "server" / "data" / "systems.db"
|
|
|
|
GENERIC_TYPES = {"oort_cloud", "asteroid_belt"}
|
|
GENERIC_DIR = WIKI_ROOT / "_generics"
|
|
|
|
|
|
def globe_path(system_id: str, body_id: str) -> Path:
|
|
sys_dir = system_id.replace(" ", "-")
|
|
return WIKI_ROOT / sys_dir / "bodies" / body_id / "globe.png"
|
|
|
|
|
|
def body_hash(body_id: str) -> int:
|
|
return int(hashlib.sha256(body_id.encode()).hexdigest()[:8], 16)
|
|
|
|
|
|
def main():
|
|
dry_run = "--dry-run" in sys.argv
|
|
|
|
conn = sqlite3.connect(str(DB_PATH))
|
|
conn.row_factory = sqlite3.Row
|
|
|
|
bodies = conn.execute(
|
|
"SELECT body_id, system_id, body_type, planet_class FROM bodies"
|
|
).fetchall()
|
|
|
|
# Build pools of existing globes by (body_type, planet_class)
|
|
pools: dict[tuple[str, str], list[Path]] = defaultdict(list)
|
|
missing: list[dict] = []
|
|
|
|
for b in bodies:
|
|
bid = b["body_id"]
|
|
bt = b["body_type"] or "unknown"
|
|
pc = (b["planet_class"] or "unknown").lower()
|
|
path = globe_path(b["system_id"], bid)
|
|
|
|
if bt in GENERIC_TYPES:
|
|
# Handled by generic images, not donors
|
|
if not path.exists():
|
|
generic = GENERIC_DIR / bt / "globe.png"
|
|
if generic.exists():
|
|
missing.append({
|
|
"body_id": bid,
|
|
"system_id": b["system_id"],
|
|
"body_type": bt,
|
|
"planet_class": pc,
|
|
"donor_path": generic,
|
|
"source": "generic",
|
|
})
|
|
continue
|
|
|
|
if path.exists():
|
|
pools[(bt, pc)].append(path)
|
|
else:
|
|
missing.append({
|
|
"body_id": bid,
|
|
"system_id": b["system_id"],
|
|
"body_type": bt,
|
|
"planet_class": pc,
|
|
"donor_path": None,
|
|
"source": "donor",
|
|
})
|
|
|
|
# For each missing body, pick a donor
|
|
used_per_system: dict[str, set[str]] = defaultdict(set)
|
|
assigned = 0
|
|
skipped = 0
|
|
log_lines = []
|
|
|
|
for m in missing:
|
|
if m["source"] == "generic":
|
|
# Generic image copy
|
|
target = globe_path(m["system_id"], m["body_id"])
|
|
if dry_run:
|
|
log_lines.append(
|
|
f"[GENERIC] {m['body_id']} <- {m['donor_path'].name} ({m['body_type']})"
|
|
)
|
|
else:
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(m["donor_path"], target)
|
|
log_lines.append(
|
|
f"[GENERIC] {m['body_id']} <- {m['donor_path']}"
|
|
)
|
|
assigned += 1
|
|
continue
|
|
|
|
bt = m["body_type"]
|
|
pc = m["planet_class"]
|
|
key = (bt, pc)
|
|
|
|
pool = pools.get(key, [])
|
|
if not pool:
|
|
# Try broader match: same body_type, any class
|
|
for (pbt, ppc), p in pools.items():
|
|
if pbt == bt and p:
|
|
pool = p
|
|
break
|
|
|
|
if not pool:
|
|
log_lines.append(
|
|
f"[SKIP] {m['body_id']} — no donors for {bt}/{pc}"
|
|
)
|
|
skipped += 1
|
|
continue
|
|
|
|
# Pick donor by hash, avoid repeats in same system
|
|
sys_id = m["system_id"]
|
|
h = body_hash(m["body_id"])
|
|
pool_size = len(pool)
|
|
used = used_per_system[sys_id]
|
|
|
|
donor = None
|
|
for offset in range(pool_size):
|
|
candidate = pool[(h + offset) % pool_size]
|
|
candidate_key = str(candidate)
|
|
if candidate_key not in used:
|
|
donor = candidate
|
|
used.add(candidate_key)
|
|
break
|
|
|
|
if donor is None:
|
|
# All donors used in this system — allow repeat from least-used
|
|
donor = pool[h % pool_size]
|
|
|
|
target = globe_path(sys_id, m["body_id"])
|
|
donor_bid = donor.parent.name
|
|
|
|
if dry_run:
|
|
log_lines.append(
|
|
f"[DONOR] {m['body_id']} <- {donor_bid} ({bt}/{pc})"
|
|
)
|
|
else:
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(donor, target)
|
|
log_lines.append(
|
|
f"[DONOR] {m['body_id']} <- {donor_bid} ({bt}/{pc})"
|
|
)
|
|
assigned += 1
|
|
|
|
conn.close()
|
|
|
|
# Report
|
|
prefix = "[DRY RUN] " if dry_run else ""
|
|
print(f"{prefix}Assigned: {assigned}, Skipped: {skipped}")
|
|
print()
|
|
for line in log_lines:
|
|
print(line)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|