feat(db): body_radius_km scatter + gas giant/moon scale classes

Deterministic ±scatter on body radii seeded by body_id hash — no two
bodies share the same radius. Gas giants 40k-60k km, moons 200-2600 km,
rocky planets ±15% from class base. Oort/asteroid skip radius (NULL).
Sol system gets real planetary radii. body_radius_km exported to
star_map_data.json for client orbital diagram sizing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-03 20:11:31 +02:00
co-authored by Claude Opus 4.6
parent e8b4dc74b5
commit 29f9945291
5 changed files with 8902 additions and 5636 deletions
File diff suppressed because it is too large Load Diff
Binary file not shown.
+35 -10
View File
@@ -1107,29 +1107,54 @@ def populate_body_radius_km(conn: sqlite3.Connection, dry_run: bool) -> int:
"super_earth": 8000.0,
"earth_like": 6371.0,
"earth": 6371.0,
"sub_earth": 4500.0,
"sub_earth": 3500.0,
"ocean_world": 6500.0,
"arid": 5800.0,
"frozen": 4500.0,
"frozen": 3500.0,
"ice_world": 3000.0,
"barren": 4500.0,
"barren": 3500.0,
"volcanic": 5500.0,
"temperate": 6371.0,
"moon": 1737.0,
}
DEFAULT_RADIUS = 6371.0
SKIP_RADIUS_TYPES = {"oort_cloud", "asteroid_belt"}
GAS_GIANT_RADIUS = {
"gas_giant": 50000.0,
"ice_giant": 25000.0,
}
GAS_GIANT_DEFAULT = 45000.0
GAS_GIANT_SCATTER = 0.20 # ±20%
rows = conn.execute(
"SELECT body_id, planet_class FROM bodies WHERE body_radius_km IS NULL"
"SELECT body_id, planet_class, body_type, mass_class FROM bodies WHERE body_radius_km IS NULL"
).fetchall()
import hashlib
SCATTER_FRACTION = 0.15 # ±15% for rocky bodies
updates = []
for body_id, planet_class in rows:
if planet_class and planet_class.lower() == "gas_giant":
continue # gas giants have no settlements; leave NULL
radius = PLANET_CLASS_RADIUS.get(
(planet_class or "").lower(), DEFAULT_RADIUS
)
for body_id, planet_class, body_type, mass_class in rows:
if body_type in SKIP_RADIUS_TYPES:
continue
h = int(hashlib.sha256(body_id.encode()).hexdigest()[:8], 16)
scatter_val = (h / 0xFFFFFFFF) * 2.0 - 1.0 # [-1.0, 1.0]
if body_type == "gas_giant":
mc = (mass_class or "").lower()
base_radius = GAS_GIANT_RADIUS.get(mc, GAS_GIANT_DEFAULT)
radius = round(base_radius * (1.0 + scatter_val * GAS_GIANT_SCATTER), 1)
elif body_type == "moon":
base_radius = 1400.0
scatter_frac = 0.86 # ±86% → ~1962604 km
radius = round(base_radius * (1.0 + scatter_val * scatter_frac), 1)
else:
base_radius = PLANET_CLASS_RADIUS.get(
(planet_class or "").lower(), DEFAULT_RADIUS
)
radius = round(base_radius * (1.0 + scatter_val * SCATTER_FRACTION), 1)
updates.append((radius, body_id))
if not dry_run and updates:
+2 -1
View File
@@ -197,7 +197,7 @@ def generate() -> dict:
cur.execute("""
SELECT system_id, body_id, parent_body_id, orbit_index, body_type,
proper_name, atmosphere, inhabited, population,
terrain_reference, mass_class
terrain_reference, mass_class, body_radius_km
FROM bodies
ORDER BY system_id, orbit_index
""")
@@ -215,6 +215,7 @@ def generate() -> dict:
"population": int(row["population"] or 0),
"terrain_reference": row["terrain_reference"],
"mass_class": row["mass_class"],
"body_radius_km": float(row["body_radius_km"] or 0),
})
# Per-system station list for atlas orbital diagram (D-191 §6)
+1 -1
View File
@@ -85,7 +85,7 @@ _CAPTURE_FILE = None # vestigial — see note above
# Default binary + model paths. The sr-voice binary is platform-specific
# (GPU backend baked in per-build) and lives OUTSIDE any git worktree so
# it survives sprint-worktree cleanup:
# persistent across worktree cleanup:
#
# ~/Projects/settled-reach/binaries/sr-voice-rocm (AMD / ROCm)
# ~/Projects/settled-reach/binaries/sr-voice-cuda (NVIDIA, future)