magicciv/tools/migrate-flora-biomes.py

180 lines
12 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
Migrate flora species biomes[] substrate_climate[] arrays.
Usage:
python3 tools/migrate-flora-biomes.py # dry-run (print diffs)
python3 tools/migrate-flora-biomes.py --apply # write back to files
The legacy biomes[] field is kept (not deleted) for backwards compatibility.
New substrate_climate[] is additive.
"""
import argparse
import glob
import json
import os
import sys
# Translation table: biome name → list of substrate_climate entries.
# Each entry: substrate + 5-bucket t_band/p_band range (0=coldest/driest, 4=hottest/wettest).
BIOME_TO_SUBSTRATE_CLIMATE = {
"forest": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"temperate_forest": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 3, "p_band_min": 2, "p_band_max": 4}],
"boreal_forest": [{"substrate": "soil", "t_band_min": 0, "t_band_max": 1, "p_band_min": 2, "p_band_max": 4}],
"jungle": [{"substrate": "soil", "t_band_min": 4, "t_band_max": 4, "p_band_min": 4, "p_band_max": 4}],
"tropical_rainforest": [{"substrate": "soil", "t_band_min": 4, "t_band_max": 4, "p_band_min": 4, "p_band_max": 4}],
"tropical_forest": [{"substrate": "soil", "t_band_min": 3, "t_band_max": 4, "p_band_min": 3, "p_band_max": 4}],
"tropical_dry_forest": [{"substrate": "soil", "t_band_min": 3, "t_band_max": 4, "p_band_min": 1, "p_band_max": 2}],
"montane_forest": [{"substrate": "bedrock", "t_band_min": 1, "t_band_max": 2, "p_band_min": 2, "p_band_max": 4}],
"cloud_forest": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 3, "p_band_min": 3, "p_band_max": 4}],
"old_growth": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 3, "p_band_min": 2, "p_band_max": 4}],
"temperate_rainforest": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 3, "p_band_min": 3, "p_band_max": 4}],
"swamp": [{"substrate": "peat", "t_band_min": 2, "t_band_max": 3, "p_band_min": 4, "p_band_max": 4}],
"bog": [{"substrate": "peat", "t_band_min": 1, "t_band_max": 3, "p_band_min": 4, "p_band_max": 4}],
"fen": [{"substrate": "peat", "t_band_min": 2, "t_band_max": 3, "p_band_min": 3, "p_band_max": 4}],
"wetland": [{"substrate": "peat", "t_band_min": 1, "t_band_max": 4, "p_band_min": 3, "p_band_max": 4}],
"coastal_wetland": [{"substrate": "peat", "t_band_min": 2, "t_band_max": 4, "p_band_min": 3, "p_band_max": 4}],
"mangrove_adjacent": [{"substrate": "peat", "t_band_min": 3, "t_band_max": 4, "p_band_min": 3, "p_band_max": 4}],
"mangrove": [{"substrate": "peat", "t_band_min": 3, "t_band_max": 4, "p_band_min": 3, "p_band_max": 4}],
"grassland": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 3, "p_band_min": 1, "p_band_max": 2}],
"temperate_grassland": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 3, "p_band_min": 1, "p_band_max": 2}],
"savanna": [{"substrate": "soil", "t_band_min": 3, "t_band_max": 4, "p_band_min": 1, "p_band_max": 2}],
"plains": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 3, "p_band_min": 1, "p_band_max": 2}],
"tundra": [{"substrate": "permafrost", "t_band_min": 0, "t_band_max": 1, "p_band_min": 0, "p_band_max": 2}],
"arctic_tundra": [{"substrate": "permafrost", "t_band_min": 0, "t_band_max": 0, "p_band_min": 0, "p_band_max": 2}],
"alpine_tundra": [{"substrate": "bedrock", "t_band_min": 0, "t_band_max": 1, "p_band_min": 1, "p_band_max": 3}],
"mountains": [{"substrate": "bedrock", "t_band_min": 1, "t_band_max": 3, "p_band_min": 1, "p_band_max": 3}],
"montane_forest": [{"substrate": "bedrock", "t_band_min": 1, "t_band_max": 2, "p_band_min": 2, "p_band_max": 4}],
"alpine_meadow": [{"substrate": "soil", "t_band_min": 1, "t_band_max": 2, "p_band_min": 2, "p_band_max": 3}],
"highland": [{"substrate": "bedrock", "t_band_min": 1, "t_band_max": 3, "p_band_min": 1, "p_band_max": 3}],
"hills": [{"substrate": "soil", "t_band_min": 1, "t_band_max": 3, "p_band_min": 1, "p_band_max": 3}],
"basalt_highland": [{"substrate": "bedrock", "t_band_min": 1, "t_band_max": 3, "p_band_min": 0, "p_band_max": 2}],
"heathland": [{"substrate": "soil", "t_band_min": 1, "t_band_max": 2, "p_band_min": 1, "p_band_max": 3}],
"shrubland": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 3, "p_band_min": 1, "p_band_max": 2}],
"chaparral": [{"substrate": "soil", "t_band_min": 3, "t_band_max": 4, "p_band_min": 1, "p_band_max": 2}],
"desert": [{"substrate": "sand", "t_band_min": 3, "t_band_max": 4, "p_band_min": 0, "p_band_max": 0}],
"dune_field": [{"substrate": "sand", "t_band_min": 3, "t_band_max": 4, "p_band_min": 0, "p_band_max": 1}],
"dust_plain": [{"substrate": "sand", "t_band_min": 3, "t_band_max": 4, "p_band_min": 0, "p_band_max": 1}],
"rocky_waste": [{"substrate": "bedrock", "t_band_min": 1, "t_band_max": 4, "p_band_min": 0, "p_band_max": 1}],
"canyon": [{"substrate": "bedrock", "t_band_min": 2, "t_band_max": 4, "p_band_min": 0, "p_band_max": 1}],
"polar_desert": [{"substrate": "ice", "t_band_min": 0, "t_band_max": 0, "p_band_min": 0, "p_band_max": 1}],
"ice": [{"substrate": "ice", "t_band_min": 0, "t_band_max": 0, "p_band_min": 0, "p_band_max": 4}],
"snow": [{"substrate": "ice", "t_band_min": 0, "t_band_max": 1, "p_band_min": 1, "p_band_max": 4}],
"sea_ice": [{"substrate": "ice", "t_band_min": 0, "t_band_max": 0, "p_band_min": 0, "p_band_max": 4}],
"river": [{"substrate": "water", "t_band_min": 0, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"lake": [{"substrate": "water", "t_band_min": 0, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"pond": [{"substrate": "water", "t_band_min": 0, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"estuary": [{"substrate": "water", "t_band_min": 1, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"intertidal": [{"substrate": "seawater", "t_band_min": 1, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"coast": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"coastal_dune": [{"substrate": "sand", "t_band_min": 1, "t_band_max": 4, "p_band_min": 1, "p_band_max": 3}],
"coastal_sea": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"coastal_shallows": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"coastal_cliffs": [{"substrate": "bedrock", "t_band_min": 0, "t_band_max": 4, "p_band_min": 1, "p_band_max": 4}],
"ocean": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 4, "p_band_min": 0, "p_band_max": 4}],
"deep_ocean": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 4, "p_band_min": 0, "p_band_max": 4}],
"ocean_surface": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 4, "p_band_min": 0, "p_band_max": 4}],
"shallow_ocean": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 4, "p_band_min": 0, "p_band_max": 4}],
"shallow_sea": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 4, "p_band_min": 0, "p_band_max": 4}],
"cold_shallow_sea": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 1, "p_band_min": 0, "p_band_max": 4}],
"reef": [{"substrate": "seawater", "t_band_min": 3, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"coral_reef": [{"substrate": "seawater", "t_band_min": 3, "t_band_max": 4, "p_band_min": 2, "p_band_max": 4}],
"kelp_forest": [{"substrate": "seawater", "t_band_min": 1, "t_band_max": 3, "p_band_min": 2, "p_band_max": 4}],
"abyssal_plain": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 2, "p_band_min": 0, "p_band_max": 4}],
"hadal_zone": [{"substrate": "seawater", "t_band_min": 0, "t_band_max": 1, "p_band_min": 0, "p_band_max": 4}],
"inland_sea": [{"substrate": "seawater", "t_band_min": 1, "t_band_max": 4, "p_band_min": 0, "p_band_max": 4}],
"volcanic": [{"substrate": "lava", "t_band_min": 0, "t_band_max": 4, "p_band_min": 0, "p_band_max": 4}],
"lava_field": [{"substrate": "lava", "t_band_min": 0, "t_band_max": 4, "p_band_min": 0, "p_band_max": 4}],
"volcanic_plains": [{"substrate": "bedrock", "t_band_min": 2, "t_band_max": 4, "p_band_min": 0, "p_band_max": 2}],
"subterranean": [{"substrate": "bedrock", "t_band_min": 1, "t_band_max": 3, "p_band_min": 0, "p_band_max": 2}],
"cave": [{"substrate": "bedrock", "t_band_min": 1, "t_band_max": 3, "p_band_min": 0, "p_band_max": 2}],
"underdark": [{"substrate": "bedrock", "t_band_min": 1, "t_band_max": 3, "p_band_min": 0, "p_band_max": 2}],
"ancient_lakebed": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 4, "p_band_min": 0, "p_band_max": 2}],
"lowland_basin": [{"substrate": "soil", "t_band_min": 2, "t_band_max": 4, "p_band_min": 1, "p_band_max": 3}],
"cliffs": [{"substrate": "bedrock", "t_band_min": 0, "t_band_max": 4, "p_band_min": 0, "p_band_max": 4}],
}
def build_substrate_climate(biomes: list[str]) -> tuple[list[dict], list[str]]:
"""Return (entries, missing_biomes) where missing_biomes had no mapping."""
seen_keys: set[str] = set()
result: list[dict] = []
missing: list[str] = []
for biome in biomes:
if biome not in BIOME_TO_SUBSTRATE_CLIMATE:
missing.append(biome)
continue
for entry in BIOME_TO_SUBSTRATE_CLIMATE[biome]:
key = (entry["substrate"], entry["t_band_min"], entry["t_band_max"],
entry["p_band_min"], entry["p_band_max"])
if key not in seen_keys:
seen_keys.add(key)
result.append(entry)
return result, missing
def process_file(path: str, apply: bool) -> bool:
"""Process one species file. Returns True if file was (or would be) changed."""
with open(path) as f:
data = json.load(f)
biomes = data.get("biomes", [])
if not biomes:
return False
new_entries, missing = build_substrate_climate(biomes)
if missing:
rel = os.path.relpath(path)
for m in missing:
print(f" WARN {rel}: unmapped biome '{m}'", file=sys.stderr)
if not new_entries:
return False
existing = data.get("substrate_climate", [])
if existing == new_entries:
return False
if apply:
data["substrate_climate"] = new_entries
with open(path, "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
f.write("\n")
else:
rel = os.path.relpath(path)
print(f" DIFF {rel}")
print(f" biomes: {biomes}")
print(f" substrate_climate (new): {json.dumps(new_entries, separators=(',', ':'))}")
return True
def main() -> None:
parser = argparse.ArgumentParser(description="Migrate flora biomes[] → substrate_climate[]")
parser.add_argument("--apply", action="store_true", help="Write changes (default is dry-run)")
args = parser.parse_args()
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
pattern = os.path.join(repo_root, "public/resources/ecology/flora/species/*.json")
files = sorted(glob.glob(pattern))
if not files:
print(f"No files matched: {pattern}", file=sys.stderr)
sys.exit(1)
changed = 0
for path in files:
if process_file(path, apply=args.apply):
changed += 1
mode = "Applied" if args.apply else "Would change"
print(f"{mode} {changed}/{len(files)} flora species files.")
if __name__ == "__main__":
main()