180 lines
12 KiB
Python
180 lines
12 KiB
Python
|
|
#!/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()
|