diff --git a/server/data/systems.db b/server/data/systems.db index 95151e106..9a6a5adc2 100644 Binary files a/server/data/systems.db and b/server/data/systems.db differ diff --git a/tooling/planet-gen/populate_terrain_reference.py b/tooling/planet-gen/populate_terrain_reference.py new file mode 100644 index 000000000..9b2a28bf2 --- /dev/null +++ b/tooling/planet-gen/populate_terrain_reference.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +""" +Batch populate terrain_reference column in systems.db bodies table. + +For each inhabited body with NULL terrain_reference: + 1. Construct expected wiki heightmap path: + wiki/star-systems/{system_slug}/bodies/{body_id}/heightmap.png + where system_slug = system_id with spaces replaced by hyphens. + 2. Verify the file exists. + 3. Update terrain_reference to the relative path. + +Bodies with missing heightmaps are logged to stdout for remediation. +This is the prerequisite for generate_atlas.py (#832). + +Usage: + python3 tooling/planet-gen/populate_terrain_reference.py + python3 tooling/planet-gen/populate_terrain_reference.py --dry-run + python3 tooling/planet-gen/populate_terrain_reference.py --db path/to/systems.db + +Decisions: D-191 (atlas pipeline prerequisites) +""" + +import argparse +import sqlite3 +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_ROOT = (SCRIPT_DIR / ".." / "..").resolve() +DB_PATH = REPO_ROOT / "server" / "data" / "systems.db" +WIKI_DIR = REPO_ROOT / "wiki" / "star-systems" + + +def system_slug(system_id: str) -> str: + """Convert a system_id to its wiki directory slug. + + 'GJ 71' → 'GJ-71' + 'GJ 244A' → 'GJ-244A' + 'GJ 559B' → 'GJ-559B' + """ + return system_id.replace(" ", "-") + + +def expected_heightmap_path(system_id: str, body_id: str) -> Path: + """Return the expected absolute heightmap path for a body.""" + return WIKI_DIR / system_slug(system_id) / "bodies" / body_id / "heightmap.png" + + +def relative_terrain_reference(system_id: str, body_id: str) -> str: + """Return the terrain_reference value to store in the DB. + + Stored as a repo-root-relative path so it is portable across checkouts. + """ + return f"wiki/star-systems/{system_slug(system_id)}/bodies/{body_id}/heightmap.png" + + +def main(): + parser = argparse.ArgumentParser( + description="Populate terrain_reference column in systems.db bodies table" + ) + parser.add_argument("--db", default=str(DB_PATH), help="Path to systems.db") + parser.add_argument( + "--dry-run", + action="store_true", + help="Report without writing changes", + ) + args = parser.parse_args() + + db_path = Path(args.db) + if not db_path.exists(): + print(f"error: {db_path} not found") + raise SystemExit(1) + + conn = sqlite3.connect(str(db_path)) + conn.execute("PRAGMA foreign_keys=ON") + + # Fetch all inhabited bodies with NULL terrain_reference. + rows = conn.execute(""" + SELECT b.body_id, b.system_id + FROM bodies b + WHERE b.terrain_reference IS NULL + ORDER BY b.system_id, b.body_id + """).fetchall() + + print(f"\n terrain_reference population pass") + print(f" DB: {db_path}") + if args.dry_run: + print(f" Mode: DRY RUN") + print(f"\n {len(rows)} bodies with NULL terrain_reference\n") + + found = [] + missing = [] + + for body_id, system_id in rows: + path = expected_heightmap_path(system_id, body_id) + if path.exists(): + found.append((body_id, system_id)) + else: + missing.append((body_id, system_id, str(path))) + + # Report missing heightmaps before writing — helps flag gaps early. + if missing: + print(f" MISSING heightmaps ({len(missing)} bodies — no update for these):") + for body_id, system_id, path in missing: + print(f" {body_id} ({system_id}) → {path}") + print() + + if found: + print(f" Updating {len(found)} bodies with terrain_reference:") + for body_id, system_id in found: + ref = relative_terrain_reference(system_id, body_id) + print(f" {body_id} ({system_id}) → {ref}") + if not args.dry_run: + conn.execute( + "UPDATE bodies SET terrain_reference = ? WHERE body_id = ?", + (ref, body_id), + ) + + if not args.dry_run: + conn.commit() + print(f"\n Committed {len(found)} terrain_reference updates.") + else: + print(f"\n Dry run — no changes written.") + + conn.close() + + # Summary + print(f"\n Summary:") + print(f" Updated: {len(found)}") + print(f" Missing: {len(missing)}") + print(f" Total: {len(rows)}\n") + + if missing: + print(f" Action required: generate heightmaps for {len(missing)} bodies " + f"before running generate_atlas.py (#832).") + print(f" Use: make generate-terrain (or run generate.py per body)\n") + + +if __name__ == "__main__": + main()