diff --git a/server/data/systems.db b/server/data/systems.db index efaf24b16..5ac1e0a86 100644 Binary files a/server/data/systems.db and b/server/data/systems.db differ diff --git a/tooling/atlas-helpers.sh b/tooling/atlas-helpers.sh new file mode 100755 index 000000000..a65f6dfb2 --- /dev/null +++ b/tooling/atlas-helpers.sh @@ -0,0 +1,146 @@ +#!/bin/bash +# Atlas helper functions for batch body catalog authoring +# Usage: source tooling/atlas-helpers.sh + +ATLAS="tooling/atlas" + +# Get all existing proper names (for collision avoidance) +atlas_existing_names() { + { + $ATLAS list-bodies 2>/dev/null | python3 -c " +import sys,json +d=json.load(sys.stdin) +for b in d: + if b.get('proper_name'): print(b['proper_name']) +" + $ATLAS list-stations 2>/dev/null | python3 -c " +import sys,json +d=json.load(sys.stdin) +for s in d: + if s.get('proper_name'): print(s['proper_name']) +" + } | sort -u +} + +# List systems that already have bodies +atlas_systems_with_bodies() { + $ATLAS list-bodies 2>/dev/null | python3 -c " +import sys,json +d=json.load(sys.stdin) +for s in sorted(set(b['system_id'] for b in d)): print(s) +" +} + +# Check if a system already has bodies +atlas_system_has_bodies() { + local sys_id="$1" + $ATLAS list-bodies --system "$sys_id" 2>/dev/null | python3 -c " +import sys,json +d=json.load(sys.stdin) +print('yes' if len(d) > 0 else 'no') +" +} + +# Show system info +atlas_show() { + $ATLAS show-system "$1" 2>/dev/null +} + +# Author system (generate base proposal) +atlas_author() { + $ATLAS author "$1" 2>/dev/null +} + +# Verify a proposal JSON against integrity checks +atlas_verify_proposal() { + local proposal="$1" + python3 -c " +import json, sys + +with open('$proposal') as f: + p = json.load(f) + +errors = [] +bodies = p.get('bodies', []) +stations = p.get('stations', []) + +# 1. Inhabited bodies must have names +for b in bodies: + if b.get('inhabited') and not b.get('proper_name'): + errors.append(f\"Inhabited body {b['body_id']} has no proper_name\") + +# 2. Uninhabited bodies should have null names +for b in bodies: + if not b.get('inhabited') and b.get('proper_name'): + errors.append(f\"Uninhabited body {b['body_id']} has proper_name '{b['proper_name']}' (should be null)\") + +# 3. All stations must have names +for s in stations: + if not s.get('proper_name'): + errors.append(f\"Station {s['station_id']} has no proper_name\") + +# 4. Body count check (planets only, excluding moons/belts/oort) +planets = [b for b in bodies if b['body_type'] == 'planet'] +star_type = p.get('spectral_class', 'M') +if star_type.startswith(('G', 'F')): + min_planets = 8 +elif star_type.startswith('K'): + min_planets = 7 +else: + min_planets = 6 +if len(planets) < min_planets: + errors.append(f\"Only {len(planets)} planets, need {min_planets}+ for {star_type} star\") + +# 5. Required structures +oort = [b for b in bodies if b['body_type'] == 'oort_cloud'] +if not oort: + errors.append('No oort cloud') +horizon = [s for s in stations if s['station_type'] == 'horizon'] +if not horizon: + errors.append('No horizon station') +elif not any(s.get('has_gate_infrastructure') for s in horizon): + errors.append('Horizon station missing has_gate_infrastructure: true') +belts = [b for b in bodies if b['body_type'] == 'asteroid_belt'] +if not belts: + errors.append('No asteroid belt (add one unless wiki contradicts)') + +# 6. Orbit consistency for top-level bodies +top_level = [b for b in bodies if not b.get('parent_body_id')] +orbits = [b['orbit_index'] for b in top_level] +if orbits != sorted(orbits): + errors.append(f\"Top-level orbit_index not monotonic: {orbits}\") +if len(orbits) != len(set(orbits)): + errors.append(f\"Duplicate orbit_index in top-level: {orbits}\") + +# 7. Moon orbit consistency +parents = {} +for b in bodies: + pid = b.get('parent_body_id') + if pid: + parents.setdefault(pid, []).append(b['orbit_index']) +for pid, idxs in parents.items(): + if idxs != sorted(idxs): + errors.append(f\"Moon orbits under {pid} not monotonic: {idxs}\") + if len(idxs) != len(set(idxs)): + errors.append(f\"Duplicate moon orbit_index under {pid}: {idxs}\") + +# 8. Parent references valid +body_ids = set(b['body_id'] for b in bodies) +for b in bodies: + pid = b.get('parent_body_id') + if pid and pid not in body_ids: + errors.append(f\"Body {b['body_id']} references missing parent {pid}\") +for s in stations: + oid = s.get('orbits_body_id') + if oid and oid not in body_ids: + errors.append(f\"Station {s['station_id']} references missing body {oid}\") + +if errors: + print('FAIL') + for e in errors: + print(f' - {e}') + sys.exit(1) +else: + print('PASS — all integrity checks passed') +" +} diff --git a/tooling/atlas-names b/tooling/atlas-names new file mode 100755 index 000000000..5563ed0ce --- /dev/null +++ b/tooling/atlas-names @@ -0,0 +1,15 @@ +#!/bin/bash +# List all existing proper names in the atlas DB (for collision avoidance) +cd "$(dirname "$0")/.." +{ + tooling/atlas list-bodies 2>/dev/null | python3 -c " +import sys,json +for b in json.load(sys.stdin): + if b.get('proper_name'): print(b['proper_name']) +" + tooling/atlas list-stations 2>/dev/null | python3 -c " +import sys,json +for s in json.load(sys.stdin): + if s.get('proper_name'): print(s['proper_name']) +" +} | sort -u diff --git a/tooling/atlas-systems-done b/tooling/atlas-systems-done new file mode 100755 index 000000000..4c2c1cdb3 --- /dev/null +++ b/tooling/atlas-systems-done @@ -0,0 +1,7 @@ +#!/bin/bash +# List systems that already have bodies in the atlas DB +cd "$(dirname "$0")/.." +tooling/atlas list-bodies 2>/dev/null | python3 -c " +import sys,json +for s in sorted(set(b['system_id'] for b in json.load(sys.stdin))): print(s) +" diff --git a/tooling/atlas-verify b/tooling/atlas-verify new file mode 100755 index 000000000..c6ac437c4 --- /dev/null +++ b/tooling/atlas-verify @@ -0,0 +1,93 @@ +#!/bin/bash +# Verify a proposal JSON against integrity checks +# Usage: tooling/atlas-verify docs/atlas/proposals/GJ273.json +cd "$(dirname "$0")/.." +python3 -c " +import json, sys + +with open('$1') as f: + p = json.load(f) + +errors = [] +bodies = p.get('bodies', []) +stations = p.get('stations', []) + +# 1. Inhabited bodies must have names +for b in bodies: + if b.get('inhabited') and not b.get('proper_name'): + errors.append(f\"Inhabited body {b['body_id']} has no proper_name\") + +# 2. Uninhabited bodies should have null names +for b in bodies: + if not b.get('inhabited') and b.get('proper_name'): + errors.append(f\"Uninhabited body {b['body_id']} has proper_name '{b['proper_name']}' (should be null)\") + +# 3. All stations must have names +for s in stations: + if not s.get('proper_name'): + errors.append(f\"Station {s['station_id']} has no proper_name\") + +# 4. Body count check +planets = [b for b in bodies if b['body_type'] == 'planet'] +star_type = p.get('spectral_class', 'M') +if star_type.startswith(('G', 'F')): + min_planets = 8 +elif star_type.startswith('K'): + min_planets = 7 +else: + min_planets = 6 +if len(planets) < min_planets: + errors.append(f\"Only {len(planets)} planets, need {min_planets}+ for {star_type} star\") + +# 5. Required structures +oort = [b for b in bodies if b['body_type'] == 'oort_cloud'] +if not oort: + errors.append('No oort cloud') +horizon = [s for s in stations if s['station_type'] == 'horizon'] +if not horizon: + errors.append('No horizon station') +elif not any(s.get('has_gate_infrastructure') for s in horizon): + errors.append('Horizon station missing has_gate_infrastructure: true') +belts = [b for b in bodies if b['body_type'] == 'asteroid_belt'] +if not belts: + errors.append('No asteroid belt') + +# 6. Orbit consistency +top_level = [b for b in bodies if not b.get('parent_body_id')] +orbits = [b['orbit_index'] for b in top_level] +if orbits != sorted(orbits): + errors.append(f\"Top-level orbit_index not monotonic: {orbits}\") +if len(orbits) != len(set(orbits)): + errors.append(f\"Duplicate orbit_index in top-level: {orbits}\") + +# 7. Moon orbit consistency +parents = {} +for b in bodies: + pid = b.get('parent_body_id') + if pid: + parents.setdefault(pid, []).append(b['orbit_index']) +for pid, idxs in parents.items(): + if idxs != sorted(idxs): + errors.append(f\"Moon orbits under {pid} not monotonic: {idxs}\") + if len(idxs) != len(set(idxs)): + errors.append(f\"Duplicate moon orbit_index under {pid}: {idxs}\") + +# 8. Parent references valid +body_ids = set(b['body_id'] for b in bodies) +for b in bodies: + pid = b.get('parent_body_id') + if pid and pid not in body_ids: + errors.append(f\"Body {b['body_id']} references missing parent {pid}\") +for s in stations: + oid = s.get('orbits_body_id') + if oid and oid not in body_ids: + errors.append(f\"Station {s['station_id']} references missing body {oid}\") + +if errors: + print('FAIL') + for e in errors: + print(f' - {e}') + sys.exit(1) +else: + print('PASS') +"