The 30-file tree moves under atlas as its third rung (D-243), ten verbs fronting it. Each verb restates its module's options so `--help` describes something; tooling/test_planet_router.py hands every declared option to the module's own argparse and fails on drift, and now runs in make test-tooling. The 2026-09-02 half of this move had converted the top-level imports and the repo roots. Finishing it found what the half-move left: - Lazy in-function imports, and all of sol_data/, still named siblings bare. They resolved only through sys.path.insert hacks, so under reach the first globe render in generate, batch or sol-import would have raised ModuleNotFoundError. Qualified; the hacks are gone. - 247 print() calls and a stdout progress writer that fired once per 8 KB block. Report verbs (audit, quality) write through console.out, progress through console.event, and download progress is throttled to 10% steps so a job log is not tens of thousands of lines. - Every error exit raises ReachError with a fix. Two checks that could not fail: - batch --verify-determinism printed a warning and exited 0 on a mismatch. - import-provinces exited 0 with errors > 0. Both now raise. The 271-body bake is only safe to re-run because the first one holds. sol-import --body is action="append" in the module but the router took one value, so --body GJ0d --body GJ0e kept one. Now repeatable, and _flags repeats list options. test_conformance walked one level, so a nested group was reported as a verb missing @command and its ten verbs were never checked. It recurses now; proven by stripping @command from `planet quality` and watching it fail. Stray PNGs from the 2026-09-03 runaway router-test run are parked in .cache/t1288-stray-pngs/, not committed. Their reliefmaps differ from HEAD while the heightmap regenerated byte-identical — filed as T-1291. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99 lines
4.1 KiB
Python
99 lines
4.1 KiB
Python
"""
|
|
biome_config.py — loads biomes.toml and provides lookup structures.
|
|
|
|
Single source of truth for biome classification, colors, and rendering
|
|
parameters. All three pipeline modules import from here instead of
|
|
maintaining their own hardcoded tables.
|
|
|
|
Usage:
|
|
from tooling.domains.atlas.planet.biome_config import (
|
|
WHITTAKER_TABLE, CLASS_T_BAND, BIOME_PALETTE,
|
|
STAR_TINTS, ATMO_COLORS, GAS_PALETTES,
|
|
EXOTIC_CLASSES, CRATER_SCALING, RIVER_RGB, COAST_RGB,
|
|
)
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import tomllib
|
|
except ModuleNotFoundError:
|
|
import tomli as tomllib # Python < 3.11 fallback
|
|
|
|
_CONFIG_PATH = Path(__file__).resolve().parent / "biomes.toml"
|
|
|
|
|
|
def _load():
|
|
with open(_CONFIG_PATH, "rb") as f:
|
|
return tomllib.load(f)
|
|
|
|
|
|
_CFG = _load()
|
|
|
|
|
|
# ── Whittaker table ──────────────────────────────────────────────────
|
|
# List of (temp_lo, temp_hi, moist_lo, moist_hi, class_id) tuples.
|
|
WHITTAKER_TABLE = [
|
|
(w["temp_lo"], w["temp_hi"], w["moist_lo"], w["moist_hi"], w["id"])
|
|
for w in _CFG["whittaker"]
|
|
]
|
|
|
|
# ── Temperature bands per planet class ───────────────────────────────
|
|
CLASS_T_BAND = {
|
|
k: tuple(v) for k, v in _CFG["temperature_bands"].items()
|
|
}
|
|
|
|
# ── Biome color palette ──────────────────────────────────────────────
|
|
# {class_id: {"cartographic": (R,G,B), "photographic": (R,G,B), "name": str}}
|
|
BIOME_PALETTE = {}
|
|
for key, val in _CFG["biome_colors"].items():
|
|
cid = int(key)
|
|
BIOME_PALETTE[cid] = {
|
|
"cartographic": tuple(val["cartographic"]),
|
|
"photographic": tuple(val["photographic"]),
|
|
"name": val.get("name", f"class_{cid}"),
|
|
}
|
|
|
|
# Max class ID for array sizing
|
|
MAX_BIOME_ID = max(BIOME_PALETTE.keys())
|
|
|
|
|
|
def build_biome_rgb(mode: str = "cartographic") -> dict:
|
|
"""Returns {class_id: (R, G, B)} for the given render mode."""
|
|
return {k: v[mode] for k, v in BIOME_PALETTE.items()}
|
|
|
|
|
|
# ── Render colors ────────────────────────────────────────────────────
|
|
RIVER_RGB = tuple(_CFG["render_colors"]["river"])
|
|
COAST_RGB = tuple(_CFG["render_colors"]["coastline"])
|
|
|
|
# ── Star tints ───────────────────────────────────────────────────────
|
|
STAR_TINTS = {k: tuple(v) for k, v in _CFG["star_tints"].items()}
|
|
|
|
# ── Atmosphere colors ────────────────────────────────────────────────
|
|
ATMO_COLORS = {k: tuple(v) for k, v in _CFG["atmosphere_colors"].items()}
|
|
# Planet classes without atmosphere glow
|
|
for _no_atmo in ("barren", "moon", "gas_giant", "gas_giant_ringed"):
|
|
ATMO_COLORS.setdefault(_no_atmo, None)
|
|
|
|
# ── Gas giant palettes ───────────────────────────────────────────────
|
|
GAS_PALETTES = {
|
|
k: [tuple(band) for band in v]
|
|
for k, v in _CFG["gas_palettes"].items()
|
|
if k != "selection_order"
|
|
}
|
|
|
|
# Locked selection list — order determines which body gets which palette.
|
|
# Only append, never reorder or remove.
|
|
GAS_PALETTE_SELECTION = list(_CFG["gas_palettes"]["selection_order"])
|
|
|
|
# ── Exotic biome class IDs ───────────────────────────────────────────
|
|
EXOTIC_CLASSES = dict(_CFG["exotic_classes"])
|
|
|
|
# ── Crater scaling ───────────────────────────────────────────────────
|
|
CRATER_SCALING = {
|
|
"base_count": _CFG["crater_scaling"]["base_count"],
|
|
"atmosphere": dict(_CFG["crater_scaling"]["atmosphere"]),
|
|
"tectonics": dict(_CFG["crater_scaling"]["tectonics"]),
|
|
}
|